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
# 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: |
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
- 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 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)
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
}
- 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
@ -96,135 +95,67 @@ jobs:
# ============================================================================
# Compilation Test
# This job compiles all example sketches.
# ============================================================================
compile-test-linux:
name: Compile Sketches (Linux)
compile-sketches:
name: Compile ${{ matrix.sketch }} (Linux)
needs: prepare-cache
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
- uses: arduino/setup-arduino-cli@v2
- name: Restore Arduino Core & Libraries Cache
id: cache-all-linux
uses: actions/cache/restore@v4
with:
path: |
~/.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
strategy:
fail-fast: false
matrix:
sketch:
- examples/throttle_percent/throttle_percent.ino
- examples/dshot300/dshot300.ino
- examples/web_control/web_control.ino
- examples/web_client/web_client.ino
compile-test-windows:
name: Compile Sketches (Windows)
runs-on: windows-latest
timeout-minutes: 15
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-win
uses: actions/cache/restore@v4
uses: actions/cache@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-windows-esp32-v1
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
key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }}
- name: Compile Sketch
run: |
arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} examples/dshot300/dshot300.ino
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-test-linux
- compile-test-windows
- 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-test-linux.result }}" == "success" && "${{ needs.compile-test-windows.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-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
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

View File

@ -6,5 +6,5 @@
* @license MIT
*/
#include "src/DShotRMT.h"
#include "src/DShotRMT.h"

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)
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
- **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.
- **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.
- **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
### Arduino IDE
@ -38,7 +54,7 @@ Here's a basic example of how to use the `DShotRMT` library to control a motor:
```cpp
#include <Arduino.h>
#include <DShotRMT.h>
#include <DShotRMT.h> // Include the DShotRMT library
// Define the GPIO pin connected to the motor ESC
const gpio_num_t MOTOR_PIN = GPIO_NUM_27;
@ -52,6 +68,9 @@ void setup() {
// Initialize the DShot motor
motor.begin();
// Print CPU Info
DShotRMT::printCpuInfo(Serial);
Serial.println("Motor initialized. Ramping up to 25% throttle...");
// Ramp up to 25% throttle over 2.5 seconds
@ -62,6 +81,9 @@ void setup() {
Serial.println("Stopping motor.");
motor.sendThrottlePercent(0);
// Print DShot Info
DShotRMT::printDShotInfo(motor, Serial);
}
void loop() {
@ -100,14 +122,24 @@ lib_deps =
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.)
- `begin()`: Initializes the RMT peripheral and the DShot encoder.
- `sendThrottlePercent(float percent)`: Sends a throttle value as a percentage (0.0-100.0).
- `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the motor.
- `sendCommand(uint16_t command)`: Sends a DShot command (0-47) to the motor.
- `getTelemetry(uint16_t magnet_count)`: Receives and parses telemetry data from the motor (for bidirectional DShot, which is currently not officially supported).
For more details, please refer to the `DShotRMT.h` header file.
- `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 DShot RMT channels and encoder.
- `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 ESC. A value of 0 sends a motor stop command.
- `sendCommand(uint16_t command)`: Sends a single DShot command (0-47) to the ESC.
- `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.
- `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

View File

@ -7,7 +7,7 @@
*/
#include <Arduino.h>
#include <DShotRMT.h>
#include "DShotRMT.h"
// USB serial port settings
static constexpr auto &USB_SERIAL = Serial0;
@ -18,12 +18,12 @@ static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// static constexpr auto MOTOR01_PIN = 17;
// 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)
// Note: Bidirectional DShot is currently not officially supported
// Note: Bidirectional DShot is currently not officially supported
// due to instability and external hardware requirements.
static constexpr auto IS_BIDIRECTIONAL = false;
static constexpr auto IS_BIDIRECTIONAL = false;
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
@ -41,7 +41,7 @@ void setup()
motor01.begin();
// Print CPU Info
motor01.printCpuInfo();
DShotRMT::printCpuInfo(USB_SERIAL);
//
printMenu();
@ -80,7 +80,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode
if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000))
{
motor01.printDShotInfo();
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
@ -132,7 +132,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
}
else if (input == "info")
{
motor01.printDShotInfo();
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
}
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;
// 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)
// Note: Bidirectional DShot is currently not officially supported
@ -98,7 +98,7 @@ void handleSerialInput(const String &input)
}
else if (input == "info")
{
motor01.printDShotInfo();
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
}
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;
// 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)
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)
{
// Ensure motor is stopped when disarmed
motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
}
// Print motor stats every 3 seconds in continuous mode
if ((esp_timer_get_time() - last_serial_update >= 3000000))
{
motor01.printDShotInfo();
USB_SERIAL.println(" ");
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
// Get Motor RPM if bidirectional and armed
if (IS_BIDIRECTIONAL && isArmed)
@ -269,9 +266,8 @@ void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t ind
if (!index)
{
// Safety: Ensure motor is stopped during update
motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
setArmingStatus(false);
// Safety: Ensure motor is stopped during update
motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); setArmingStatus(false);
USB_SERIAL.printf("OTA Update Start: %s\n", filename.c_str());
@ -451,7 +447,7 @@ void handleSerialInput(const String &input)
if (input == "info")
{
motor01.printDShotInfo();
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return;

View File

@ -41,7 +41,7 @@ static constexpr auto USB_SERIAL_BAUD = 115200;
static constexpr auto MOTOR01_PIN = 17;
// 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)
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
if ((esp_timer_get_time() - last_serial_update >= 3000000))
{
motor01.printDShotInfo();
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
@ -254,7 +254,7 @@ void handleSerialInput(const String &input)
}
if (input == "info")
{
motor01.printDShotInfo();
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return;

View File

@ -7,59 +7,7 @@
#######################################
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)

View File

@ -1,5 +1,5 @@
name=DShotRMT
version=0.8.3
version=0.8.6
author=Wastl Kraus <wir-sind-die-matrix.de>
maintainer=Wastl Kraus <wir-sind-die-matrix.de>
license=MIT
@ -8,4 +8,4 @@ paragraph=This library can control a BlHeli_S by using encoded DShot commands.
category=Signal Input/Output
url=https://github.com/derdoktor667/DShotRMT
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>
// 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
// Constructor with GPIO number
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),
_is_bidirectional(is_bidirectional),
_motor_magnet_count(magnet_count),
_dshot_timing(DSHOT_TIMING_US[mode]),
_dshot_timing(DSHOT_TIMING_US[static_cast<int>(mode)]),
_frame_timer_us(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_command_timestamp(0),
_parsed_packet(0),
_encoded_frame_value(0),
_packet{0},
_bitPositions{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),
@ -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
_preCalculateRMTTicks();
_preCalculateBitPositions();
// Activate internal pullup resistor
// gpio_set_pull_mode(_gpio, GPIO_PULLUP_ONLY);
@ -83,8 +58,8 @@ DShotRMT::~DShotRMT()
{
if (rmt_disable(_rmt_tx_channel) == DSHOT_OK)
{
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
}
}
@ -93,8 +68,8 @@ DShotRMT::~DShotRMT()
{
if (rmt_disable(_rmt_rx_channel) == DSHOT_OK)
{
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
}
}
@ -112,7 +87,7 @@ dshot_result_t DShotRMT::begin()
{
if (!_initTXChannel().success)
{
return {false, TX_INIT_FAILED};
return {false, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED};
}
if (_is_bidirectional)
@ -123,7 +98,7 @@ dshot_result_t DShotRMT::begin()
rmt_disable(_rmt_tx_channel);
rmt_del_channel(_rmt_tx_channel);
_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_tx_channel = nullptr;
if (_rmt_rx_channel) {
if (_rmt_rx_channel)
{
rmt_disable(_rmt_rx_channel);
rmt_del_channel(_rmt_rx_channel);
_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
@ -152,7 +128,7 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
// A throttle value of 0 is a disarm command
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
@ -167,7 +143,7 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
{
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
@ -179,9 +155,9 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
// Send DShot command to ESC
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);
@ -189,13 +165,14 @@ dshot_result_t DShotRMT::sendCommand(uint16_t command)
}
// 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))
{
result.msg = INVALID_COMMAND;
result.error_code = dshot_msg_code_t::DSHOT_ERROR_INVALID_COMMAND;
return result;
}
@ -209,7 +186,7 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re
if (!single_result.success)
{
all_successful = false;
result.msg = single_result.msg;
result.error_code = single_result.error_code;
break;
}
@ -225,7 +202,7 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re
if (result.success)
{
result.msg = COMMAND_SUCCESS;
result.error_code = dshot_msg_code_t::DSHOT_ERROR_COMMAND_SUCCESS;
}
return result;
@ -234,11 +211,11 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re
// Get telemetry data
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)
{
result.msg = BIDIR_NOT_ENABLED;
result.error_code = dshot_msg_code_t::DSHOT_ERROR_BIDIR_NOT_ENABLED;
return result;
}
@ -260,7 +237,7 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
result.success = true;
result.erpm = erpm;
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)
{
// 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);
}
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
dshot_result_t DShotRMT::saveESCSettings()
{
return sendCommand(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());
return sendCommand(dshotCommands_e::DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
}
// 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();
@ -352,19 +293,19 @@ dshot_result_t DShotRMT::_initTXChannel()
_tx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS;
_tx_channel_config.trans_queue_depth = RMT_QUEUE_DEPTH;
_rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
_rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
_rmt_tx_config.flags.eot_level = _is_bidirectional ? 1 : 0;
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)
{
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()
@ -372,7 +313,7 @@ dshot_result_t DShotRMT::_initRXChannel()
// Double check if bidirectional mode is enabled
if (!_is_bidirectional)
{
return {true, NONE};
return {true, dshot_msg_code_t::DSHOT_ERROR_NONE};
}
_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)
{
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
_rx_event_callbacks.on_recv_done = _on_rx_done;
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)
{
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
@ -406,22 +347,38 @@ dshot_result_t DShotRMT::_initRXChannel()
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)
{
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()
{
rmt_copy_encoder_config_t encoder_config = {};
if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
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_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
@ -439,7 +396,7 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value)
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
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
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, NONE};
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)
// 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 {false, TRANSMISSION_FAILED};
}
_timer_reset(); // Reset the timer for the next frame
return {true, TRANSMISSION_SUCCESS};
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)
{
_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
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);
// 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;
// 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;
}
@ -578,7 +506,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();
@ -586,11 +514,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
@ -613,3 +540,32 @@ bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const
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
#include <Arduino.h>
#include <dshot_commands.h>
#include <dshot_definitions.h>
#include <driver/gpio.h>
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
#include <atomic>
// DShot Protocol Constants & Types
static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
static constexpr auto DSHOT_THROTTLE_MIN = 48;
static constexpr auto DSHOT_THROTTLE_MAX = 2047;
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
// Main class for DShot signal generation and reception.
// This class provides an interface to generate DShot signals for Electronic Speed Controllers (ESCs)
// and to receive telemetry data using the ESP32's RMT peripheral.
class DShotRMT
{
public:
// Constructors & Destructor
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);
// Constructor for DShotRMT with GPIO number.
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);
// Destructor for DShotRMT.
// Cleans up RMT channels and encoder resources.
~DShotRMT();
// Public Core Functions
// Initializes the DShot RMT channels and encoder.
dshot_result_t begin();
// Sends a DShot throttle value to the ESC.
dshot_result_t sendThrottle(uint16_t throttle);
// Sends a DShot throttle value as a percentage to the ESC.
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(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);
// Sends a command to the ESC to request ESC information.
dshot_result_t getESCInfo();
// Sets the motor spin direction.
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();
// 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 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. Use sendThrottle() instead.
[[deprecated("Use sendThrottle() instead")]]
bool setThrottle(uint16_t throttle)
{
@ -111,6 +91,7 @@ public:
return result.success;
}
// Deprecated. Use sendCommand() instead.
[[deprecated("Use sendCommand() instead")]]
bool sendDShotCommand(uint16_t command)
{
@ -118,6 +99,7 @@ public:
return result.success;
}
// Deprecated. Use getTelemetry() instead.
[[deprecated("Use getTelemetry() instead")]]
uint32_t getMotorRPM(uint8_t magnet_count)
{
@ -126,55 +108,9 @@ public:
}
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 ---
bool _isValidCommand(dshot_commands_t command);
dshot_result_t _executeCommand(dshot_commands_t command);
bool _isValidCommand(dshotCommands_e command);
dshot_result_t _executeCommand(dshotCommands_e command);
// Core Configuration Variables
gpio_num_t _gpio;
@ -189,11 +125,10 @@ private:
uint16_t _last_throttle;
uint64_t _last_transmission_time_us;
uint64_t _last_command_timestamp;
uint16_t _parsed_packet;
uint16_t _encoded_frame_value;
dshot_packet_t _packet;
uint8_t _bitPositions[DSHOT_BITS_PER_FRAME];
uint16_t _level0; // Signal level for the first part of a pulse (always HIGH for DShot)
uint16_t _level1; // Signal level for the second part of a pulse (always LOW for DShot)
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;
@ -218,26 +153,18 @@ private:
// Private Packet Management Functions
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);
void _preCalculateRMTTicks();
void _preCalculateBitPositions();
// 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);
// 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
// Web Site Content
static constexpr char index_html[] = R"rawliteral(
const char *index_html = R"rawliteral(
<!DOCTYPE html>
<html lang="de">