commit
536a030dfc
|
|
@ -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 }}"
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,8 @@
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
#include "src/DShotRMT.h"
|
#include "src/DShotRMT.h"
|
||||||
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -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
|
||||||
|
|
|
||||||
33
README.md
33
README.md
|
|
@ -1,11 +1,15 @@
|
||||||
# DShotRMT - ESP32 RMT DShot Driver
|
# DShotRMT - ESP32 RMT DShot Driver
|
||||||
|
|
||||||
[](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml)
|
[](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml)
|
||||||
|
[](https://www.arduinolibraries.com/libraries/dshot-rmt)
|
||||||
|
[](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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
278
src/DShotRMT.cpp
278
src/DShotRMT.cpp
|
|
@ -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,22 +33,18 @@ 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
|
||||||
if (_dshot_encoder)
|
if (_dshot_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_INIT_SUCCESS};
|
||||||
}
|
|
||||||
|
|
||||||
return {true, dshot_msg_code_t::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
178
src/DShotRMT.h
178
src/DShotRMT.h
|
|
@ -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"
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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};
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Loading…
Reference in New Issue