prepare release 0.8.6

...implemented official byte-Encoder, many more
This commit is contained in:
Wastl Kraus 2025-09-27 15:05:58 +02:00 committed by GitHub
commit 9dc91a01c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 620 additions and 612 deletions

View File

@ -1,93 +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 name: ESP32 Build & Quality Check
# Set permissions for the GitHub token.
# 'contents: read' is required to checkout the repository.
permissions: permissions:
contents: read contents: read
# Define the trigger for the workflow.
# It runs on any push to any branch.
on: on:
push: push:
branches: [ '*' ] branches: [ '*' ]
# Concurrency control ensures that only the latest commit for a branch
# runs, canceling any workflows already in progress for the same branch.
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: 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.
# ============================================================================ # ============================================================================
prepare-cache:
quality-check-linux: name: Prepare Cache (Linux)
name: Arduino Lint Check (Linux)
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v5 - name: Checkout Repository
- uses: arduino/setup-arduino-cli@v2 uses: actions/checkout@v4
- name: Restore Arduino Core Cache
id: cache-core-linux - name: Setup Arduino CLI
uses: actions/cache/restore@v4 uses: arduino/setup-arduino-cli@v2
- name: Restore Arduino Core & Libraries Cache
uses: actions/cache@v4
id: cache-arduino
with: with:
path: | path: |
~/.arduino15/packages ~/.arduino15/packages
~/.arduino15/cache ~/.arduino15/cache
key: arduino-core-linux-esp32-v1 ~/Arduino/libraries
restore-keys: | key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }}
arduino-core-linux-
- name: Ensure ESP32 Core is installed (Linux) - name: Install Dependencies
if: steps.cache-arduino.outputs.cache-hit != 'true'
shell: bash shell: bash
run: | run: |
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
if ! arduino-cli core list | grep -q 'esp32'; then
arduino-cli core install esp32:esp32 arduino-cli core install esp32:esp32
fi arduino-cli lib install "ArduinoJson"
- name: Save Arduino Core Cache mkdir -p "$HOME/Arduino/libraries"
if: always() && steps.cache-core-linux.outputs.cache-hit != 'true' git clone --depth=1 https://github.com/ESP32Async/ESPAsyncWebServer "$HOME/Arduino/libraries/ESPAsyncWebServer"
uses: actions/cache/save@v4 git clone --depth=1 https://github.com/ESP32Async/AsyncTCP "$HOME/Arduino/libraries/AsyncTCP"
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
quality-check-windows: # ============================================================================
name: Arduino Lint Check (Windows) # Code Quality & Linting
runs-on: windows-latest # 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 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v5 - name: Checkout Repository
- uses: arduino/setup-arduino-cli@v2 uses: actions/checkout@v4
- name: Restore Arduino Core Cache
id: cache-core-win - name: Setup Arduino CLI
uses: actions/cache/restore@v4 uses: arduino/setup-arduino-cli@v2
- name: Restore Arduino Core & Libraries Cache
uses: actions/cache@v4
with: with:
path: | path: |
~/.arduino15/packages ~/.arduino15/packages
~/.arduino15/cache ~/.arduino15/cache
key: arduino-core-windows-esp32-v1 ~/Arduino/libraries
restore-keys: | key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }}
arduino-core-windows-
- name: Ensure ESP32 Core is installed (Windows) - name: Run Arduino Lint
shell: pwsh uses: arduino/arduino-lint-action@v2
run: |
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
with: with:
path: ${{ github.workspace }} path: ${{ github.workspace }}
compliance: strict compliance: strict
@ -96,135 +95,67 @@ jobs:
# ============================================================================ # ============================================================================
# Compilation Test # Compilation Test
# This job compiles all example sketches.
# ============================================================================ # ============================================================================
compile-sketches:
compile-test-linux: name: Compile ${{ matrix.sketch }} (Linux)
name: Compile Sketches (Linux) needs: prepare-cache
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
steps: strategy:
- uses: actions/checkout@v5 fail-fast: false
- uses: arduino/setup-arduino-cli@v2 matrix:
- name: Restore Arduino Core & Libraries Cache sketch:
id: cache-all-linux - examples/throttle_percent/throttle_percent.ino
uses: actions/cache/restore@v4 - examples/dshot300/dshot300.ino
with: - examples/web_control/web_control.ino
path: | - examples/web_client/web_client.ino
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-linux-esp32-v1
restore-keys: |
arduino-full-linux-
- name: Ensure ESP32 Core and Dependencies (Linux)
shell: bash
run: |
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
if ! arduino-cli core list | grep -q 'esp32'; then
arduino-cli core install esp32:esp32
fi
mkdir -p ~/Arduino/libraries
if [ ! -d ~/Arduino/libraries/ArduinoJson ]; then
git clone --depth=1 https://github.com/bblanchon/ArduinoJson.git ~/Arduino/libraries/ArduinoJson
fi
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: Save Arduino Core & Libraries Cache
if: always() && steps.cache-all-linux.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-linux-esp32-v1
- name: Compile Sketch
run: |
arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} examples/dshot300/dshot300.ino
compile-test-windows:
name: Compile Sketches (Windows)
runs-on: windows-latest
timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v5 - name: Checkout Repository
- uses: arduino/setup-arduino-cli@v2 uses: actions/checkout@v4
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
- name: Restore Arduino Core & Libraries Cache - name: Restore Arduino Core & Libraries Cache
id: cache-all-win uses: actions/cache@v4
uses: actions/cache/restore@v4
with: with:
path: | path: |
~/.arduino15/packages ~/.arduino15/packages
~/.arduino15/cache ~/.arduino15/cache
~/Arduino/libraries ~/Arduino/libraries
key: arduino-full-windows-esp32-v1 key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }}
restore-keys: |
arduino-full-windows-
- name: Ensure ESP32 Core and Dependencies (Windows)
shell: pwsh
run: |
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
}
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-win.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-windows-esp32-v1
- name: Compile Sketch - name: Compile Sketch
run: | run: arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} "${{ matrix.sketch }}"
arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} examples/dshot300/dshot300.ino
# ============================================================================ # ============================================================================
# Build Status Report # Build Status Report
# This job runs last and provides a summary of the build status.
# ============================================================================ # ============================================================================
build-summary: build-summary:
name: Build Summary name: Build Summary
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always() # This ensures the job runs even if previous jobs fail
needs: needs:
- quality-check-linux - quality-check
- quality-check-windows - compile-sketches
- compile-test-linux
- compile-test-windows
steps: steps:
- name: Create Build Summary - name: Create Build Summary
run: | run: |
echo "# 🔧 DShotRMT Build Report" >> $GITHUB_STEP_SUMMARY echo "# 🔧 DShotRMT Build Report" >> $GITHUB_STEP_SUMMARY
echo "| Check | Status | Details |" >> $GITHUB_STEP_SUMMARY echo "| Check | Status | Details |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|---------|" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|---------|" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.quality-check.result }}" == "success" ]]; then
[[ "${{ 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 | ✅ Passed | Arduino Lint completed successfully |" >> $GITHUB_STEP_SUMMARY \ else
|| echo "| 📋 Quality Check | ❌ Failed | Check Arduino Lint report |" >> $GITHUB_STEP_SUMMARY echo "| 📋 Quality Check | ❌ Failed | Check Arduino Lint report for errors |" >> $GITHUB_STEP_SUMMARY
fi
[[ "${{ needs.compile-test-linux.result }}" == "success" && "${{ needs.compile-test-windows.result }}" == "success" ]] \ if [[ "${{ needs.compile-sketches.result }}" == "success" ]]; then
&& echo "| 🔨 Compilation | ✅ Passed | All examples compiled successfully |" >> $GITHUB_STEP_SUMMARY \ echo "| 🔨 Compilation | ✅ Passed | All examples compiled successfully |" >> $GITHUB_STEP_SUMMARY
|| echo "| 🔨 Compilation | ❌ Failed | Compilation errors detected |" >> $GITHUB_STEP_SUMMARY else
echo "| 🔨 Compilation | ❌ Failed | One or more examples failed to compile |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY fi
[[ "${{ needs.quality-check-linux.result }}" == "success" && "${{ needs.quality-check-windows.result }}" == "success" && "${{ needs.compile-test-linux.result }}" == "success" && "${{ needs.compile-test-windows.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

View File

@ -2,19 +2,35 @@
[![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml/badge.svg)](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml) [![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 RMT (Remote Control) peripheral. It's designed for both Arduino and ESP-IDF projects, providing a simple and efficient way to control brushless motors. 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`). This library specifically leverages the official `rmt_bytes_encoder` API for an efficient, hardware-timed, and maintainable implementation. It 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.
This library is a rewrite using the modern ESP-IDF 5 RMT encoder API (`rmt_tx.h` / `rmt_rx.h`) for improved performance and flexibility. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch. ### DShot300 Example Output
Here's an example of the output from the `dshot300` example sketch:
![DShot300 Example Output](img/dshot300.png)
## 🚀 Core Features ## 🚀 Core Features
- **Multiple DShot Modes:** Supports DSHOT150, DSHOT300, DSHOT600, and DSHOT1200. - **Multiple DShot Modes:** Supports DSHOT150, DSHOT300, DSHOT600, and DSHOT1200.
- **Bidirectional DShot:** Implemented, but currently not officially supported due to instability and external hardware requirements. - **Bidirectional DShot Support:** Implemented, but note that official support is limited due to potential instability and external hardware requirements. Use with caution.
- **Hardware-Timed Signals:** Precise signal generation using the ESP32 RMT peripheral, ensuring stable and reliable motor control. - **Hardware-Timed Signals:** Precise signal generation using the ESP32 RMT peripheral, ensuring stable and reliable motor control.
- **Simple API:** Easy-to-use C++ class with intuitive methods like `sendThrottlePercent()`. - **Simple API:** Easy-to-use C++ class with intuitive methods like `sendThrottlePercent()`.
- **Robust Error Handling:** Provides detailed feedback on operation success or failure via `dshot_result_t`.
- **Efficient and Lightweight:** The core library has no external dependencies. - **Efficient and Lightweight:** The core library has no external dependencies.
- **Arduino and ESP-IDF Compatible:** Can be used in both Arduino and ESP-IDF projects. - **Arduino and ESP-IDF Compatible:** Can be used in both Arduino and ESP-IDF projects.
## ⏱️ DShot Timing Information
The DShot protocol defines specific timing characteristics for each mode. The following table outlines the bit length, T1H (high time for a '1' bit), T0H (high time for a '0' bit), and frame length for the supported DShot modes:
| DShot Mode | Bit Length (µs) | T1H Length (µs) | T0H Length (µs) | Frame Length (µs) |
| :--------- | :-------------- | :-------------- | :-------------- | :---------------- |
| DSHOT150 | 6.67 | 5.00 | 2.50 | 106.72 |
| DSHOT300 | 3.33 | 2.50 | 1.25 | 53.28 |
| DSHOT600 | 1.67 | 1.25 | 0.625 | 26.72 |
| DSHOT1200 | 0.83 | 0.67 | 0.335 | 13.28 |
## 📦 Installation ## 📦 Installation
### Arduino IDE ### Arduino IDE
@ -38,7 +54,7 @@ Here's a basic example of how to use the `DShotRMT` library to control a motor:
```cpp ```cpp
#include <Arduino.h> #include <Arduino.h>
#include <DShotRMT.h> #include <DShotRMT.h> // Include the DShotRMT library
// Define the GPIO pin connected to the motor ESC // Define the GPIO pin connected to the motor ESC
const gpio_num_t MOTOR_PIN = GPIO_NUM_27; const gpio_num_t MOTOR_PIN = GPIO_NUM_27;
@ -52,6 +68,9 @@ void setup() {
// Initialize the DShot motor // Initialize the DShot motor
motor.begin(); motor.begin();
// Print CPU Info
DShotRMT::printCpuInfo(Serial);
Serial.println("Motor initialized. Ramping up to 25% throttle..."); Serial.println("Motor initialized. Ramping up to 25% throttle...");
// Ramp up to 25% throttle over 2.5 seconds // Ramp up to 25% throttle over 2.5 seconds
@ -62,6 +81,9 @@ void setup() {
Serial.println("Stopping motor."); Serial.println("Stopping motor.");
motor.sendThrottlePercent(0); motor.sendThrottlePercent(0);
// Print DShot Info
DShotRMT::printDShotInfo(motor, Serial);
} }
void loop() { void loop() {
@ -100,14 +122,24 @@ lib_deps =
The main class is `DShotRMT`. Here are the most important methods: The main class is `DShotRMT`. Here are the most important methods:
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional = false)`: Constructor to create a new DShotRMT instance. (Note: Bidirectional DShot is currently not officially supported.) - `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT)`: Constructor to create a new DShotRMT instance.
- `begin()`: Initializes the RMT peripheral and the DShot encoder. - `begin()`: Initializes the DShot RMT channels and encoder.
- `sendThrottlePercent(float percent)`: Sends a throttle value as a percentage (0.0-100.0). - `sendThrottlePercent(float percent)`: Sends a throttle value as a percentage (0.0-100.0) to the ESC.
- `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the motor. - `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the ESC. A value of 0 sends a motor stop command.
- `sendCommand(uint16_t command)`: Sends a DShot command (0-47) to the motor. - `sendCommand(uint16_t command)`: Sends a single DShot command (0-47) to the ESC.
- `getTelemetry(uint16_t magnet_count)`: Receives and parses telemetry data from the motor (for bidirectional DShot, which is currently not officially supported). - `sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US)`: Sends a DShot command multiple times with a delay between repetitions. This is a blocking function.
- `getTelemetry(uint16_t magnet_count = 0)`: Retrieves telemetry data from the ESC. If `magnet_count` is 0, uses the stored motor magnet count.
For more details, please refer to the `DShotRMT.h` header file. - `getESCInfo()`: Sends a command to the ESC to request ESC information.
- `setMotorSpinDirection(bool reversed)`: Sets the motor spin direction. `true` for reversed, `false` for normal.
- `saveESCSettings()`: Sends a command to the ESC to save its current settings. Use with caution as this writes to ESC's non-volatile memory.
- `printDShotResult(dshot_result_t &result, Stream &output = Serial)`: Prints the result of a DShot operation to the specified output stream.
- `DShotRMT::printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial)`: Prints detailed DShot signal information for a given DShotRMT instance.
- `DShotRMT::printCpuInfo(Stream &output = Serial)`: Prints detailed CPU information.
- `setMotorMagnetCount(uint16_t magnet_count)`: Sets the motor magnet count for RPM calculation.
- `getMode()`: Gets the current DShot mode.
- `isBidirectional()`: Checks if bidirectional DShot is enabled.
- `getEncodedFrameValue()`: Gets the last encoded DShot frame value.
- `getThrottleValue()`: Gets the last transmitted throttle value.
## 🤝 Contributing ## 🤝 Contributing

View File

@ -7,7 +7,7 @@
*/ */
#include <Arduino.h> #include <Arduino.h>
#include <DShotRMT.h> #include "DShotRMT.h"
// USB serial port settings // USB serial port settings
static constexpr auto &USB_SERIAL = Serial0; static constexpr auto &USB_SERIAL = Serial0;
@ -18,7 +18,7 @@ static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// static constexpr auto MOTOR01_PIN = 17; // static constexpr auto MOTOR01_PIN = 17;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
// Note: Bidirectional DShot is currently not officially supported // Note: Bidirectional DShot is currently not officially supported
@ -41,7 +41,7 @@ void setup()
motor01.begin(); motor01.begin();
// Print CPU Info // Print CPU Info
motor01.printCpuInfo(); DShotRMT::printCpuInfo(USB_SERIAL);
// //
printMenu(); printMenu();
@ -80,7 +80,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode // Print motor stats every 3 seconds in continuous mode
if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000)) if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000))
{ {
motor01.printDShotInfo(); DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
@ -132,7 +132,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
} }
else if (input == "info") else if (input == "info")
{ {
motor01.printDShotInfo(); DShotRMT::printDShotInfo(motor01, USB_SERIAL);
} }
else if (input == "rpm" && IS_BIDIRECTIONAL) else if (input == "rpm" && IS_BIDIRECTIONAL)
{ {

View File

@ -17,7 +17,7 @@ static constexpr auto USB_SERIAL_BAUD = 115200;
static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27; static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
// Note: Bidirectional DShot is currently not officially supported // Note: Bidirectional DShot is currently not officially supported
@ -98,7 +98,7 @@ void handleSerialInput(const String &input)
} }
else if (input == "info") else if (input == "info")
{ {
motor01.printDShotInfo(); DShotRMT::printDShotInfo(motor01, USB_SERIAL);
} }
else if (input == "rpm" && IS_BIDIRECTIONAL) else if (input == "rpm" && IS_BIDIRECTIONAL)
{ {

View File

@ -42,7 +42,7 @@ static constexpr auto USB_SERIAL_BAUD = 115200;
static constexpr auto MOTOR01_PIN = 17; static constexpr auto MOTOR01_PIN = 17;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements. static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements.
@ -166,16 +166,13 @@ void loop()
} }
else if (!isArmed && continuous_throttle) else if (!isArmed && continuous_throttle)
{ {
// Ensure motor is stopped when disarmed
motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
} }
// Print motor stats every 3 seconds in continuous mode // Print motor stats every 3 seconds in continuous mode
if ((esp_timer_get_time() - last_serial_update >= 3000000)) if ((esp_timer_get_time() - last_serial_update >= 3000000))
{ {
motor01.printDShotInfo(); DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
// Get Motor RPM if bidirectional and armed // Get Motor RPM if bidirectional and armed
if (IS_BIDIRECTIONAL && isArmed) if (IS_BIDIRECTIONAL && isArmed)
@ -270,8 +267,7 @@ void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t ind
if (!index) if (!index)
{ {
// Safety: Ensure motor is stopped during update // Safety: Ensure motor is stopped during update
motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); setArmingStatus(false);
setArmingStatus(false);
USB_SERIAL.printf("OTA Update Start: %s\n", filename.c_str()); USB_SERIAL.printf("OTA Update Start: %s\n", filename.c_str());
@ -451,7 +447,7 @@ void handleSerialInput(const String &input)
if (input == "info") if (input == "info")
{ {
motor01.printDShotInfo(); DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED"); USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return; return;

View File

@ -41,7 +41,7 @@ static constexpr auto USB_SERIAL_BAUD = 115200;
static constexpr auto MOTOR01_PIN = 17; static constexpr auto MOTOR01_PIN = 17;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements. static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements.
@ -138,7 +138,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode // Print motor stats every 3 seconds in continuous mode
if ((esp_timer_get_time() - last_serial_update >= 3000000)) if ((esp_timer_get_time() - last_serial_update >= 3000000))
{ {
motor01.printDShotInfo(); DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
@ -254,7 +254,7 @@ void handleSerialInput(const String &input)
} }
if (input == "info") if (input == "info")
{ {
motor01.printDShotInfo(); DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED"); USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return; return;

View File

@ -7,59 +7,7 @@
####################################### #######################################
DShotRMT KEYWORD1 DShotRMT KEYWORD1
DShotCommandManager KEYWORD1
dshot_mode_t KEYWORD1
dshot_packet_t KEYWORD1
dshot_timing_t KEYWORD1
dshot_commands_t KEYWORD1
dshot_command_result_t KEYWORD1
dshot_command_sequence_item_t KEYWORD1
dshotCommands_e KEYWORD1
dshotCommandType_e KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
# DShotRMT Methods
begin KEYWORD2
setThrottle KEYWORD2
sendThrottle KEYWORD2
sendDShotCommand KEYWORD2
sendCommand KEYWORD2
getERPM KEYWORD2
getMotorRPM KEYWORD2
getGPIO KEYWORD2
getDShotPacket KEYWORD2
is_bidirectional KEYWORD2
printDShotInfo KEYWORD2
printCpuInfo KEYWORD2
# DShotCommandManager Methods
sendCommand KEYWORD2
sendCommandWithDelay KEYWORD2
stopMotor KEYWORD2
set3DMode KEYWORD2
setSpinDirection KEYWORD2
saveSettings KEYWORD2
setExtendedTelemetry KEYWORD2
requestESCInfo KEYWORD2
setLED KEYWORD2
activateBeacon KEYWORD2
setAudioStreamMode KEYWORD2
setSilentMode KEYWORD2
executeSequence KEYWORD2
executeInitSequence KEYWORD2
executeCalibrationSequence KEYWORD2
getCommandName KEYWORD2
isValidCommand KEYWORD2
printStatistics KEYWORD2
resetStatistics KEYWORD2
getTotalCommandCount KEYWORD2
getFailedCommandCount KEYWORD2
getLastExecutionTime KEYWORD2
printMenu KEYWORD2
handleMenuInput KEYWORD2
####################################### #######################################
# Constants (LITERAL1) # Constants (LITERAL1)

View File

@ -1,5 +1,5 @@
name=DShotRMT name=DShotRMT
version=0.8.3 version=0.8.6
author=Wastl Kraus <wir-sind-die-matrix.de> author=Wastl Kraus <wir-sind-die-matrix.de>
maintainer=Wastl Kraus <wir-sind-die-matrix.de> maintainer=Wastl Kraus <wir-sind-die-matrix.de>
license=MIT license=MIT
@ -8,4 +8,4 @@ paragraph=This library can control a BlHeli_S by using encoded DShot commands.
category=Signal Input/Output category=Signal Input/Output
url=https://github.com/derdoktor667/DShotRMT url=https://github.com/derdoktor667/DShotRMT
architectures=esp32 architectures=esp32
provides_includes=DShotRMT.h, dshot_commands.h, web_content.h, ota_update.h provides_includes=DShotRMT.h, dshot_definitions.h, web_content.h, ota_update.h

View File

@ -8,29 +8,6 @@
#include <DShotRMT.h> #include <DShotRMT.h>
// Timing parameters for each DShot mode
// Format: {bit_length_us, t1h_length_us}
static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = {
{0.00, 0.00}, // DSHOT_OFF
{6.67, 5.00}, // DSHOT150
{3.33, 2.50}, // DSHOT300
{1.67, 1.25}, // DSHOT600
{0.83, 0.67}}; // DSHOT1200
// Helper function to print DShot results and telemetry
void printDShotResult(dshot_result_t &result, Stream &output)
{
output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", result.msg);
// 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();
}
// Constructors & Destructor // Constructors & Destructor
// Constructor with GPIO number // Constructor with GPIO number
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count) DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count)
@ -38,17 +15,16 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, ui
_mode(mode), _mode(mode),
_is_bidirectional(is_bidirectional), _is_bidirectional(is_bidirectional),
_motor_magnet_count(magnet_count), _motor_magnet_count(magnet_count),
_dshot_timing(DSHOT_TIMING_US[mode]), _dshot_timing(DSHOT_TIMING_US[static_cast<int>(mode)]),
_frame_timer_us(0), _frame_timer_us(0),
_rmt_ticks{0}, _rmt_ticks{0},
_last_throttle(DSHOT_CMD_MOTOR_STOP), _last_throttle(static_cast<uint16_t>(dshotCommands_e::DSHOT_CMD_MOTOR_STOP)),
_last_transmission_time_us(0), _last_transmission_time_us(0),
_last_command_timestamp(0), _last_command_timestamp(0),
_parsed_packet(0), _encoded_frame_value(0),
_packet{0}, _packet{0},
_bitPositions{0}, _pulse_level(1), // DShot standard: signal is idle-low, so pulses start by going HIGH
_level0(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
_level1(0), // DShot standard: signal returns to LOW after the high pulse
_rmt_tx_channel(nullptr), _rmt_tx_channel(nullptr),
_rmt_rx_channel(nullptr), _rmt_rx_channel(nullptr),
_dshot_encoder(nullptr), _dshot_encoder(nullptr),
@ -62,7 +38,6 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, ui
{ {
// Pre-calculate timing and bit positions for performance // Pre-calculate timing and bit positions for performance
_preCalculateRMTTicks(); _preCalculateRMTTicks();
_preCalculateBitPositions();
// Activate internal pullup resistor // Activate internal pullup resistor
// gpio_set_pull_mode(_gpio, GPIO_PULLUP_ONLY); // gpio_set_pull_mode(_gpio, GPIO_PULLUP_ONLY);
@ -112,7 +87,7 @@ dshot_result_t DShotRMT::begin()
{ {
if (!_initTXChannel().success) if (!_initTXChannel().success)
{ {
return {false, TX_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED};
} }
if (_is_bidirectional) if (_is_bidirectional)
@ -123,7 +98,7 @@ dshot_result_t DShotRMT::begin()
rmt_disable(_rmt_tx_channel); rmt_disable(_rmt_tx_channel);
rmt_del_channel(_rmt_tx_channel); rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr; _rmt_tx_channel = nullptr;
return {false, RX_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED};
} }
} }
@ -134,16 +109,17 @@ dshot_result_t DShotRMT::begin()
rmt_del_channel(_rmt_tx_channel); rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr; _rmt_tx_channel = nullptr;
if (_rmt_rx_channel) { if (_rmt_rx_channel)
{
rmt_disable(_rmt_rx_channel); rmt_disable(_rmt_rx_channel);
rmt_del_channel(_rmt_rx_channel); rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr; _rmt_rx_channel = nullptr;
} }
return {false, ENCODER_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED};
} }
return {true, INIT_SUCCESS}; return {true, dshot_msg_code_t::DSHOT_ERROR_INIT_SUCCESS};
} }
// Send throttle value // Send throttle value
@ -152,7 +128,7 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
// A throttle value of 0 is a disarm command // A throttle value of 0 is a disarm command
if (throttle == 0) if (throttle == 0)
{ {
return sendCommand(DSHOT_CMD_MOTOR_STOP); return sendCommand(static_cast<uint16_t>(dshotCommands_e::DSHOT_CMD_MOTOR_STOP));
} }
// Constrain throttle to the valid DShot range // Constrain throttle to the valid DShot range
@ -167,7 +143,7 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
{ {
if (percent < 0.0f || percent > 100.0f) if (percent < 0.0f || percent > 100.0f)
{ {
return {false, PERCENT_NOT_IN_RANGE}; return {false, dshot_msg_code_t::DSHOT_ERROR_PERCENT_NOT_IN_RANGE};
} }
// Map percent to DShot throttle range // Map percent to DShot throttle range
@ -179,9 +155,9 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
// Send DShot command to ESC // Send DShot command to ESC
dshot_result_t DShotRMT::sendCommand(uint16_t command) dshot_result_t DShotRMT::sendCommand(uint16_t command)
{ {
if (command > DSHOT_CMD_MAX) if (command > static_cast<uint16_t>(dshotCommands_e::DSHOT_CMD_MAX))
{ {
return {false, COMMAND_NOT_VALID}; return {false, dshot_msg_code_t::DSHOT_ERROR_COMMAND_NOT_VALID};
} }
_packet = _buildDShotPacket(command); _packet = _buildDShotPacket(command);
@ -189,13 +165,14 @@ dshot_result_t DShotRMT::sendCommand(uint16_t command)
} }
// Send full DShot commands for setup etc // Send full DShot commands for setup etc
dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t repeat_count, uint16_t delay_us) // This is a blocking function that uses delayMicroseconds for repetitions.
dshot_result_t DShotRMT::sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count, uint16_t delay_us)
{ {
dshot_result_t result = {false, UNKNOWN_ERROR}; dshot_result_t result = {false, dshot_msg_code_t::DSHOT_ERROR_UNKNOWN};
if (!_isValidCommand(dshot_command)) if (!_isValidCommand(dshot_command))
{ {
result.msg = INVALID_COMMAND; result.error_code = dshot_msg_code_t::DSHOT_ERROR_INVALID_COMMAND;
return result; return result;
} }
@ -209,7 +186,7 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re
if (!single_result.success) if (!single_result.success)
{ {
all_successful = false; all_successful = false;
result.msg = single_result.msg; result.error_code = single_result.error_code;
break; break;
} }
@ -225,7 +202,7 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re
if (result.success) if (result.success)
{ {
result.msg = COMMAND_SUCCESS; result.error_code = dshot_msg_code_t::DSHOT_ERROR_COMMAND_SUCCESS;
} }
return result; return result;
@ -234,11 +211,11 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re
// Get telemetry data // Get telemetry data
dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
{ {
dshot_result_t result = {false, TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; dshot_result_t result = {false, dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY};
if (!_is_bidirectional) if (!_is_bidirectional)
{ {
result.msg = BIDIR_NOT_ENABLED; result.error_code = dshot_msg_code_t::DSHOT_ERROR_BIDIR_NOT_ENABLED;
return result; return result;
} }
@ -260,7 +237,7 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
result.success = true; result.success = true;
result.erpm = erpm; result.erpm = erpm;
result.motor_rpm = motor_rpm; result.motor_rpm = motor_rpm;
result.msg = TELEMETRY_SUCCESS; result.error_code = dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_SUCCESS;
} }
} }
@ -271,66 +248,30 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
dshot_result_t DShotRMT::setMotorSpinDirection(bool reversed) dshot_result_t DShotRMT::setMotorSpinDirection(bool reversed)
{ {
// Use command as a yes / no switch // Use command as a yes / no switch
dshot_commands_t command = reversed ? DSHOT_CMD_SPIN_DIRECTION_REVERSED : DSHOT_CMD_SPIN_DIRECTION_NORMAL; dshotCommands_e command = reversed ? dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_REVERSED : dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_NORMAL;
return sendCommand(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); return sendCommand(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
} }
dshot_result_t DShotRMT::getESCInfo() dshot_result_t DShotRMT::getESCInfo()
{ {
return sendCommand(DSHOT_CMD_ESC_INFO); return sendCommand(static_cast<uint16_t>(dshotCommands_e::DSHOT_CMD_ESC_INFO));
} }
// Use with caution // Use with caution
dshot_result_t DShotRMT::saveESCSettings() dshot_result_t DShotRMT::saveESCSettings()
{ {
return sendCommand(DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); return sendCommand(dshotCommands_e::DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
}
// Public Info & Debug Functions
void DShotRMT::setMotorMagnetCount(uint16_t magnet_count)
{
_motor_magnet_count = magnet_count;
}
void DShotRMT::printDShotInfo(Stream &output) const
{
output.println("\n === DShot Signal Info === ");
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");
output.printf("Current Packet: ");
for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
{
output.print((_parsed_packet >> i) & 1);
}
output.printf("\nCurrent Value: %u\n", _packet.throttle_value);
}
//
void DShotRMT::printCpuInfo(Stream &output) const
{
output.println("\n === 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());
} }
// Simple check // Simple check
bool DShotRMT::_isValidCommand(dshot_commands_t command) bool DShotRMT::_isValidCommand(dshotCommands_e command)
{ {
return (command >= DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX); return (static_cast<uint16_t>(command) >= static_cast<uint16_t>(dshotCommands_e::DSHOT_CMD_MOTOR_STOP) && static_cast<uint16_t>(command) <= static_cast<uint16_t>(dshotCommands_e::DSHOT_CMD_MAX));
} }
// //
dshot_result_t DShotRMT::_executeCommand(dshot_commands_t command) dshot_result_t DShotRMT::_executeCommand(dshotCommands_e command)
{ {
uint64_t start_time = esp_timer_get_time(); uint64_t start_time = esp_timer_get_time();
@ -357,14 +298,14 @@ dshot_result_t DShotRMT::_initTXChannel()
if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK) if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
{ {
return {false, TX_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED};
} }
if (rmt_enable(_rmt_tx_channel) != DSHOT_OK) if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
{ {
return {false, TX_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED};
} }
return {true, TX_INIT_SUCCESS}; return {true, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_SUCCESS};
} }
dshot_result_t DShotRMT::_initRXChannel() dshot_result_t DShotRMT::_initRXChannel()
@ -372,7 +313,7 @@ dshot_result_t DShotRMT::_initRXChannel()
// Double check if bidirectional mode is enabled // Double check if bidirectional mode is enabled
if (!_is_bidirectional) if (!_is_bidirectional)
{ {
return {true, NONE}; return {true, dshot_msg_code_t::DSHOT_ERROR_NONE};
} }
_rx_channel_config.gpio_num = _gpio; _rx_channel_config.gpio_num = _gpio;
@ -386,19 +327,19 @@ dshot_result_t DShotRMT::_initRXChannel()
if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK) if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
{ {
return {false, RX_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED};
} }
// Register the callback function that will be triggered when a frame is received // Register the callback function that will be triggered when a frame is received
_rx_event_callbacks.on_recv_done = _on_rx_done; _rx_event_callbacks.on_recv_done = _on_rx_done;
if (rmt_rx_register_event_callbacks(_rmt_rx_channel, &_rx_event_callbacks, this) != DSHOT_OK) if (rmt_rx_register_event_callbacks(_rmt_rx_channel, &_rx_event_callbacks, this) != DSHOT_OK)
{ {
return {false, CALLBACK_REGISTERING_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_CALLBACK_REGISTERING_FAILED};
} }
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{ {
return {false, RX_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED};
} }
// Start the receiver to wait for incoming telemetry data // Start the receiver to wait for incoming telemetry data
@ -406,22 +347,38 @@ dshot_result_t DShotRMT::_initRXChannel()
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t); size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
if (rmt_receive(_rmt_rx_channel, rx_symbols, rx_size_bytes, &_rmt_rx_config) != DSHOT_OK) if (rmt_receive(_rmt_rx_channel, rx_symbols, rx_size_bytes, &_rmt_rx_config) != DSHOT_OK)
{ {
return {false, RECEIVER_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_RECEIVER_FAILED};
} }
return {true, RX_INIT_SUCCESS}; return {true, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_SUCCESS};
} }
dshot_result_t DShotRMT::_initDShotEncoder() 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, ENCODER_INIT_FAILED}; return {false, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED};
} }
return {true, ENCODER_INIT_SUCCESS}; return {true, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_SUCCESS};
} }
// Private Packet Management Functions // Private Packet Management Functions
@ -439,7 +396,7 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value)
return packet; return packet;
} }
uint16_t DShotRMT::_parseDShotPacket(const dshot_packet_t &packet) uint16_t DShotRMT::_buildDShotFrameValue(const dshot_packet_t &packet)
{ {
// Combine throttle, telemetry bit, and CRC into a single 16-bit frame // Combine throttle, telemetry bit, and CRC into a single 16-bit frame
uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request; uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request;
@ -479,63 +436,34 @@ void DShotRMT::_preCalculateRMTTicks()
} }
} }
void DShotRMT::_preCalculateBitPositions()
{
// Pre-calculate bit positions to avoid redundant calculations in the encoding loop
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
_bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i;
}
}
// Private Frame Processing Functions // Private Frame Processing Functions
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
{ {
// Ensure enough time has passed since the last transmission // Ensure enough time has passed since the last transmission
if (!_timer_signal()) if (!_isFrameIntervalElapsed())
{ {
return {true, NONE}; return {true, dshot_msg_code_t::DSHOT_ERROR_NONE};
} }
rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME]; _encoded_frame_value = _buildDShotFrameValue(packet);
dshot_result_t result = _encodeDShotFrame(packet, tx_symbols);
if (!result.success) // Byte-swap the 16-bit value for correct transmission order (ESP32 is little-endian, DShot is MSB first)
uint16_t swapped_value = __builtin_bswap16(_encoded_frame_value);
// The DShot frame is 16 bits, which is 2 bytes
size_t tx_size_bytes = sizeof(swapped_value);
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &swapped_value, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK)
{ {
return result; return {false, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_FAILED};
} }
size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t); _recordFrameTransmissionTime(); // Reset the timer for the next frame
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK) return {true, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_SUCCESS};
{
return {false, TRANSMISSION_FAILED};
}
_timer_reset(); // Reset the timer for the next frame
return {true, TRANSMISSION_SUCCESS};
} }
// This function needs to be fast, as it generates the RMT symbols just before sending // 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)
{
_parsed_packet = _parseDShotPacket(packet);
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
int bit_position = _bitPositions[i];
bool bit = (_parsed_packet >> 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, ENCODING_SUCCESS};
}
// Placed in IRAM for high performance, as it's called from an ISR context // 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) uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
@ -557,11 +485,11 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET); uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET);
// Step 4: Extract data and CRC from the 16-bit frame // Step 4: Extract data and CRC from the 16-bit frame
uint16_t received_data = data_and_crc >> 4; uint16_t received_data = data_and_crc >> DSHOT_CRC_BIT_SHIFT;
uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK; uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK;
// Step 5: A valid response must have the telemetry request bit set to 1. This is a sanity check. // Step 5: A valid response must have the telemetry request bit set to 1. This is a sanity check.
if (!((received_data >> 11) & 1)) if (!((received_data >> DSHOT_TELEMETRY_BIT_POSITION) & 1))
{ {
return DSHOT_NULL_PACKET; return DSHOT_NULL_PACKET;
} }
@ -578,7 +506,7 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
} }
// Timing Control Functions // Timing Control Functions
bool IRAM_ATTR DShotRMT::_timer_signal() bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed()
{ {
// Check if the minimum interval between frames has passed // Check if the minimum interval between frames has passed
uint64_t current_time = esp_timer_get_time(); uint64_t current_time = esp_timer_get_time();
@ -586,11 +514,10 @@ bool IRAM_ATTR DShotRMT::_timer_signal()
return elapsed >= _frame_timer_us; return elapsed >= _frame_timer_us;
} }
bool DShotRMT::_timer_reset() void DShotRMT::_recordFrameTransmissionTime()
{ {
// Record the time of the current transmission // Record the time of the current transmission
_last_transmission_time_us = esp_timer_get_time(); _last_transmission_time_us = esp_timer_get_time();
return true;
} }
// Static Callback Functions // Static Callback Functions
@ -613,3 +540,32 @@ bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const
return false; return false;
} }
// 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);
output.printf("Bidirectional: %s\n", dshot_rmt.isBidirectional() ? "YES" : "NO");
output.printf("Current Packet: ");
for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
{
output.print((dshot_rmt.getEncodedFrameValue() >> i) & 1);
}
output.printf("\nCurrent Value: %u\n", dshot_rmt.getThrottleValue());
}
void DShotRMT::printCpuInfo(Stream &output)
{
output.println("\n === 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());
}

View File

@ -9,101 +9,81 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <dshot_commands.h> #include <dshot_definitions.h>
#include <driver/gpio.h> #include <driver/gpio.h>
#include <driver/rmt_tx.h> #include <driver/rmt_tx.h>
#include <driver/rmt_rx.h> #include <driver/rmt_rx.h>
#include <atomic> #include <atomic>
// DShot Protocol Constants & Types // Main class for DShot signal generation and reception.
static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0; // This class provides an interface to generate DShot signals for Electronic Speed Controllers (ESCs)
static constexpr auto DSHOT_THROTTLE_MIN = 48; // and to receive telemetry data using the ESP32's RMT peripheral.
static constexpr auto DSHOT_THROTTLE_MAX = 2047;
static constexpr auto DSHOT_BITS_PER_FRAME = 16;
static constexpr auto DEFAULT_MOTOR_MAGNET_COUNT = 14;
// Custom status codes
static constexpr auto DSHOT_OK = 0;
static constexpr auto DSHOT_ERROR = 1;
// DShot Modes
typedef enum dshot_mode
{
DSHOT_OFF,
DSHOT150,
DSHOT300,
DSHOT600,
DSHOT1200
} dshot_mode_t;
// DShot Packet Structure
typedef struct dshot_packet
{
uint16_t throttle_value : 11;
bool telemetric_request : 1;
uint16_t checksum : 4;
} dshot_packet_t;
// DShot Timing Configuration
typedef struct dshot_timing
{
double bit_length_us;
double t1h_lenght_us;
} dshot_timing_us_t;
// RMT Timing Configuration
typedef struct rmt_ticks
{
uint16_t bit_length_ticks;
uint16_t t1h_ticks;
uint16_t t1l_ticks;
uint16_t t0h_ticks;
uint16_t t0l_ticks;
} rmt_ticks_t;
// Unified DShot Result Structure
typedef struct dshot_result
{
bool success;
const char *msg;
uint16_t erpm;
uint16_t motor_rpm;
} dshot_result_t;
// Command Type Alias
typedef dshotCommands_e dshot_commands_t;
// Helper Functions
void printDShotResult(dshot_result_t &result, Stream &output = Serial);
//
// DShotRMT Main Class
class DShotRMT class DShotRMT
{ {
public: public:
// Constructors & Destructor // Constructor for DShotRMT with GPIO number.
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT); explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = dshot_mode_t::DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Constructor for DShotRMT with Arduino pin number.
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT); DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Destructor for DShotRMT.
// Cleans up RMT channels and encoder resources.
~DShotRMT(); ~DShotRMT();
// Public Core Functions // Public Core Functions
// Initializes the DShot RMT channels and encoder.
dshot_result_t begin(); dshot_result_t begin();
// Sends a DShot throttle value to the ESC.
dshot_result_t sendThrottle(uint16_t throttle); dshot_result_t sendThrottle(uint16_t throttle);
// Sends a DShot throttle value as a percentage to the ESC.
dshot_result_t sendThrottlePercent(float percent); dshot_result_t sendThrottlePercent(float percent);
// Sends a single DShot command to the ESC.
dshot_result_t sendCommand(uint16_t command); dshot_result_t sendCommand(uint16_t command);
dshot_result_t sendCommand(dshot_commands_t dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US);
// Sends a DShot command multiple times with a delay between repetitions. This is a blocking function.
dshot_result_t sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US);
// Retrieves telemetry data from the ESC.
dshot_result_t getTelemetry(uint16_t magnet_count = 0); dshot_result_t getTelemetry(uint16_t magnet_count = 0);
// Sends a command to the ESC to request ESC information.
dshot_result_t getESCInfo(); dshot_result_t getESCInfo();
// Sets the motor spin direction.
dshot_result_t setMotorSpinDirection(bool reversed); dshot_result_t setMotorSpinDirection(bool reversed);
// Sends a command to the ESC to save its current settings.
// Use with caution as this writes to ESC's non-volatile memory.
dshot_result_t saveESCSettings(); dshot_result_t saveESCSettings();
// Public Utility & Info Functions // Public Utility & Info Functions
// Prints detailed DShot signal information for a given DShotRMT instance.
static void printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial);
// Prints detailed CPU information.
static void printCpuInfo(Stream &output = Serial);
// Sets the motor magnet count for RPM calculation.
void setMotorMagnetCount(uint16_t magnet_count); void setMotorMagnetCount(uint16_t magnet_count);
void printDShotInfo(Stream &output = Serial) const;
void printCpuInfo(Stream &output = Serial) const; // Gets the current DShot mode.
dshot_mode_t getMode() const { return _mode; }
// Checks if bidirectional DShot is enabled.
bool isBidirectional() const { return _is_bidirectional; }
// Gets the last encoded DShot frame value.
uint16_t getEncodedFrameValue() const { return _encoded_frame_value; }
// Gets the last transmitted throttle value.
uint16_t getThrottleValue() const { return _packet.throttle_value; }
// Deprecated Methods // Deprecated Methods
// Deprecated. Use sendThrottle() instead.
[[deprecated("Use sendThrottle() instead")]] [[deprecated("Use sendThrottle() instead")]]
bool setThrottle(uint16_t throttle) bool setThrottle(uint16_t throttle)
{ {
@ -111,6 +91,7 @@ public:
return result.success; return result.success;
} }
// Deprecated. Use sendCommand() instead.
[[deprecated("Use sendCommand() instead")]] [[deprecated("Use sendCommand() instead")]]
bool sendDShotCommand(uint16_t command) bool sendDShotCommand(uint16_t command)
{ {
@ -118,6 +99,7 @@ public:
return result.success; return result.success;
} }
// Deprecated. Use getTelemetry() instead.
[[deprecated("Use getTelemetry() instead")]] [[deprecated("Use getTelemetry() instead")]]
uint32_t getMotorRPM(uint8_t magnet_count) uint32_t getMotorRPM(uint8_t magnet_count)
{ {
@ -126,55 +108,9 @@ public:
} }
private: private:
// 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
static constexpr auto const DSHOT_RX_TIMEOUT_MS = 2;
static constexpr auto const DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility
static constexpr auto const RMT_BUFFER_SYMBOLS = 128;
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 = 800; // 0.8us minimum pulse
static constexpr auto const DSHOT_PULSE_MAX = 8000; // 8.0us maximum pulse
static constexpr auto const DSHOT_TELEMETRY_INVALID = DSHOT_THROTTLE_MAX;
// Error Messages
static constexpr char const *NONE = "";
static constexpr char const *UNKNOWN_ERROR = "Unknown Error!";
static constexpr char const *INIT_SUCCESS = "SignalGeneratorRMT initialized successfully";
static constexpr char const *INIT_FAILED = "SignalGeneratorRMT init failed!";
static constexpr char const *TX_INIT_SUCCESS = "TX RMT channel initialized successfully";
static constexpr char const *TX_INIT_FAILED = "TX RMT channel init failed!";
static constexpr char const *RX_INIT_SUCCESS = "RX RMT channel initialized successfully";
static constexpr char const *RX_INIT_FAILED = "RX RMT channel init failed!";
static constexpr char const *ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully";
static constexpr char const *ENCODER_INIT_FAILED = "RMT encoder init failed!";
static constexpr char const *ENCODING_SUCCESS = "Packet encoded successfully";
static constexpr char const *TRANSMISSION_SUCCESS = "Transmission successfully";
static constexpr char const *TRANSMISSION_FAILED = "Transmission failed!";
static constexpr char const *RECEIVER_FAILED = "RMT receiver failed!";
static constexpr char const *THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)";
static constexpr char const *PERCENT_NOT_IN_RANGE = "Percent not in range! (0.0 - 100.0)";
static constexpr char const *COMMAND_NOT_VALID = "Command not valid! (0 - 47)";
static constexpr char const *BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!";
static constexpr char const *TELEMETRY_SUCCESS = "Valid Telemetric Frame received!";
static constexpr char const *TELEMETRY_FAILED = "No valid Telemetric Frame received!";
static constexpr char const *INVALID_MAGNET_COUNT = "Invalid motor magnet count!";
static constexpr char const *TIMING_CORRECTION = "Timing correction!";
static constexpr char const *CALLBACK_REGISTERING_FAILED = "RMT RX Callback registering failed!";
static constexpr char const *INVALID_COMMAND = "Invalid command!";
static constexpr char const *COMMAND_SUCCESS = "DShot command sent successfully";
// --- UTILITY METHODS --- // --- UTILITY METHODS ---
bool _isValidCommand(dshot_commands_t command); bool _isValidCommand(dshotCommands_e command);
dshot_result_t _executeCommand(dshot_commands_t command); dshot_result_t _executeCommand(dshotCommands_e command);
// Core Configuration Variables // Core Configuration Variables
gpio_num_t _gpio; gpio_num_t _gpio;
@ -189,11 +125,10 @@ private:
uint16_t _last_throttle; uint16_t _last_throttle;
uint64_t _last_transmission_time_us; uint64_t _last_transmission_time_us;
uint64_t _last_command_timestamp; uint64_t _last_command_timestamp;
uint16_t _parsed_packet; uint16_t _encoded_frame_value;
dshot_packet_t _packet; dshot_packet_t _packet;
uint8_t _bitPositions[DSHOT_BITS_PER_FRAME]; uint16_t _pulse_level; // DShot protocol: Signal is idle-low, so pulses start by going HIGH.
uint16_t _level0; // Signal level for the first part of a pulse (always HIGH for DShot) uint16_t _idle_level; // DShot protocol: Signal returns to LOW after the high pulse.
uint16_t _level1; // Signal level for the second part of a pulse (always LOW for DShot)
// RMT Hardware Handles // RMT Hardware Handles
rmt_channel_handle_t _rmt_tx_channel; rmt_channel_handle_t _rmt_tx_channel;
@ -218,26 +153,18 @@ private:
// Private Packet Management Functions // Private Packet Management Functions
dshot_packet_t _buildDShotPacket(const uint16_t &value); dshot_packet_t _buildDShotPacket(const uint16_t &value);
uint16_t _parseDShotPacket(const dshot_packet_t &packet); uint16_t _buildDShotFrameValue(const dshot_packet_t &packet);
uint16_t _calculateCRC(const uint16_t &data); uint16_t _calculateCRC(const uint16_t &data);
void _preCalculateRMTTicks(); void _preCalculateRMTTicks();
void _preCalculateBitPositions();
// Private Frame Processing Functions // Private Frame Processing Functions
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet); 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); uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols);
// Private Timing Control Functions // Private Timing Control Functions
bool _timer_signal(); bool _isFrameIntervalElapsed();
bool _timer_reset(); void _recordFrameTransmissionTime();
// Static Callback Functions // 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); static bool _on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
// Command Constants
static constexpr auto DEFAULT_CMD_DELAY_US = 10;
static constexpr auto DEFAULT_CMD_REPEAT_COUNT = 1;
static constexpr auto SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats
static constexpr auto SETTINGS_COMMAND_DELAY_US = 5;
}; };

View File

@ -1,70 +0,0 @@
/*
* This file is part of Cleanflight and Betaflight.
*
* Cleanflight and Betaflight are free software. You can redistribute
* this software and/or modify this software under the terms of the
* GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Cleanflight and Betaflight are distributed in the hope that they
* will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software.
*
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#define DSHOT_MAX_COMMAND 47
/*
DshotSettingRequest (KISS24). Spin direction, 3d and save Settings require 10 requests.. and the TLM Byte must always be high if 1-47 are used to send settings
3D Mode:
0 = stop
48 (low) - 1047 (high) -> negative direction
1048 (low) - 2047 (high) -> positive direction
*/
typedef enum
{
DSHOT_CMD_MOTOR_STOP = 0,
DSHOT_CMD_BEACON1,
DSHOT_CMD_BEACON2,
DSHOT_CMD_BEACON3,
DSHOT_CMD_BEACON4,
DSHOT_CMD_BEACON5,
DSHOT_CMD_ESC_INFO, // V2 includes settings
DSHOT_CMD_SPIN_DIRECTION_1,
DSHOT_CMD_SPIN_DIRECTION_2,
DSHOT_CMD_3D_MODE_OFF,
DSHOT_CMD_3D_MODE_ON,
DSHOT_CMD_SETTINGS_REQUEST, // Currently not implemented
DSHOT_CMD_SAVE_SETTINGS,
DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE,
DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE,
DSHOT_CMD_SPIN_DIRECTION_NORMAL = 20,
DSHOT_CMD_SPIN_DIRECTION_REVERSED = 21,
DSHOT_CMD_LED0_ON, // BLHeli32 only
DSHOT_CMD_LED1_ON, // BLHeli32 only
DSHOT_CMD_LED2_ON, // BLHeli32 only
DSHOT_CMD_LED3_ON, // BLHeli32 only
DSHOT_CMD_LED0_OFF, // BLHeli32 only
DSHOT_CMD_LED1_OFF, // BLHeli32 only
DSHOT_CMD_LED2_OFF, // BLHeli32 only
DSHOT_CMD_LED3_OFF, // BLHeli32 only
DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30, // KISS audio Stream mode on/Off
DSHOT_CMD_SILENT_MODE_ON_OFF = 31, // KISS silent Mode on/Off
DSHOT_CMD_MAX = 47
} dshotCommands_e;
typedef enum
{
DSHOT_CMD_TYPE_INLINE = 0, // dshot commands sent inline with motor signal (motors must be enabled)
DSHOT_CMD_TYPE_BLOCKING // dshot commands sent in blocking method (motors must be disabled)
} dshotCommandType_e;

288
src/dshot_definitions.h Normal file
View File

@ -0,0 +1,288 @@
#pragma once
#include <Arduino.h>
#include <driver/gpio.h> // Added for gpio_num_t
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
#include <atomic> // Added for std::atomic
// Defines the available DShot communication speeds.
enum class dshot_mode_t
{
DSHOT_OFF,
DSHOT150,
DSHOT300,
DSHOT600,
DSHOT1200
};
// Represents the 16-bit DShot data packet sent to the ESC.
typedef struct dshot_packet
{
uint16_t throttle_value : 11; // 11-bit throttle value or command.
bool telemetric_request : 1; // 1-bit telemetry request flag.
uint16_t checksum : 4; // 4-bit CRC checksum.
} dshot_packet_t;
// Defines the bit length and high time for a '1' bit in microseconds for each DShot mode.
typedef struct dshot_timing
{
double bit_length_us; // Total duration of one bit in microseconds.
double t1h_lenght_us; // High time duration for a '1' bit in microseconds.
} dshot_timing_us_t;
// Stores pre-calculated timing values in RMT ticks for efficient signal generation.
typedef struct rmt_ticks
{
uint16_t bit_length_ticks; // Total duration of one bit in RMT ticks.
uint16_t t1h_ticks; // High time duration for a '1' bit in RMT ticks.
uint16_t t1l_ticks; // Low time duration for a '1' bit in RMT ticks.
uint16_t t0h_ticks; // High time duration for a '0' bit in RMT ticks.
uint16_t t0l_ticks; // Low time duration for a '0' bit in RMT ticks.
} rmt_ticks_t;
// Enum class for specific error and success codes returned by DShotRMT functions.
enum class dshot_msg_code_t
{
DSHOT_ERROR_NONE = 0,
DSHOT_ERROR_UNKNOWN,
DSHOT_ERROR_TX_INIT_FAILED,
DSHOT_ERROR_RX_INIT_FAILED,
DSHOT_ERROR_ENCODER_INIT_FAILED,
DSHOT_ERROR_CALLBACK_REGISTERING_FAILED,
DSHOT_ERROR_RECEIVER_FAILED,
DSHOT_ERROR_TRANSMISSION_FAILED,
DSHOT_ERROR_THROTTLE_NOT_IN_RANGE,
DSHOT_ERROR_PERCENT_NOT_IN_RANGE,
DSHOT_ERROR_COMMAND_NOT_VALID,
DSHOT_ERROR_BIDIR_NOT_ENABLED,
DSHOT_ERROR_TELEMETRY_FAILED,
DSHOT_ERROR_INVALID_MAGNET_COUNT,
DSHOT_ERROR_INVALID_COMMAND,
DSHOT_ERROR_TIMING_CORRECTION,
DSHOT_ERROR_INIT_FAILED,
DSHOT_ERROR_INIT_SUCCESS,
DSHOT_ERROR_TX_INIT_SUCCESS,
DSHOT_ERROR_RX_INIT_SUCCESS,
DSHOT_ERROR_ENCODER_INIT_SUCCESS,
DSHOT_ERROR_ENCODING_SUCCESS,
DSHOT_ERROR_TRANSMISSION_SUCCESS,
DSHOT_ERROR_TELEMETRY_SUCCESS,
DSHOT_ERROR_COMMAND_SUCCESS
};
// Contains the success status, an error code, and optional telemetry data.
typedef struct dshot_result
{
bool success;
dshot_msg_code_t error_code; // Specific error or success code.
uint16_t erpm; // Electrical RPM (eRPM) if telemetry is successful.
uint16_t motor_rpm; // Motor RPM if telemetry is successful and magnet count is known.
} dshot_result_t;
// Enum class for standard DShot commands that can be sent to an ESC.
enum dshotCommands_e
{
DSHOT_CMD_MOTOR_STOP = 0,
DSHOT_CMD_BEACON1,
DSHOT_CMD_BEACON2,
DSHOT_CMD_BEACON3,
DSHOT_CMD_BEACON4,
DSHOT_CMD_BEACON5,
DSHOT_CMD_ESC_INFO, // V2 includes settings
DSHOT_CMD_SPIN_DIRECTION_1,
DSHOT_CMD_SPIN_DIRECTION_2,
DSHOT_CMD_3D_MODE_OFF,
DSHOT_CMD_3D_MODE_ON,
DSHOT_CMD_SETTINGS_REQUEST, // Currently not implemented
DSHOT_CMD_SAVE_SETTINGS,
DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE,
DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE,
DSHOT_CMD_SPIN_DIRECTION_NORMAL = 20,
DSHOT_CMD_SPIN_DIRECTION_REVERSED = 21,
DSHOT_CMD_LED0_ON, // BLHeli32 only
DSHOT_CMD_LED1_ON, // BLHeli32 only
DSHOT_CMD_LED2_ON, // BLHeli32 only
DSHOT_CMD_LED3_ON, // BLHeli32 only
DSHOT_CMD_LED0_OFF, // BLHeli32 only
DSHOT_CMD_LED1_OFF, // BLHeli32 only
DSHOT_CMD_LED2_OFF, // BLHeli32 only
DSHOT_CMD_LED3_OFF, // BLHeli32 only
DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30, // KISS audio Stream mode on/Off
DSHOT_CMD_SILENT_MODE_ON_OFF = 31, // KISS silent Mode on/Off
DSHOT_CMD_MAX = 47
};
// Defines how DShot commands are sent.
enum class dshotCommandType_e
{
DSHOT_CMD_TYPE_INLINE = 0, // Commands sent inline with motor signal (motors must be enabled).
DSHOT_CMD_TYPE_BLOCKING // Commands sent in blocking method (motors must be disabled).
};
// DShot Protocol Constants
const uint16_t DSHOT_THROTTLE_FAILSAFE = 0;
const uint16_t DSHOT_THROTTLE_MIN = 48;
const uint16_t DSHOT_THROTTLE_MAX = 2047;
const uint16_t DSHOT_BITS_PER_FRAME = 16;
const uint16_t DEFAULT_MOTOR_MAGNET_COUNT = 14;
// Custom status codes
const int DSHOT_OK = 0;
const int DSHOT_ERROR = 1;
// Configuration Constants (from DShotRMT.h private section)
const uint16_t DSHOT_NULL_PACKET = 0b0000000000000000;
const uint16_t DSHOT_FULL_PACKET = 0b1111111111111111;
const uint16_t DSHOT_CRC_MASK = 0b0000000000001111;
const rmt_clock_source_t DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
const uint32_t DSHOT_RMT_RESOLUTION = 8 * 1000 * 1000; // 8 MHz resolution
const uint16_t RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / (1 * 1000 * 1000); // RMT Ticks per microsecond
const uint16_t DSHOT_RX_TIMEOUT_MS = 2;
const uint16_t DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility
const uint16_t RMT_BUFFER_SYMBOLS = 64;
const uint16_t RMT_QUEUE_DEPTH = 1;
const uint16_t GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame
const uint16_t POLE_PAIRS_MIN = 1;
const uint16_t MAGNETS_PER_POLE_PAIR = 2;
const uint16_t NO_DSHOT_TELEMETRY = 0;
const uint16_t DSHOT_PULSE_MIN = 800; // 0.8us minimum pulse
const uint16_t DSHOT_PULSE_MAX = 8000; // 8.0us maximum pulse
const uint16_t DSHOT_TELEMETRY_INVALID = DSHOT_THROTTLE_MAX;
const uint16_t DSHOT_TELEMETRY_BIT_POSITION = 11; // Bit position of the telemetry request flag in the DShot frame
const uint16_t DSHOT_CRC_BIT_SHIFT = 4; // Number of bits to shift to extract data from data_and_crc
// Command Constants (from DShotRMT.h private section)
const uint16_t DEFAULT_CMD_DELAY_US = 10;
const uint16_t DEFAULT_CMD_REPEAT_COUNT = 1;
const uint16_t SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats
const uint16_t SETTINGS_COMMAND_DELAY_US = 5;
// Timing parameters for each DShot mode
// Format: {bit_length_us, t1h_length_us}
const dshot_timing_us_t DSHOT_TIMING_US[] = {
{0.00, 0.00}, // DSHOT_OFF
{6.67, 5.00}, // DSHOT150
{3.33, 2.50}, // DSHOT300
{1.67, 1.25}, // DSHOT600
{0.83, 0.67}}; // DSHOT1200
// Error Messages
const char *const NONE = "";
const char *const UNKNOWN_ERROR = "Unknown Error!";
const char *const INIT_SUCCESS = "SignalGeneratorRMT initialized successfully";
const char *const INIT_FAILED = "SignalGeneratorRMT init failed!";
const char *const TX_INIT_SUCCESS = "TX RMT channel initialized successfully";
const char *const TX_INIT_FAILED = "TX RMT channel init failed!";
const char *const RX_INIT_SUCCESS = "RX RMT channel initialized successfully";
const char *const RX_INIT_FAILED = "RX RMT channel init failed!";
const char *const ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully";
const char *const ENCODER_INIT_FAILED = "RMT encoder init failed!";
const char *const ENCODING_SUCCESS = "Packet encoded successfully";
const char *const TRANSMISSION_SUCCESS = "Transmission successfully";
const char *const TRANSMISSION_FAILED = "Transmission failed!";
const char *const RECEIVER_FAILED = "RMT receiver failed!";
const char *const THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)";
const char *const PERCENT_NOT_IN_RANGE = "Percent not in range! (0.0 - 100.0)";
const char *const COMMAND_NOT_VALID = "Command not valid! (0 - 47)";
const char *const BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!";
const char *const TELEMETRY_SUCCESS = "Valid Telemetric Frame received!";
const char *const TELEMETRY_FAILED = "No valid Telemetric Frame received!";
const char *const INVALID_MAGNET_COUNT = "Invalid motor magnet count!";
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
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();
}

View File

@ -9,7 +9,7 @@
#pragma once #pragma once
// Web Site Content // Web Site Content
static constexpr char index_html[] = R"rawliteral( const char *index_html = R"rawliteral(
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">