release 0.9.0

release 0.9.0
This commit is contained in:
Wastl Kraus 2025-10-09 19:48:21 +02:00 committed by GitHub
commit 536a030dfc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 508 additions and 427 deletions

View File

@ -46,7 +46,7 @@ jobs:
~/.arduino15/packages ~/.arduino15/packages
~/.arduino15/cache ~/.arduino15/cache
~/Arduino/libraries ~/Arduino/libraries
key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }} key: linux-arduino-esp32-v2-v3
- name: Install Dependencies - name: Install Dependencies
if: steps.cache-arduino.outputs.cache-hit != 'true' if: steps.cache-arduino.outputs.cache-hit != 'true'
@ -83,7 +83,7 @@ jobs:
~/.arduino15/packages ~/.arduino15/packages
~/.arduino15/cache ~/.arduino15/cache
~/Arduino/libraries ~/Arduino/libraries
key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }} key: linux-arduino-esp32-v2-v3
- name: Run Arduino Lint - name: Run Arduino Lint
uses: arduino/arduino-lint-action@v2 uses: arduino/arduino-lint-action@v2
@ -125,7 +125,7 @@ jobs:
~/.arduino15/packages ~/.arduino15/packages
~/.arduino15/cache ~/.arduino15/cache
~/Arduino/libraries ~/Arduino/libraries
key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }} key: linux-arduino-esp32-v2-v3
- name: Compile Sketch - name: Compile Sketch
run: arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} "${{ matrix.sketch }}" run: arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} "${{ matrix.sketch }}"

View File

@ -6,5 +6,8 @@
* @license MIT * @license MIT
*/ */
#pragma once
#include <Arduino.h>
#include "src/DShotRMT.h" #include "src/DShotRMT.h"

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 Wastl Kraus Copyright (c) 2023 derdoktor667
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

View File

@ -1,11 +1,15 @@
# DShotRMT - ESP32 RMT DShot Driver # DShotRMT - ESP32 RMT DShot Driver
[![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)
[![Arduino Library](https://img.shields.io/badge/Arduino-Library-blue.svg)](https://www.arduinolibraries.com/libraries/dshot-rmt)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
An Arduino IDElibrary 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. An Arduino IDE 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 BLHeli ESCs in both Arduino and ESP-IDF projects.
The legacy version using the old `rmt.h` API is available in the `oldAPI` branch. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch.
---
### DShot300 Example Output ### DShot300 Example Output
Here's an example of the output from the `dshot300` example sketch: Here's an example of the output from the `dshot300` example sketch:
@ -15,7 +19,7 @@ Here's an example of the output from the `dshot300` example sketch:
## 🚀 Core Features ## 🚀 Core Features
- **Multiple DShot Modes:** Supports DSHOT150, DSHOT300, DSHOT600, and DSHOT1200. - **Multiple DShot Modes:** Supports DSHOT150, DSHOT300, DSHOT600, and DSHOT1200.
- **Bidirectional DShot Support:** Implemented, but note that official support is limited due to potential instability and external hardware requirements. Use with caution. - **Bidirectional DShot Support:** Implemented, but note that official support is limited due to potential instability and external hardware requirements. Use with caution (and pull-up).
- **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()`.
- **Error Handling:** Provides detailed feedback on operation success or failure via `dshot_result_t`. - **Error Handling:** Provides detailed feedback on operation success or failure via `dshot_result_t`.
@ -43,11 +47,11 @@ The DShot protocol defines specific timing characteristics for each mode. The fo
## ⚡ Quick Start ## ⚡ Quick Start
Here's a basic example of how to use the `DShotRMT` library to control a motor. Please use example sketches for more detailes: Here's a basic example of how to use the `DShotRMT` library to control a motor. Note that `DShotRMT.h` now includes all necessary dependencies, so you only need to include this single header. Please use example sketches for more detailes:
```cpp ```cpp
#include <Arduino.h> #include <Arduino.h>
#include <DShotRMT.h> // Include the DShotRMT library #include <DShotRMT.h>
// 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;
@ -65,14 +69,13 @@ void setup() {
printCpuInfo(Serial); printCpuInfo(Serial);
Serial.println("Motor initialized. Ramping up to 25% throttle..."); Serial.println("Motor initialized. Ramping up to 25% throttle...");
}
}
void loop() { void loop() {
// Ramp up to 25% throttle over 2.5 seconds // Ramp up to 25% throttle over 2.5 seconds
for (int i = 0; i <= 25; i++) { for (int i = 0; i <= 25; i++) {
motor.sendThrottlePercent(i); motor.sendThrottlePercent(i);
delay(100); delay(200);
} }
Serial.println("Stopping motor."); Serial.println("Stopping motor.");
@ -80,6 +83,9 @@ void loop() {
// Print DShot Info // Print DShot Info
printDShotInfo(motor, Serial); printDShotInfo(motor, Serial);
// Take a break before next bench run
delay(3000);
} }
``` ```
@ -108,8 +114,9 @@ The main class is `DShotRMT`. Here are the most important methods:
- `begin()`: Initializes the DShot RMT channels and encoder. - `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. - `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. - `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 command)`: Sends a DShot command to the ESC. Automatically handles repetitions and delays for specific commands (e.g., `DSHOT_CMD_SAVE_SETTINGS`).
- `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. - `sendCommand(dshotCommands_e command, uint16_t repeat_count, uint16_t delay_us)`: Sends a DShot command to the ESC with a specified repeat count and delay. This is a blocking function.
- `sendCommand(uint16_t command_value)`: Sends a DShot command to the ESC by accepting an integer value. It validates the input and then calls `sendCommand(dshotCommands_e command)`.
- `getTelemetry(uint16_t magnet_count = 0)`: Retrieves telemetry data from the ESC. If `magnet_count` is 0, uses the stored motor magnet count. - `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. - `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. - `setMotorSpinDirection(bool reversed)`: Sets the motor spin direction. `true` for reversed, `false` for normal.
@ -123,6 +130,14 @@ The main class is `DShotRMT`. Here are the most important methods:
- `getEncodedFrameValue()`: Gets the last encoded DShot frame value. - `getEncodedFrameValue()`: Gets the last encoded DShot frame value.
- `getThrottleValue()`: Gets the last transmitted throttle value. - `getThrottleValue()`: Gets the last transmitted throttle value.
## ⚙️ ESP-IDF Integration
This library is built upon the ESP-IDF framework, specifically leveraging its RMT (Remote Control Peripheral) module for precise signal generation. For detailed information on the underlying ESP-IDF components and their usage, please refer to the official ESP-IDF documentation:
* [ESP-IDF v5.5.1 Documentation](https://docs.espressif.com/projects/esp-idf/en/v5.5.1/)
---
## 🤝 Contributing ## 🤝 Contributing
Contributions are welcome! Please fork the repository, create a feature branch, and submit a pull request. Contributions are welcome! Please fork the repository, create a feature branch, and submit a pull request.

View File

@ -1,6 +1,6 @@
/** /**
* @file throttle_percent.ino * @file throttle_percent.ino
* @brief Demo sketch for DShotRMT library using percentage throttle. * @brief Example sketch for DShotRMT library demonstrating throttle control by percentage
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-20 * @date 2025-09-20
* @license MIT * @license MIT
@ -28,7 +28,7 @@ static constexpr auto IS_BIDIRECTIONAL = false;
static constexpr auto MOTOR01_MAGNET_COUNT = 14; static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Creates the motor instance // Creates the motor instance
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL, MOTOR01_MAGNET_COUNT); DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL, DEFAULT_MOTOR_MAGNET_COUNT);
// Forward declaration // Forward declaration
void handleSerialInput(const String &input); void handleSerialInput(const String &input);

View File

@ -1,6 +1,6 @@
/** /**
* @file web_client.ino * @file web_client.ino
* @brief DShotRMT Web Control as WiFi Client * @brief Example sketch for DShotRMT library demonstrating web control via a client
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-11 * @date 2025-09-11
* @license MIT * @license MIT
@ -16,12 +16,11 @@
******************************************************************/ ******************************************************************/
#include <Arduino.h> #include <Arduino.h>
#include <Update.h>
#include <WiFi.h>
#include <DShotRMT.h> #include <DShotRMT.h>
#include <ota_update.h> #include <WiFi.h>
#include <web_content.h> #include <Update.h>
#include "web_utilities/ota_update.h"
#include "web_utilities/web_content.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
@ -51,7 +50,7 @@ static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is
static constexpr auto MOTOR01_MAGNET_COUNT = 14; static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Creates the motor instance // Creates the motor instance
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL); DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL, MOTOR01_MAGNET_COUNT);
// Web Server Configuration // Web Server Configuration
AsyncWebServer server(80); AsyncWebServer server(80);
@ -177,7 +176,7 @@ void loop()
// Get Motor RPM if bidirectional and armed // Get Motor RPM if bidirectional and armed
if (IS_BIDIRECTIONAL && isArmed) if (IS_BIDIRECTIONAL && isArmed)
{ {
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); dshot_result_t telem_result = motor01.getTelemetry();
printDShotResult(telem_result); printDShotResult(telem_result);
} }
@ -194,7 +193,7 @@ void loop()
if (IS_BIDIRECTIONAL && isArmed) if (IS_BIDIRECTIONAL && isArmed)
{ {
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); dshot_result_t telem_result = motor01.getTelemetry();
if (telem_result.success && telem_result.motor_rpm > 0) if (telem_result.success && telem_result.motor_rpm > 0)
{ {
current_rpm = String(telem_result.motor_rpm); current_rpm = String(telem_result.motor_rpm);
@ -235,11 +234,11 @@ void setupOTA()
// Serve OTA update page // Serve OTA update page
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request) server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send_P(200, "text/html", ota_html); }); { request->send_P(200, "text/html", ota_html); });
// Handle OTA update upload // Handle OTA update upload
server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request) server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request)
{ {
bool shouldReboot = !Update.hasError(); bool shouldReboot = !Update.hasError();
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", AsyncWebServerResponse *response = request->beginResponse(200, "text/plain",
@ -254,7 +253,8 @@ void setupOTA()
ESP.restart(); ESP.restart();
} else { } else {
USB_SERIAL.println("OTA Update failed!"); USB_SERIAL.println("OTA Update failed!");
} }, handleOTAUpload); }
}, handleOTAUpload);
USB_SERIAL.println("OTA Update ready at: /update"); USB_SERIAL.println("OTA Update ready at: /update");
} }
@ -344,7 +344,7 @@ void printWiFiStatus()
{ {
USB_SERIAL.println(); USB_SERIAL.println();
USB_SERIAL.println("***********************************************"); USB_SERIAL.println("***********************************************");
USB_SERIAL.println(" --- WIFI INFO --- "); USB_SERIAL.println(" --- WIFI INFO ---");
USB_SERIAL.println("***********************************************"); USB_SERIAL.println("***********************************************");
USB_SERIAL.printf("SSID: %s\n", WiFi.SSID().c_str()); USB_SERIAL.printf("SSID: %s\n", WiFi.SSID().c_str());
USB_SERIAL.printf("IP Address: %s\n", WiFi.localIP().toString().c_str()); USB_SERIAL.printf("IP Address: %s\n", WiFi.localIP().toString().c_str());
@ -393,7 +393,7 @@ void printMenu()
{ {
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
USB_SERIAL.println("***********************************************"); USB_SERIAL.println("***********************************************");
USB_SERIAL.println(" --- DShotRMT Web Client Demo --- "); USB_SERIAL.println(" --- DShotRMT Web Client Demo ---");
USB_SERIAL.println("***********************************************"); USB_SERIAL.println("***********************************************");
if (wifi_connected) if (wifi_connected)
@ -494,7 +494,7 @@ void handleSerialInput(const String &input)
{ {
if (isArmed) if (isArmed)
{ {
dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); dshot_result_t result = motor01.getTelemetry();
printDShotResult(result); printDShotResult(result);
} }
else else
@ -684,4 +684,4 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
case WS_EVT_ERROR: case WS_EVT_ERROR:
break; break;
} }
} }

View File

@ -1,6 +1,6 @@
/** /**
* @file web_control.ino * @file web_control.ino
* @brief Demo sketch for DShotRMT library * @brief Example sketch for DShotRMT library demonstrating web control via an access point
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-09 * @date 2025-09-09
* @license MIT * @license MIT
@ -16,10 +16,10 @@
******************************************************************/ ******************************************************************/
#include <Arduino.h> #include <Arduino.h>
#include <WiFi.h>
#include <DShotRMT.h> #include <DShotRMT.h>
#include <web_content.h> #include <WiFi.h>
#include <Update.h>
#include "web_utilities/web_content.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
@ -50,7 +50,7 @@ static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is
static constexpr auto MOTOR01_MAGNET_COUNT = 14; static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Creates the motor instance // Creates the motor instance
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL); DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL, MOTOR01_MAGNET_COUNT);
// Web Server Configuration // Web Server Configuration
AsyncWebServer server(80); AsyncWebServer server(80);
@ -145,7 +145,7 @@ void loop()
// Get Motor RPM if bidirectional and armed // Get Motor RPM if bidirectional and armed
if (IS_BIDIRECTIONAL && isArmed) if (IS_BIDIRECTIONAL && isArmed)
{ {
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); dshot_result_t telem_result = motor01.getTelemetry();
printDShotResult(telem_result); printDShotResult(telem_result);
} }
@ -160,7 +160,7 @@ void loop()
if (IS_BIDIRECTIONAL && isArmed) if (IS_BIDIRECTIONAL && isArmed)
{ {
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); dshot_result_t telem_result = motor01.getTelemetry();
if (telem_result.success && telem_result.motor_rpm > 0) if (telem_result.success && telem_result.motor_rpm > 0)
{ {
current_rpm = String(telem_result.motor_rpm); current_rpm = String(telem_result.motor_rpm);
@ -263,7 +263,7 @@ void handleSerialInput(const String &input)
{ {
if (isArmed) if (isArmed)
{ {
dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); dshot_result_t result = motor01.getTelemetry();
printDShotResult(result); printDShotResult(result);
} }
else else

View File

@ -1,5 +1,5 @@
name=DShotRMT name=DShotRMT
version=0.8.9 version=0.9.0
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

View File

@ -6,7 +6,7 @@
* @license MIT * @license MIT
*/ */
#include <DShotRMT.h> #include "DShotRMT.h"
// 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)
@ -14,7 +14,7 @@ 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[static_cast<int>(mode)]) _dshot_timing(DSHOT_TIMING_US[_mode])
{ {
// Pre-calculate timing and bit positions for performance // Pre-calculate timing and bit positions for performance
_preCalculateRMTTicks(); _preCalculateRMTTicks();
@ -33,21 +33,17 @@ DShotRMT::~DShotRMT()
// Cleanup TX channel // Cleanup TX channel
if (_rmt_tx_channel) if (_rmt_tx_channel)
{ {
if (rmt_disable(_rmt_tx_channel) == DSHOT_OK) 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;
}
} }
// Cleanup RX channel // Cleanup RX channel
if (_rmt_rx_channel) if (_rmt_rx_channel)
{ {
if (rmt_disable(_rmt_rx_channel) == DSHOT_OK) 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;
}
} }
// Cleanup encoder // Cleanup encoder
@ -61,41 +57,33 @@ DShotRMT::~DShotRMT()
// Initialize DShotRMT // Initialize DShotRMT
dshot_result_t DShotRMT::begin() dshot_result_t DShotRMT::begin()
{ {
if (!_initTXChannel().success) dshot_result_t result = init_rmt_tx_channel(_gpio, &_rmt_tx_channel, _is_bidirectional);
if (!result.success)
{ {
return {false, dshot_msg_code_t::DSHOT_TX_INIT_FAILED}; _cleanupRmtResources(); // Clean up any allocated resources on failure
return result;
} }
if (_is_bidirectional) if (_is_bidirectional)
{ {
if (!_initRXChannel().success) result = init_rmt_rx_channel(_gpio, &_rmt_rx_channel, &_rx_event_callbacks, this);
if (!result.success)
{ {
// Cleanup previously allocated TX channel on failure _cleanupRmtResources(); // Clean up any allocated resources on failure
rmt_disable(_rmt_tx_channel); return result;
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
return {false, dshot_msg_code_t::DSHOT_RX_INIT_FAILED};
} }
} }
if (!_initDShotEncoder().success) result = init_dshot_encoder(&_dshot_encoder, _rmt_ticks, _pulse_level, _idle_level);
if (!result.success)
{ {
// Cleanup previously allocated channels on failure _cleanupRmtResources(); // Clean up any allocated resources on failure
rmt_disable(_rmt_tx_channel); return result;
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
if (_rmt_rx_channel)
{
rmt_disable(_rmt_rx_channel);
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
}
return {false, dshot_msg_code_t::DSHOT_ENCODER_INIT_FAILED};
} }
return {true, dshot_msg_code_t::DSHOT_INIT_SUCCESS}; return {true, DSHOT_INIT_SUCCESS};
} }
// Send throttle value // Send throttle value
@ -104,7 +92,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(dshotCommands_e::DSHOT_CMD_MOTOR_STOP); return sendCommand(DSHOT_CMD_MOTOR_STOP);
} }
// Constrain throttle to the valid DShot range // Constrain throttle to the valid DShot range
@ -119,7 +107,7 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
{ {
if (percent < 0.0f || percent > 100.0f) if (percent < 0.0f || percent > 100.0f)
{ {
return {false, dshot_msg_code_t::DSHOT_PERCENT_NOT_IN_RANGE}; return {false, DSHOT_PERCENT_NOT_IN_RANGE};
} }
// Map percent to DShot throttle range // Map percent to DShot throttle range
@ -127,23 +115,47 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
return sendThrottle(throttle); return sendThrottle(throttle);
} }
// Send DShot command to ESC // Sends a DShot command (0-47) to the ESC by accepting an integer value.
dshot_result_t DShotRMT::sendCommand(dshotCommands_e command) dshot_result_t DShotRMT::sendCommand(uint16_t command_value)
{ {
_packet = _buildDShotPacket(static_cast<uint16_t>(command)); // Validate the integer command value before casting
return _sendDShotFrame(_packet); if (command_value < DSHOT_CMD_MOTOR_STOP || command_value > DSHOT_CMD_MAX)
{
return {false, DSHOT_COMMAND_NOT_VALID};
}
return sendCommand(static_cast<dshotCommands_e>(command_value));
} }
// Sends a DShot command (0-47) to the ESC.
// Send full DShot commands for setup etc dshot_result_t DShotRMT::sendCommand(dshotCommands_e command)
// This is a blocking function that uses delayMicroseconds for repetitions.
dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uint16_t repeat_count, uint16_t delay_us)
{ {
dshot_result_t result = {false, dshot_msg_code_t::DSHOT_UNKNOWN, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT;
uint16_t delay_us = DEFAULT_CMD_DELAY_US;
if (!_isValidCommand(dshot_command)) switch (command)
{ {
result.result_code = dshot_msg_code_t::DSHOT_INVALID_COMMAND; case DSHOT_CMD_SAVE_SETTINGS:
case DSHOT_CMD_SPIN_DIRECTION_NORMAL:
case DSHOT_CMD_SPIN_DIRECTION_REVERSED:
repeat_count = SETTINGS_COMMAND_REPEATS;
delay_us = SETTINGS_COMMAND_DELAY_US;
break;
default:
// For other commands, use default repeat and delay
break;
}
return sendCommand(command, repeat_count, delay_us);
}
// Sends a DShot command (0-47) to the ESC with a specified repeat count and delay.
dshot_result_t DShotRMT::sendCommand(dshotCommands_e command, uint16_t repeat_count, uint16_t delay_us)
{
dshot_result_t result = {false, DSHOT_UNKNOWN, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY};
if (!_isValidCommand(command))
{
result.result_code = DSHOT_INVALID_COMMAND;
return result; return result;
} }
@ -152,7 +164,7 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin
// Send command multiple times with delay // Send command multiple times with delay
for (uint16_t i = 0; i < repeat_count; i++) for (uint16_t i = 0; i < repeat_count; i++)
{ {
dshot_result_t single_result = _executeCommand(dshot_command); dshot_result_t single_result = _executeCommand(command);
if (!single_result.success) if (!single_result.success)
{ {
@ -168,12 +180,11 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin
} }
} }
//
result.success = all_successful; result.success = all_successful;
if (result.success) if (result.success)
{ {
result.result_code = dshot_msg_code_t::DSHOT_COMMAND_SUCCESS; result.result_code = DSHOT_COMMAND_SUCCESS;
} }
return result; return result;
@ -182,11 +193,11 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin
// Get telemetry data // Get telemetry data
dshot_result_t DShotRMT::getTelemetry() dshot_result_t DShotRMT::getTelemetry()
{ {
dshot_result_t result = {false, dshot_msg_code_t::DSHOT_TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; dshot_result_t result = {false, DSHOT_TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY};
if (!_is_bidirectional) if (!_is_bidirectional)
{ {
result.result_code = dshot_msg_code_t::DSHOT_BIDIR_NOT_ENABLED; result.result_code = DSHOT_BIDIR_NOT_ENABLED;
return result; return result;
} }
@ -208,7 +219,7 @@ dshot_result_t DShotRMT::getTelemetry()
result.success = true; result.success = true;
result.erpm = erpm; result.erpm = erpm;
result.motor_rpm = motor_rpm; result.motor_rpm = motor_rpm;
result.result_code = dshot_msg_code_t::DSHOT_TELEMETRY_SUCCESS; result.result_code = DSHOT_TELEMETRY_SUCCESS;
} }
} }
@ -221,24 +232,22 @@ dshot_result_t DShotRMT::setMotorSpinDirection(bool reversed)
// Use command as a yes / no switch // Use command as a yes / no switch
dshotCommands_e command = reversed ? dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_REVERSED : dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_NORMAL; dshotCommands_e command = reversed ? dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_REVERSED : dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_NORMAL;
return _sendCommandInternal(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); return sendCommand(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
} }
// Use with caution // Use with caution
dshot_result_t DShotRMT::saveESCSettings() dshot_result_t DShotRMT::saveESCSettings()
{ {
return _sendCommandInternal(dshotCommands_e::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);
} }
// Simple check // Simple check
bool DShotRMT::_isValidCommand(dshotCommands_e command) const bool DShotRMT::_isValidCommand(dshotCommands_e command) const
{ {
return (command >= dshotCommands_e::DSHOT_CMD_MOTOR_STOP && command <= dshotCommands_e::DSHOT_CMD_MAX); return (command >= dshotCommands_e::DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX);
} }
// // Executes a single DShot command by building and sending a DShot frame.
dshot_result_t DShotRMT::_executeCommand(dshotCommands_e 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();
@ -252,104 +261,6 @@ dshot_result_t DShotRMT::_executeCommand(dshotCommands_e command)
return result; return result;
} }
// Private Initialization Functions
dshot_result_t DShotRMT::_initTXChannel()
{
_tx_channel_config.gpio_num = _gpio;
_tx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT;
_tx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION;
_tx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS;
_tx_channel_config.trans_queue_depth = RMT_QUEUE_DEPTH;
_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, dshot_msg_code_t::DSHOT_TX_INIT_FAILED};
}
if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
{
return {false, dshot_msg_code_t::DSHOT_TX_INIT_FAILED};
}
return {true, dshot_msg_code_t::DSHOT_TX_INIT_SUCCESS};
}
dshot_result_t DShotRMT::_initRXChannel()
{
// Double check if bidirectional mode is enabled
if (!_is_bidirectional)
{
return {true, dshot_msg_code_t::DSHOT_NONE};
}
_rx_channel_config.gpio_num = _gpio;
_rx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT;
_rx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION;
_rx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS;
// Filter for pulses that are within a reasonable range for DShot telemetry
_rmt_rx_config.signal_range_min_ns = DSHOT_PULSE_MIN_NS;
_rmt_rx_config.signal_range_max_ns = DSHOT_PULSE_MAX_NS;
if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
{
return {false, dshot_msg_code_t::DSHOT_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, dshot_msg_code_t::DSHOT_CALLBACK_REGISTERING_FAILED};
}
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{
return {false, dshot_msg_code_t::DSHOT_RX_INIT_FAILED};
}
// Start the receiver to wait for incoming telemetry data
rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME];
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, dshot_msg_code_t::DSHOT_RECEIVER_FAILED};
}
return {true, dshot_msg_code_t::DSHOT_RX_INIT_SUCCESS};
}
//
dshot_result_t DShotRMT::_initDShotEncoder()
{
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, dshot_msg_code_t::DSHOT_ENCODER_INIT_FAILED};
}
return {true, dshot_msg_code_t::DSHOT_ENCODER_INIT_SUCCESS};
}
// Private Packet Management Functions // Private Packet Management Functions
dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value) const dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value) const
{ {
@ -387,18 +298,18 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t &data) const
void DShotRMT::_preCalculateRMTTicks() void DShotRMT::_preCalculateRMTTicks()
{ {
// Pre-calculate all timing values in RMT ticks to save CPU cycles later // Pre-calculate all timing values in RMT ticks to save CPU cycles later.
_rmt_ticks.bit_length_ticks = static_cast<uint16_t>(_dshot_timing.bit_length_us * RMT_TICKS_PER_US); _rmt_ticks.bit_length_ticks = static_cast<uint16_t>(_dshot_timing.bit_length_us * RMT_TICKS_PER_US);
_rmt_ticks.t1h_ticks = static_cast<uint16_t>(_dshot_timing.t1h_lenght_us * RMT_TICKS_PER_US); _rmt_ticks.t1h_ticks = static_cast<uint16_t>(_dshot_timing.t1h_lenght_us * RMT_TICKS_PER_US);
_rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double of 0 _rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a '0' bit is half of a '1' bit.
_rmt_ticks.t1l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t1h_ticks; _rmt_ticks.t1l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t1h_ticks;
_rmt_ticks.t0l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t0h_ticks; _rmt_ticks.t0l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t0h_ticks;
// Calculate the minimum time required between frames // Calculate the minimum time required between frames.
// Pause between frames is frame time in us, some padding and about 30 us is added by hardware // Pause between frames is frame time in us, some padding and about 30 us is added by hardware.
_frame_timer_us = (static_cast<uint64_t>(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US; _frame_timer_us = (static_cast<uint64_t>(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US;
// For bidirectional, double up // For bidirectional, double up.
if (_is_bidirectional) if (_is_bidirectional)
{ {
_frame_timer_us = (_frame_timer_us << 1); _frame_timer_us = (_frame_timer_us << 1);
@ -411,7 +322,7 @@ 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 (!_isFrameIntervalElapsed()) if (!_isFrameIntervalElapsed())
{ {
return {true, dshot_msg_code_t::DSHOT_NONE}; return {true, DSHOT_NONE};
} }
_encoded_frame_value = _buildDShotFrameValue(packet); _encoded_frame_value = _buildDShotFrameValue(packet);
@ -422,24 +333,28 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
// The DShot frame is 16 bits, which is 2 bytes // The DShot frame is 16 bits, which is 2 bytes
size_t tx_size_bytes = sizeof(swapped_value); 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) rmt_transmit_config_t tx_config = {}; // Initialize all members to zero
tx_config.loop_count = 0; // No automatic loops - real-time calculation
tx_config.flags.eot_level = _is_bidirectional ? 1 : 0;
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &swapped_value, tx_size_bytes, &tx_config) != DSHOT_OK)
{ {
return {false, dshot_msg_code_t::DSHOT_TRANSMISSION_FAILED}; return {false, DSHOT_TRANSMISSION_FAILED};
} }
_recordFrameTransmissionTime(); // Reset the timer for the next frame _recordFrameTransmissionTime(); // Reset the timer for the next frame
return {true, dshot_msg_code_t::DSHOT_TRANSMISSION_SUCCESS}; return {true, DSHOT_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
// 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) const uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) const
{ {
uint32_t gcr_value = 0; uint32_t gcr_value = 0;
// Step 1: Decode RMT symbols into a 21-bit GCR (Group Code Recording) value. // Decode RMT symbols into a 21-bit GCR (Group Code Recording) value.
// The ESC sends back a signal where the duration determines the bit value. // The ESC sends back a signal where the duration determines the bit value.
for (size_t i = 0; i < GCR_BITS_PER_FRAME; ++i) for (size_t i = 0; i < GCR_BITS_PER_FRAME; ++i)
{ {
@ -447,23 +362,23 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
gcr_value = (gcr_value << 1) | bit_is_one; gcr_value = (gcr_value << 1) | bit_is_one;
} }
// Step 2: Perform GCR decoding (GCR = Value ^ (Value >> 1)) // Perform GCR decoding (GCR = Value ^ (Value >> 1)).
uint32_t decoded_frame = gcr_value ^ (gcr_value >> 1); uint32_t decoded_frame = gcr_value ^ (gcr_value >> 1);
// Step 3: Extract the 16-bit DShot frame from the decoded data // Extract the 16-bit DShot frame from the decoded data.
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 // Extract data and CRC from the 16-bit frame.
uint16_t received_data = data_and_crc >> DSHOT_CRC_BIT_SHIFT; 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. // A valid response must have the telemetry request bit set to 1. This is a sanity check.
if (!((received_data >> DSHOT_TELEMETRY_BIT_POSITION) & 1)) if (!((received_data >> DSHOT_TELEMETRY_BIT_POSITION) & 1))
{ {
return DSHOT_NULL_PACKET; return DSHOT_NULL_PACKET;
} }
// Step 6: Calculate and validate CRC // Calculate and validate CRC.
uint16_t calculated_crc = _calculateCRC(received_data); uint16_t calculated_crc = _calculateCRC(received_data);
if (received_crc != calculated_crc) if (received_crc != calculated_crc)
{ {
@ -509,3 +424,26 @@ bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const
return false; return false;
} }
void DShotRMT::_cleanupRmtResources()
{
if (_rmt_tx_channel)
{
rmt_disable(_rmt_tx_channel);
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
}
if (_rmt_rx_channel)
{
rmt_disable(_rmt_rx_channel);
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
}
if (_dshot_encoder)
{
rmt_del_encoder(_dshot_encoder);
_dshot_encoder = nullptr;
}
}

View File

@ -1,146 +1,124 @@
/** /**
* @file DShotRMT.h * @file DShotRMT.h
* @brief Optimized DShot signal generation using ESP32 RMT with bidirectional support * @brief DShot signal generation using ESP32 RMT with bidirectional support
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-18 * @date 2025-06-11
* @license MIT * @license MIT
*/ */
#pragma once #pragma once
#include <Arduino.h>
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
#include <atomic> #include <atomic>
#include <Arduino.h>
#include <driver/gpio.h>
#include <driver/rmt_rx.h>
#include <driver/rmt_tx.h>
#include "dshot_definitions.h" #include "dshot_definitions.h"
#include <driver/rmt_encoder.h> #include "dshot_init.h"
// DShotRMT Class Definition // Forward declaration for the RMT receive callback
class DShotRMT;
void IRAM_ATTR rmt_rx_done_callback(rmt_channel_handle_t rx_chan, const rmt_rx_done_event_data_t *edata, void *user_data);
// DShot Protocol Constants
static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
// DShotRMT class for generating DShot signals and receiving telemetry.
class DShotRMT class DShotRMT
{ {
public: public:
// Constructor for DShotRMT with GPIO number. // Constructor for DShotRMT.
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); DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Constructor for DShotRMT with Arduino pin number. // Constructor using 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 = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Destructor // Destructor
~DShotRMT(); ~DShotRMT();
// Public Core Functions // Initialize DShotRMT
// Initializes the DShot RMT channels and encoder.
dshot_result_t begin(); dshot_result_t begin();
// Sends a throttle value as a percentage (0.0-100.0) to the ESC. // Sends a raw throttle value to the ESC.
dshot_result_t sendThrottlePercent(float percent);
// Sends a raw throttle value (48-2047) to the ESC. A value of 0 sends a motor stop command.
dshot_result_t sendThrottle(uint16_t throttle); dshot_result_t sendThrottle(uint16_t throttle);
// Sends a DShot command (0-47) to the ESC. // Sends a throttle value as a percentage to the ESC.
dshot_result_t sendThrottlePercent(float percent);
// Sends a DShot command to the ESC by accepting an integer value.
dshot_result_t sendCommand(uint16_t command_value);
// Sends a DShot command to the ESC.
dshot_result_t sendCommand(dshotCommands_e command); dshot_result_t sendCommand(dshotCommands_e command);
// Sends a DShot command to the ESC with a specified repeat count and delay.
dshot_result_t sendCommand(dshotCommands_e command, uint16_t repeat_count, uint16_t delay_us);
// Retrieves telemetry data from the ESC. // Retrieves telemetry data from the ESC.
dshot_result_t getTelemetry(); dshot_result_t getTelemetry();
// Sets the motor spin direction. 'true' for reversed, 'false' for normal. // 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. // Sends a command to the ESC to save its current settings.
dshot_result_t saveESCSettings(); dshot_result_t saveESCSettings();
// Public Utility & Info Functions // Getters for DShot info
// Sets the motor magnet count for RPM calculation.
void setMotorMagnetCount(uint16_t magnet_count);
// Gets the current DShot mode.
dshot_mode_t getMode() const { return _mode; } dshot_mode_t getMode() const { return _mode; }
// Checks if bidirectional DShot is enabled.
bool isBidirectional() const { return _is_bidirectional; } bool isBidirectional() const { return _is_bidirectional; }
uint16_t getThrottleValue() const { return _last_throttle; }
// Gets the encoded frame value.
uint16_t getEncodedFrameValue() const { return _encoded_frame_value; } uint16_t getEncodedFrameValue() const { return _encoded_frame_value; }
// Gets the last transmitted throttle value.
uint16_t getThrottleValue() const { return _packet.throttle_value; }
// Testing return "verbose" messages
const char *getDShotMsg(dshot_result_t &result) const { return (_get_result_code_str(result.result_code)); }
// Deprecated Methods
// Deprecated. Use sendThrottle() instead.
[[deprecated("Use sendThrottle() instead")]]
dshot_result_t sendValue(uint16_t value) { return sendThrottle(value); }
// Deprecated. Use sendCommand() instead.
[[deprecated("Use sendCommand() instead")]]
dshot_result_t sendCommand(uint16_t command) { return sendCommand(static_cast<dshotCommands_e>(command)); }
private: private:
// --- UTILITY METHODS --- static bool IRAM_ATTR _on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
bool _isValidCommand(dshotCommands_e command) const;
dshot_result_t _executeCommand(dshotCommands_e command);
dshot_result_t _sendCommandInternal(dshotCommands_e dshot_command, uint16_t repeat_count, uint16_t delay_us);
// Core Configuration Variables // DShot Configuration Parameters
gpio_num_t _gpio; gpio_num_t _gpio; // GPIO pin used for DShot communication
dshot_mode_t _mode; dshot_mode_t _mode; // DShot mode (e.g., DSHOT300, DSHOT600)
bool _is_bidirectional; bool _is_bidirectional; // True if bidirectional DShot is enabled
uint16_t _motor_magnet_count; uint16_t _motor_magnet_count; // Number of magnets in the motor for RPM calculation
const dshot_timing_us_t &_dshot_timing; dshot_timing_us_t _dshot_timing; // DShot timing parameters in microseconds
uint64_t _frame_timer_us = 0;
// Timing & Packet Variables // RMT Hardware Handles and Configuration
rmt_ticks_t _rmt_ticks{}; rmt_channel_handle_t _rmt_tx_channel = nullptr; // RMT transmit channel handle
uint16_t _last_throttle = dshotCommands_e::DSHOT_CMD_MOTOR_STOP; rmt_channel_handle_t _rmt_rx_channel = nullptr; // RMT receive channel handle
uint64_t _last_transmission_time_us = 0; rmt_encoder_handle_t _dshot_encoder = nullptr; // DShot RMT encoder handle
uint64_t _last_command_timestamp = 0; rmt_ticks_t _rmt_ticks; // Pre-calculated RMT timing ticks
uint16_t _encoded_frame_value = 0; uint16_t _pulse_level = 1; // Output level for a pulse (typically high)
dshot_packet_t _packet{}; uint16_t _idle_level = 0; // Output level for idle (typically low)
uint16_t _pulse_level = 1; // DShot protocol: Signal is idle-low, so pulses start by going HIGH.
uint16_t _idle_level = 0; // DShot protocol: Signal returns to LOW after the high pulse.
// RMT Hardware Handles // DShot Frame Timing and State Variables
rmt_channel_handle_t _rmt_tx_channel = nullptr; uint64_t _last_transmission_time_us = 0; // Timestamp of the last DShot frame transmission
rmt_channel_handle_t _rmt_rx_channel = nullptr; uint64_t _frame_timer_us = 0; // Minimum time required between DShot frames
rmt_encoder_handle_t _dshot_encoder = nullptr; uint16_t _last_throttle = 0; // Last transmitted throttle value
dshot_packet_t _packet; // Current DShot packet being processed
uint16_t _encoded_frame_value = 0; // Last encoded 16-bit DShot frame value
uint64_t _last_command_timestamp = 0; // Timestamp of the last command sent
// RMT Configuration Structures // Telemetry Related Variables
rmt_tx_channel_config_t _tx_channel_config{}; std::atomic<uint16_t> _last_erpm_atomic = 0; // Atomically stored last received eRPM value
rmt_rx_channel_config_t _rx_channel_config{}; std::atomic<bool> _telemetry_ready_flag_atomic = false; // Atomically stored flag indicating new telemetry data
rmt_transmit_config_t _rmt_tx_config{}; rmt_rx_event_callbacks_t _rx_event_callbacks = {
rmt_receive_config_t _rmt_rx_config{}; // RMT receive event callbacks
.on_recv_done = _on_rx_done,
};
// Bidirectional / Telemetry Variables // Private Helper Functions for DShot Protocol Logic
rmt_rx_event_callbacks_t _rx_event_callbacks{}; bool _isValidCommand(dshotCommands_e command) const; // Checks if a given DShot command is valid
std::atomic<uint16_t> _last_erpm_atomic{0}; dshot_result_t _executeCommand(dshotCommands_e command); // Executes a single DShot command
std::atomic<bool> _telemetry_ready_flag_atomic{false}; dshot_packet_t _buildDShotPacket(const uint16_t &value) const; // Builds a DShot packet from a value (throttle or command)
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet) const; // Combines packet data into a 16-bit DShot frame value
uint16_t _calculateCRC(const uint16_t &data) const; // Calculates the 4-bit CRC for a DShot frame
void _preCalculateRMTTicks(); // Pre-calculates RMT timing ticks for the selected DShot mode
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet); // Sends a DShot frame via RMT TX channel
uint16_t IRAM_ATTR _decodeDShotFrame(const rmt_symbol_word_t *symbols) const; // Decodes a received RMT symbol array into an eRPM value
bool IRAM_ATTR _isFrameIntervalElapsed() const; // Checks if enough time has passed since the last frame transmission
void _recordFrameTransmissionTime(); // Records the current time as the last frame transmission time
// Private Initialization Functions // Static Callback Function for RMT RX Events
dshot_result_t _initTXChannel(); void _cleanupRmtResources();
dshot_result_t _initRXChannel();
dshot_result_t _initDShotEncoder();
// Private Packet Management Functions
dshot_packet_t _buildDShotPacket(const uint16_t &value) const;
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet) const;
uint16_t _calculateCRC(const uint16_t &data) const;
void _preCalculateRMTTicks();
// Private Frame Processing Functions
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet);
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols) const;
// Private Timing Control Functions
bool _isFrameIntervalElapsed() const;
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);
}; };
// Include utility functions after class definition #include "dshot_utils.h" // Include for helper functions
#include "dshot_utils.h"

View File

@ -1,10 +1,28 @@
/**
* @file dshot_definitions.h
* @brief Defines DShot protocol constants, data structures, and command enums for DShotRMT library
* @author Wastl Kraus
* @date 2025-10-04
* @license MIT
*/
#pragma once #pragma once
#include <Arduino.h> #include <cstdint>
#include <driver/gpio.h> #include <driver/rmt_common.h>
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h> // DShot protocol definitions
#include <atomic> static constexpr uint16_t DSHOT_FRAME_LENGTH = 16; // 11 throttle bits + 1 telemetry bit + 4 CRC bits
static constexpr uint16_t DSHOT_BITS_PER_FRAME = 16;
static constexpr uint16_t DSHOT_THROTTLE_MAX = 2047; // Maximum throttle value (0-2047)
static constexpr uint16_t DSHOT_THROTTLE_MIN = 48; // Minimum throttle value for motor spin
static constexpr uint16_t DSHOT_CMD_MIN = 0; // Minimum command value
static constexpr uint16_t DSHOT_CMD_MAX = 47; // Maximum command value
static constexpr uint16_t DSHOT_TELEMETRY_BIT_MASK = 0x0800; // Bit mask for telemetry request bit (11th bit)
static constexpr uint16_t DSHOT_CRC_MASK = 0x000F; // Bit mask for CRC bits
// Default motor magnet count for RPM calculation
static constexpr uint16_t DEFAULT_MOTOR_MAGNET_COUNT = 14;
// Defines the available DShot communication speeds. // Defines the available DShot communication speeds.
enum dshot_mode_t enum dshot_mode_t
@ -13,8 +31,7 @@ enum dshot_mode_t
DSHOT150, DSHOT150,
DSHOT300, DSHOT300,
DSHOT600, DSHOT600,
DSHOT1200, DSHOT1200
DSHOT_MODE_MAX
}; };
// Represents the 16-bit DShot data packet sent to the ESC. // Represents the 16-bit DShot data packet sent to the ESC.
@ -43,7 +60,7 @@ typedef struct rmt_ticks
} rmt_ticks_t; } rmt_ticks_t;
// Enum class for specific error and success codes // Enum class for specific error and success codes
enum class dshot_msg_code_t enum dshot_msg_code_t
{ {
DSHOT_NONE = 0, DSHOT_NONE = 0,
DSHOT_UNKNOWN, DSHOT_UNKNOWN,
@ -110,17 +127,9 @@ enum dshotCommands_e
DSHOT_CMD_LED2_OFF, DSHOT_CMD_LED2_OFF,
DSHOT_CMD_LED3_OFF, DSHOT_CMD_LED3_OFF,
DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30, DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30,
DSHOT_CMD_SILENT_MODE_ON_OFF = 31, DSHOT_CMD_SILENT_MODE_ON_OFF = 31
DSHOT_CMD_MAX = 47
}; };
// DShot Protocol Constants
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 // Custom status codes
static constexpr int DSHOT_OK = 0; static constexpr int DSHOT_OK = 0;
static constexpr int DSHOT_ERROR = 1; static constexpr int DSHOT_ERROR = 1;
@ -128,7 +137,6 @@ static constexpr int DSHOT_ERROR = 1;
// Configuration Constants // Configuration Constants
static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
static constexpr auto DSHOT_FULL_PACKET = 0b1111111111111111; static constexpr auto DSHOT_FULL_PACKET = 0b1111111111111111;
static constexpr auto DSHOT_CRC_MASK = 0b0000000000001111;
static constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT; static constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
static constexpr auto DSHOT_RMT_RESOLUTION = 8000000; // 8 MHz resolution static constexpr auto DSHOT_RMT_RESOLUTION = 8000000; // 8 MHz resolution
static constexpr auto RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / 1000000; // RMT Ticks per microsecond static constexpr auto RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / 1000000; // RMT Ticks per microsecond
@ -158,93 +166,5 @@ const dshot_timing_us_t DSHOT_TIMING_US[] = {
{6.67, 5.00}, // DSHOT150 {6.67, 5.00}, // DSHOT150
{3.33, 2.50}, // DSHOT300 {3.33, 2.50}, // DSHOT300
{1.67, 1.25}, // DSHOT600 {1.67, 1.25}, // DSHOT600
{0.83, 0.67}, // DSHOT1200 {0.83, 0.67} // DSHOT1200
{0.00, 0.00} // DSHOT_MODE_MAX (dummy entry) };
};
// Error Messages
static constexpr char NONE[] = "";
static constexpr char UNKNOWN_ERROR[] = "Unknown Error!";
static constexpr char INIT_SUCCESS[] = "SignalGeneratorRMT initialized successfully";
static constexpr char INIT_FAILED[] = "SignalGeneratorRMT init failed!";
static constexpr char TX_INIT_SUCCESS[] = "TX RMT channel initialized successfully";
static constexpr char TX_INIT_FAILED[] = "TX RMT channel init failed!";
static constexpr char RX_INIT_SUCCESS[] = "RX RMT channel initialized successfully";
static constexpr char RX_INIT_FAILED[] = "RX RMT channel init failed!";
static constexpr char ENCODER_INIT_SUCCESS[] = "RMT encoder initialized successfully";
static constexpr char ENCODER_INIT_FAILED[] = "RMT encoder init failed!";
static constexpr char ENCODING_SUCCESS[] = "Packet encoded successfully";
static constexpr char TRANSMISSION_SUCCESS[] = "Transmission successfully";
static constexpr char TRANSMISSION_FAILED[] = "Transmission failed!";
static constexpr char RECEIVER_FAILED[] = "RMT receiver failed!";
static constexpr char THROTTLE_NOT_IN_RANGE[] = "Throttle not in range! (48 - 2047)";
static constexpr char PERCENT_NOT_IN_RANGE[] = "Percent not in range! (0.0 - 100.0)";
static constexpr char COMMAND_NOT_VALID[] = "Command not valid! (0 - 47)";
static constexpr char BIDIR_NOT_ENABLED[] = "Bidirectional DShot not enabled!";
static constexpr char TELEMETRY_SUCCESS[] = "Valid Telemetric Frame received!";
static constexpr char TELEMETRY_FAILED[] = "No valid Telemetric Frame received!";
static constexpr char INVALID_MAGNET_COUNT[] = "Invalid motor magnet count!";
static constexpr char TIMING_CORRECTION[] = "Timing correction!";
static constexpr char CALLBACK_REGISTERING_FAILED[] = "RMT RX Callback registering failed!";
static constexpr char INVALID_COMMAND[] = "Invalid command!";
static constexpr char COMMAND_SUCCESS[] = "DShot command sent successfully";
// Helper to get result code string
inline const char *_get_result_code_str(dshot_msg_code_t code)
{
switch (code)
{
case dshot_msg_code_t::DSHOT_NONE:
return NONE;
case dshot_msg_code_t::DSHOT_UNKNOWN:
return UNKNOWN_ERROR;
case dshot_msg_code_t::DSHOT_TX_INIT_FAILED:
return TX_INIT_FAILED;
case dshot_msg_code_t::DSHOT_RX_INIT_FAILED:
return RX_INIT_FAILED;
case dshot_msg_code_t::DSHOT_ENCODER_INIT_FAILED:
return ENCODER_INIT_FAILED;
case dshot_msg_code_t::DSHOT_CALLBACK_REGISTERING_FAILED:
return CALLBACK_REGISTERING_FAILED;
case dshot_msg_code_t::DSHOT_RECEIVER_FAILED:
return RECEIVER_FAILED;
case dshot_msg_code_t::DSHOT_TRANSMISSION_FAILED:
return TRANSMISSION_FAILED;
case dshot_msg_code_t::DSHOT_THROTTLE_NOT_IN_RANGE:
return THROTTLE_NOT_IN_RANGE;
case dshot_msg_code_t::DSHOT_PERCENT_NOT_IN_RANGE:
return PERCENT_NOT_IN_RANGE;
case dshot_msg_code_t::DSHOT_COMMAND_NOT_VALID:
return COMMAND_NOT_VALID;
case dshot_msg_code_t::DSHOT_BIDIR_NOT_ENABLED:
return BIDIR_NOT_ENABLED;
case dshot_msg_code_t::DSHOT_TELEMETRY_FAILED:
return TELEMETRY_FAILED;
case dshot_msg_code_t::DSHOT_INVALID_MAGNET_COUNT:
return INVALID_MAGNET_COUNT;
case dshot_msg_code_t::DSHOT_INVALID_COMMAND:
return INVALID_COMMAND;
case dshot_msg_code_t::DSHOT_TIMING_CORRECTION:
return TIMING_CORRECTION;
case dshot_msg_code_t::DSHOT_INIT_FAILED:
return INIT_FAILED;
case dshot_msg_code_t::DSHOT_INIT_SUCCESS:
return INIT_SUCCESS;
case dshot_msg_code_t::DSHOT_TX_INIT_SUCCESS:
return TX_INIT_SUCCESS;
case dshot_msg_code_t::DSHOT_RX_INIT_SUCCESS:
return RX_INIT_SUCCESS;
case dshot_msg_code_t::DSHOT_ENCODER_INIT_SUCCESS:
return ENCODER_INIT_SUCCESS;
case dshot_msg_code_t::DSHOT_ENCODING_SUCCESS:
return ENCODING_SUCCESS;
case dshot_msg_code_t::DSHOT_TRANSMISSION_SUCCESS:
return TRANSMISSION_SUCCESS;
case dshot_msg_code_t::DSHOT_TELEMETRY_SUCCESS:
return TELEMETRY_SUCCESS;
case dshot_msg_code_t::DSHOT_COMMAND_SUCCESS:
return COMMAND_SUCCESS;
default:
return UNKNOWN_ERROR;
}
}

107
src/dshot_init.cpp Normal file
View File

@ -0,0 +1,107 @@
/**
* @file dshot_init.cpp
* @brief RMT configuration and initialization functions for DShotRMT library
* @author Wastl Kraus
* @date 2025-10-04
* @license MIT
*/
#include "dshot_init.h"
// Function to initialize the RMT TX channel
dshot_result_t init_rmt_tx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, bool is_bidirectional)
{
rmt_tx_channel_config_t tx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_BUFFER_SYMBOLS,
.trans_queue_depth = RMT_QUEUE_DEPTH,
};
rmt_transmit_config_t rmt_tx_config = {}; // Initialize all members to zero
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, out_channel) != DSHOT_OK)
{
return {false, DSHOT_TX_INIT_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK)
{
return {false, DSHOT_TX_INIT_FAILED};
}
return {true, DSHOT_TX_INIT_SUCCESS};
}
// Function to initialize the RMT RX channel
dshot_result_t init_rmt_rx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, rmt_rx_event_callbacks_t *rx_event_callbacks, void *user_data)
{
rmt_rx_channel_config_t rx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_BUFFER_SYMBOLS,
};
if (rmt_new_rx_channel(&rx_channel_config, out_channel) != DSHOT_OK)
{
return {false, DSHOT_RX_INIT_FAILED};
}
if (rmt_rx_register_event_callbacks(*out_channel, rx_event_callbacks, user_data) != DSHOT_OK)
{
return {false, DSHOT_CALLBACK_REGISTERING_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK)
{
return {false, DSHOT_RX_INIT_FAILED};
}
// Start the receiver to wait for incoming telemetry data
rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME];
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
rmt_receive_config_t rmt_rx_config = {
.signal_range_min_ns = DSHOT_PULSE_MIN_NS,
.signal_range_max_ns = DSHOT_PULSE_MAX_NS,
};
if (rmt_receive(*out_channel, rx_symbols, rx_size_bytes, &rmt_rx_config) != DSHOT_OK)
{
return {false, DSHOT_RECEIVER_FAILED};
}
return {true, DSHOT_RX_INIT_SUCCESS};
}
// Function to initialize the DShot RMT encoder
dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder, const rmt_ticks_t &rmt_ticks, uint16_t pulse_level, uint16_t idle_level)
{
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, out_encoder) != DSHOT_OK)
{
return {false, DSHOT_ENCODER_INIT_FAILED};
}
return {true, DSHOT_ENCODER_INIT_SUCCESS};
}

23
src/dshot_init.h Normal file
View File

@ -0,0 +1,23 @@
/**
* @file dshot_init.h
* @brief RMT configuration and initialization function declarations for DShotRMT library
* @author Wastl Kraus
* @date 2025-10-04
* @license MIT
*/
#pragma once
#include <driver/rmt_rx.h>
#include <driver/rmt_tx.h>
#include "dshot_definitions.h"
// Function to initialize the RMT TX channel
dshot_result_t init_rmt_tx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, bool is_bidirectional);
// Function to initialize the RMT RX channel
dshot_result_t init_rmt_rx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, rmt_rx_event_callbacks_t *rx_event_callbacks, void *user_data);
// Function to initialize the DShot RMT encoder
dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder, const rmt_ticks_t &rmt_ticks, uint16_t pulse_level, uint16_t idle_level);

View File

@ -1,12 +1,109 @@
/**
* @file dshot_utils.h
* @brief Utility functions for DShotRMT library
* @author Wastl Kraus
* @date 2025-10-04
* @license MIT
*/
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include "DShotRMT.h" // Include the full class definition
// Forward declaration of the DShotRMT class to break circular dependency
class DShotRMT;
// Error Messages
static constexpr char NONE[] = "";
static constexpr char UNKNOWN_ERROR[] = "Unknown Error!";
static constexpr char INIT_SUCCESS[] = "SignalGeneratorRMT initialized successfully";
static constexpr char INIT_FAILED[] = "SignalGeneratorRMT init failed!";
static constexpr char TX_INIT_SUCCESS[] = "TX RMT channel initialized successfully";
static constexpr char TX_INIT_FAILED[] = "TX RMT channel init failed!";
static constexpr char RX_INIT_SUCCESS[] = "RX RMT channel initialized successfully";
static constexpr char RX_INIT_FAILED[] = "RX RMT channel init failed!";
static constexpr char ENCODER_INIT_SUCCESS[] = "RMT encoder initialized successfully";
static constexpr char ENCODER_INIT_FAILED[] = "RMT encoder init failed!";
static constexpr char ENCODING_SUCCESS[] = "Packet encoded successfully";
static constexpr char TRANSMISSION_SUCCESS[] = "Transmission successfully";
static constexpr char TRANSMISSION_FAILED[] = "Transmission failed!";
static constexpr char RECEIVER_FAILED[] = "RMT receiver failed!";
static constexpr char THROTTLE_NOT_IN_RANGE[] = "Throttle not in range! (48 - 2047)";
static constexpr char PERCENT_NOT_IN_RANGE[] = "Percent not in range! (0.0 - 100.0)";
static constexpr char COMMAND_NOT_VALID[] = "Command not valid! (0 - 47)";
static constexpr char BIDIR_NOT_ENABLED[] = "Bidirectional DShot not enabled!";
static constexpr char TELEMETRY_SUCCESS[] = "Valid Telemetric Frame received!";
static constexpr char TELEMETRY_FAILED[] = "No valid Telemetric Frame received!";
static constexpr char INVALID_MAGNET_COUNT[] = "Invalid motor magnet count!";
static constexpr char TIMING_CORRECTION[] = "Timing correction!";
static constexpr char CALLBACK_REGISTERING_FAILED[] = "RMT RX Callback registering failed!";
static constexpr char INVALID_COMMAND[] = "Invalid command!";
static constexpr char COMMAND_SUCCESS[] = "DShot command sent successfully";
// Helper to get result code string
inline const char *get_result_code_str(dshot_msg_code_t code)
{
switch (code)
{
case DSHOT_NONE:
return NONE;
case DSHOT_UNKNOWN:
return UNKNOWN_ERROR;
case DSHOT_TX_INIT_FAILED:
return TX_INIT_FAILED;
case DSHOT_RX_INIT_FAILED:
return RX_INIT_FAILED;
case DSHOT_ENCODER_INIT_FAILED:
return ENCODER_INIT_FAILED;
case DSHOT_CALLBACK_REGISTERING_FAILED:
return CALLBACK_REGISTERING_FAILED;
case DSHOT_RECEIVER_FAILED:
return RECEIVER_FAILED;
case DSHOT_TRANSMISSION_FAILED:
return TRANSMISSION_FAILED;
case DSHOT_THROTTLE_NOT_IN_RANGE:
return THROTTLE_NOT_IN_RANGE;
case DSHOT_PERCENT_NOT_IN_RANGE:
return PERCENT_NOT_IN_RANGE;
case DSHOT_COMMAND_NOT_VALID:
return COMMAND_NOT_VALID;
case DSHOT_BIDIR_NOT_ENABLED:
return BIDIR_NOT_ENABLED;
case DSHOT_TELEMETRY_FAILED:
return TELEMETRY_FAILED;
case DSHOT_INVALID_MAGNET_COUNT:
return INVALID_MAGNET_COUNT;
case DSHOT_INVALID_COMMAND:
return INVALID_COMMAND;
case DSHOT_TIMING_CORRECTION:
return TIMING_CORRECTION;
case DSHOT_INIT_FAILED:
return INIT_FAILED;
case DSHOT_INIT_SUCCESS:
return INIT_SUCCESS;
case DSHOT_TX_INIT_SUCCESS:
return TX_INIT_SUCCESS;
case DSHOT_RX_INIT_SUCCESS:
return RX_INIT_SUCCESS;
case DSHOT_ENCODER_INIT_SUCCESS:
return ENCODER_INIT_SUCCESS;
case DSHOT_ENCODING_SUCCESS:
return ENCODING_SUCCESS;
case DSHOT_TRANSMISSION_SUCCESS:
return TRANSMISSION_SUCCESS;
case DSHOT_TELEMETRY_SUCCESS:
return TELEMETRY_SUCCESS;
case DSHOT_COMMAND_SUCCESS:
return COMMAND_SUCCESS;
default:
return UNKNOWN_ERROR;
}
}
// Helper to quick print DShot result codes // Helper to quick print DShot result codes
inline void printDShotResult(dshot_result_t &result, Stream &output = Serial) inline void printDShotResult(dshot_result_t &result, Stream &output = Serial)
{ {
output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", _get_result_code_str(result.result_code)); output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", get_result_code_str(result.result_code));
// Print telemetry data if available // Print telemetry data if available
if (result.success && (result.erpm > 0 || result.motor_rpm > 0)) if (result.success && (result.erpm > 0 || result.motor_rpm > 0))

View File

@ -2,7 +2,7 @@
* @file ota_update.h * @file ota_update.h
* @brief Contains the HTML, CSS, and JavaScript for the OTA (Over-The-Air) update web page. * @brief Contains the HTML, CSS, and JavaScript for the OTA (Over-The-Air) update web page.
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-13 * @date 2025-10-04
* @license MIT * @license MIT
*/ */

View File

@ -2,7 +2,7 @@
* @file web_content.h * @file web_content.h
* @brief Contains the HTML, CSS, and JavaScript for the main DShot control web page. * @brief Contains the HTML, CSS, and JavaScript for the main DShot control web page.
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-13 * @date 2025-10-04
* @license MIT * @license MIT
*/ */