commit
536a030dfc
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
~/.arduino15/packages
|
||||
~/.arduino15/cache
|
||||
~/Arduino/libraries
|
||||
key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }}
|
||||
key: linux-arduino-esp32-v2-v3
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.cache-arduino.outputs.cache-hit != 'true'
|
||||
|
|
@ -83,7 +83,7 @@ jobs:
|
|||
~/.arduino15/packages
|
||||
~/.arduino15/cache
|
||||
~/Arduino/libraries
|
||||
key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }}
|
||||
key: linux-arduino-esp32-v2-v3
|
||||
|
||||
- name: Run Arduino Lint
|
||||
uses: arduino/arduino-lint-action@v2
|
||||
|
|
@ -125,7 +125,7 @@ jobs:
|
|||
~/.arduino15/packages
|
||||
~/.arduino15/cache
|
||||
~/Arduino/libraries
|
||||
key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }}
|
||||
key: linux-arduino-esp32-v2-v3
|
||||
|
||||
- name: Compile Sketch
|
||||
run: arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} "${{ matrix.sketch }}"
|
||||
|
|
|
|||
|
|
@ -6,5 +6,8 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "src/DShotRMT.h"
|
||||
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Wastl Kraus
|
||||
Copyright (c) 2023 derdoktor667
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
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
|
||||
|
||||
[](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.
|
||||
|
||||
---
|
||||
|
||||
### DShot300 Example Output
|
||||
|
||||
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
|
||||
|
||||
- **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.
|
||||
- **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`.
|
||||
|
|
@ -43,11 +47,11 @@ The DShot protocol defines specific timing characteristics for each mode. The fo
|
|||
|
||||
## ⚡ 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
|
||||
#include <Arduino.h>
|
||||
#include <DShotRMT.h> // Include the DShotRMT library
|
||||
#include <DShotRMT.h>
|
||||
|
||||
// Define the GPIO pin connected to the motor ESC
|
||||
const gpio_num_t MOTOR_PIN = GPIO_NUM_27;
|
||||
|
|
@ -65,14 +69,13 @@ void setup() {
|
|||
printCpuInfo(Serial);
|
||||
|
||||
Serial.println("Motor initialized. Ramping up to 25% throttle...");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Ramp up to 25% throttle over 2.5 seconds
|
||||
for (int i = 0; i <= 25; i++) {
|
||||
motor.sendThrottlePercent(i);
|
||||
delay(100);
|
||||
delay(200);
|
||||
}
|
||||
|
||||
Serial.println("Stopping motor.");
|
||||
|
|
@ -80,6 +83,9 @@ void loop() {
|
|||
|
||||
// Print DShot Info
|
||||
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.
|
||||
- `sendThrottlePercent(float percent)`: Sends a throttle value as a percentage (0.0-100.0) to the ESC.
|
||||
- `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the ESC. A value of 0 sends a motor stop command.
|
||||
- `sendCommand(uint16_t command)`: Sends a single DShot command (0-47) to the ESC.
|
||||
- `sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US)`: Sends a DShot command multiple times with a delay between repetitions. This is a blocking function.
|
||||
- `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 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.
|
||||
- `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.
|
||||
|
|
@ -123,6 +130,14 @@ The main class is `DShotRMT`. Here are the most important methods:
|
|||
- `getEncodedFrameValue()`: Gets the last encoded DShot frame 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
|
||||
|
||||
Contributions are welcome! Please fork the repository, create a feature branch, and submit a pull request.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @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
|
||||
* @date 2025-09-20
|
||||
* @license MIT
|
||||
|
|
@ -28,7 +28,7 @@ static constexpr auto IS_BIDIRECTIONAL = false;
|
|||
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
|
||||
|
||||
// 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
|
||||
void handleSerialInput(const String &input);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @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
|
||||
* @date 2025-09-11
|
||||
* @license MIT
|
||||
|
|
@ -16,12 +16,11 @@
|
|||
******************************************************************/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Update.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <DShotRMT.h>
|
||||
#include <ota_update.h>
|
||||
#include <web_content.h>
|
||||
#include <WiFi.h>
|
||||
#include <Update.h>
|
||||
#include "web_utilities/ota_update.h"
|
||||
#include "web_utilities/web_content.h"
|
||||
|
||||
#include <ArduinoJson.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;
|
||||
|
||||
// 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
|
||||
AsyncWebServer server(80);
|
||||
|
|
@ -177,7 +176,7 @@ void loop()
|
|||
// Get Motor RPM if bidirectional and armed
|
||||
if (IS_BIDIRECTIONAL && isArmed)
|
||||
{
|
||||
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||
dshot_result_t telem_result = motor01.getTelemetry();
|
||||
printDShotResult(telem_result);
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +193,7 @@ void loop()
|
|||
|
||||
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)
|
||||
{
|
||||
current_rpm = String(telem_result.motor_rpm);
|
||||
|
|
@ -254,7 +253,8 @@ void setupOTA()
|
|||
ESP.restart();
|
||||
} else {
|
||||
USB_SERIAL.println("OTA Update failed!");
|
||||
} }, handleOTAUpload);
|
||||
}
|
||||
}, handleOTAUpload);
|
||||
|
||||
USB_SERIAL.println("OTA Update ready at: /update");
|
||||
}
|
||||
|
|
@ -344,7 +344,7 @@ void printWiFiStatus()
|
|||
{
|
||||
USB_SERIAL.println();
|
||||
USB_SERIAL.println("***********************************************");
|
||||
USB_SERIAL.println(" --- WIFI INFO --- ");
|
||||
USB_SERIAL.println(" --- WIFI INFO ---");
|
||||
USB_SERIAL.println("***********************************************");
|
||||
USB_SERIAL.printf("SSID: %s\n", WiFi.SSID().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(" --- DShotRMT Web Client Demo --- ");
|
||||
USB_SERIAL.println(" --- DShotRMT Web Client Demo ---");
|
||||
USB_SERIAL.println("***********************************************");
|
||||
|
||||
if (wifi_connected)
|
||||
|
|
@ -494,7 +494,7 @@ void handleSerialInput(const String &input)
|
|||
{
|
||||
if (isArmed)
|
||||
{
|
||||
dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||
dshot_result_t result = motor01.getTelemetry();
|
||||
printDShotResult(result);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @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
|
||||
* @date 2025-09-09
|
||||
* @license MIT
|
||||
|
|
@ -16,10 +16,10 @@
|
|||
******************************************************************/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <DShotRMT.h>
|
||||
#include <web_content.h>
|
||||
#include <WiFi.h>
|
||||
#include <Update.h>
|
||||
#include "web_utilities/web_content.h"
|
||||
|
||||
#include <ArduinoJson.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;
|
||||
|
||||
// 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
|
||||
AsyncWebServer server(80);
|
||||
|
|
@ -145,7 +145,7 @@ void loop()
|
|||
// Get Motor RPM if bidirectional and armed
|
||||
if (IS_BIDIRECTIONAL && isArmed)
|
||||
{
|
||||
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||
dshot_result_t telem_result = motor01.getTelemetry();
|
||||
printDShotResult(telem_result);
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ void loop()
|
|||
|
||||
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)
|
||||
{
|
||||
current_rpm = String(telem_result.motor_rpm);
|
||||
|
|
@ -263,7 +263,7 @@ void handleSerialInput(const String &input)
|
|||
{
|
||||
if (isArmed)
|
||||
{
|
||||
dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||
dshot_result_t result = motor01.getTelemetry();
|
||||
printDShotResult(result);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name=DShotRMT
|
||||
version=0.8.9
|
||||
version=0.9.0
|
||||
author=Wastl Kraus <wir-sind-die-matrix.de>
|
||||
maintainer=Wastl Kraus <wir-sind-die-matrix.de>
|
||||
license=MIT
|
||||
|
|
|
|||
278
src/DShotRMT.cpp
278
src/DShotRMT.cpp
|
|
@ -6,7 +6,7 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
#include <DShotRMT.h>
|
||||
#include "DShotRMT.h"
|
||||
|
||||
// Constructor with GPIO number
|
||||
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),
|
||||
_is_bidirectional(is_bidirectional),
|
||||
_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
|
||||
_preCalculateRMTTicks();
|
||||
|
|
@ -33,22 +33,18 @@ DShotRMT::~DShotRMT()
|
|||
// Cleanup 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_tx_channel = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup 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_rx_channel = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup encoder
|
||||
if (_dshot_encoder)
|
||||
|
|
@ -61,41 +57,33 @@ DShotRMT::~DShotRMT()
|
|||
// Initialize DShotRMT
|
||||
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 (!_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
|
||||
rmt_disable(_rmt_tx_channel);
|
||||
rmt_del_channel(_rmt_tx_channel);
|
||||
_rmt_tx_channel = nullptr;
|
||||
return {false, dshot_msg_code_t::DSHOT_RX_INIT_FAILED};
|
||||
_cleanupRmtResources(); // Clean up any allocated resources on failure
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_initDShotEncoder().success)
|
||||
result = init_dshot_encoder(&_dshot_encoder, _rmt_ticks, _pulse_level, _idle_level);
|
||||
|
||||
if (!result.success)
|
||||
{
|
||||
// Cleanup previously allocated channels on failure
|
||||
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;
|
||||
_cleanupRmtResources(); // Clean up any allocated resources on failure
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -104,7 +92,7 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
|
|||
// A throttle value of 0 is a disarm command
|
||||
if (throttle == 0)
|
||||
{
|
||||
return sendCommand(dshotCommands_e::DSHOT_CMD_MOTOR_STOP);
|
||||
return sendCommand(DSHOT_CMD_MOTOR_STOP);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return {false, dshot_msg_code_t::DSHOT_PERCENT_NOT_IN_RANGE};
|
||||
return {false, DSHOT_PERCENT_NOT_IN_RANGE};
|
||||
}
|
||||
|
||||
// Map percent to DShot throttle range
|
||||
|
|
@ -127,23 +115,47 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
|
|||
return sendThrottle(throttle);
|
||||
}
|
||||
|
||||
// Send DShot command to ESC
|
||||
dshot_result_t DShotRMT::sendCommand(dshotCommands_e command)
|
||||
// Sends a DShot command (0-47) to the ESC by accepting an integer value.
|
||||
dshot_result_t DShotRMT::sendCommand(uint16_t command_value)
|
||||
{
|
||||
_packet = _buildDShotPacket(static_cast<uint16_t>(command));
|
||||
return _sendDShotFrame(_packet);
|
||||
// Validate the integer command value before casting
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
// Send full DShot commands for setup etc
|
||||
// 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)
|
||||
// Sends a DShot command (0-47) to the ESC.
|
||||
dshot_result_t DShotRMT::sendCommand(dshotCommands_e command)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +164,7 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin
|
|||
// Send command multiple times with delay
|
||||
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)
|
||||
{
|
||||
|
|
@ -168,12 +180,11 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
result.success = all_successful;
|
||||
|
||||
if (result.success)
|
||||
{
|
||||
result.result_code = dshot_msg_code_t::DSHOT_COMMAND_SUCCESS;
|
||||
result.result_code = DSHOT_COMMAND_SUCCESS;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -182,11 +193,11 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin
|
|||
// Get telemetry data
|
||||
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)
|
||||
{
|
||||
result.result_code = dshot_msg_code_t::DSHOT_BIDIR_NOT_ENABLED;
|
||||
result.result_code = DSHOT_BIDIR_NOT_ENABLED;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +219,7 @@ dshot_result_t DShotRMT::getTelemetry()
|
|||
result.success = true;
|
||||
result.erpm = erpm;
|
||||
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
|
||||
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
|
||||
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
|
||||
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)
|
||||
{
|
||||
uint64_t start_time = esp_timer_get_time();
|
||||
|
|
@ -252,104 +261,6 @@ dshot_result_t DShotRMT::_executeCommand(dshotCommands_e command)
|
|||
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
|
||||
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()
|
||||
{
|
||||
// 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.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.t0l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t0h_ticks;
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
_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)
|
||||
{
|
||||
_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
|
||||
if (!_isFrameIntervalElapsed())
|
||||
{
|
||||
return {true, dshot_msg_code_t::DSHOT_NONE};
|
||||
return {true, DSHOT_NONE};
|
||||
}
|
||||
|
||||
_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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
{
|
||||
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.
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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_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))
|
||||
{
|
||||
return DSHOT_NULL_PACKET;
|
||||
}
|
||||
|
||||
// Step 6: Calculate and validate CRC
|
||||
// Calculate and validate CRC.
|
||||
uint16_t calculated_crc = _calculateCRC(received_data);
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
* @brief Optimized DShot signal generation using ESP32 RMT with bidirectional support
|
||||
* @brief DShot signal generation using ESP32 RMT with bidirectional support
|
||||
* @author Wastl Kraus
|
||||
* @date 2025-09-18
|
||||
* @date 2025-06-11
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <driver/rmt_tx.h>
|
||||
#include <driver/rmt_rx.h>
|
||||
#include <atomic>
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rmt_rx.h>
|
||||
#include <driver/rmt_tx.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
|
||||
{
|
||||
public:
|
||||
// Constructor for DShotRMT with GPIO number.
|
||||
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
||||
// Constructor for DShotRMT.
|
||||
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.
|
||||
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
||||
// Constructor using pin number
|
||||
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
||||
|
||||
// Destructor
|
||||
~DShotRMT();
|
||||
|
||||
// Public Core Functions
|
||||
// Initializes the DShot RMT channels and encoder.
|
||||
// Initialize DShotRMT
|
||||
dshot_result_t begin();
|
||||
|
||||
// Sends a throttle value as a percentage (0.0-100.0) 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.
|
||||
// Sends a raw throttle value to the ESC.
|
||||
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);
|
||||
|
||||
// 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.
|
||||
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);
|
||||
|
||||
// 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();
|
||||
|
||||
// Public Utility & Info Functions
|
||||
// Sets the motor magnet count for RPM calculation.
|
||||
void setMotorMagnetCount(uint16_t magnet_count);
|
||||
|
||||
// Gets the current DShot mode.
|
||||
// Getters for DShot info
|
||||
dshot_mode_t getMode() const { return _mode; }
|
||||
|
||||
// Checks if bidirectional DShot is enabled.
|
||||
bool isBidirectional() const { return _is_bidirectional; }
|
||||
|
||||
// Gets the encoded frame value.
|
||||
uint16_t getThrottleValue() const { return _last_throttle; }
|
||||
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:
|
||||
// --- UTILITY METHODS ---
|
||||
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);
|
||||
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);
|
||||
|
||||
// Core Configuration Variables
|
||||
gpio_num_t _gpio;
|
||||
dshot_mode_t _mode;
|
||||
bool _is_bidirectional;
|
||||
uint16_t _motor_magnet_count;
|
||||
const dshot_timing_us_t &_dshot_timing;
|
||||
uint64_t _frame_timer_us = 0;
|
||||
// DShot Configuration Parameters
|
||||
gpio_num_t _gpio; // GPIO pin used for DShot communication
|
||||
dshot_mode_t _mode; // DShot mode (e.g., DSHOT300, DSHOT600)
|
||||
bool _is_bidirectional; // True if bidirectional DShot is enabled
|
||||
uint16_t _motor_magnet_count; // Number of magnets in the motor for RPM calculation
|
||||
dshot_timing_us_t _dshot_timing; // DShot timing parameters in microseconds
|
||||
|
||||
// Timing & Packet Variables
|
||||
rmt_ticks_t _rmt_ticks{};
|
||||
uint16_t _last_throttle = dshotCommands_e::DSHOT_CMD_MOTOR_STOP;
|
||||
uint64_t _last_transmission_time_us = 0;
|
||||
uint64_t _last_command_timestamp = 0;
|
||||
uint16_t _encoded_frame_value = 0;
|
||||
dshot_packet_t _packet{};
|
||||
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 and Configuration
|
||||
rmt_channel_handle_t _rmt_tx_channel = nullptr; // RMT transmit channel handle
|
||||
rmt_channel_handle_t _rmt_rx_channel = nullptr; // RMT receive channel handle
|
||||
rmt_encoder_handle_t _dshot_encoder = nullptr; // DShot RMT encoder handle
|
||||
rmt_ticks_t _rmt_ticks; // Pre-calculated RMT timing ticks
|
||||
uint16_t _pulse_level = 1; // Output level for a pulse (typically high)
|
||||
uint16_t _idle_level = 0; // Output level for idle (typically low)
|
||||
|
||||
// RMT Hardware Handles
|
||||
rmt_channel_handle_t _rmt_tx_channel = nullptr;
|
||||
rmt_channel_handle_t _rmt_rx_channel = nullptr;
|
||||
rmt_encoder_handle_t _dshot_encoder = nullptr;
|
||||
// DShot Frame Timing and State Variables
|
||||
uint64_t _last_transmission_time_us = 0; // Timestamp of the last DShot frame transmission
|
||||
uint64_t _frame_timer_us = 0; // Minimum time required between DShot frames
|
||||
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
|
||||
rmt_tx_channel_config_t _tx_channel_config{};
|
||||
rmt_rx_channel_config_t _rx_channel_config{};
|
||||
rmt_transmit_config_t _rmt_tx_config{};
|
||||
rmt_receive_config_t _rmt_rx_config{};
|
||||
// Telemetry Related Variables
|
||||
std::atomic<uint16_t> _last_erpm_atomic = 0; // Atomically stored last received eRPM value
|
||||
std::atomic<bool> _telemetry_ready_flag_atomic = false; // Atomically stored flag indicating new telemetry data
|
||||
rmt_rx_event_callbacks_t _rx_event_callbacks = {
|
||||
// RMT receive event callbacks
|
||||
.on_recv_done = _on_rx_done,
|
||||
};
|
||||
|
||||
// Bidirectional / Telemetry Variables
|
||||
rmt_rx_event_callbacks_t _rx_event_callbacks{};
|
||||
std::atomic<uint16_t> _last_erpm_atomic{0};
|
||||
std::atomic<bool> _telemetry_ready_flag_atomic{false};
|
||||
// Private Helper Functions for DShot Protocol Logic
|
||||
bool _isValidCommand(dshotCommands_e command) const; // Checks if a given DShot command is valid
|
||||
dshot_result_t _executeCommand(dshotCommands_e command); // Executes a single DShot command
|
||||
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
|
||||
dshot_result_t _initTXChannel();
|
||||
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);
|
||||
// Static Callback Function for RMT RX Events
|
||||
void _cleanupRmtResources();
|
||||
};
|
||||
|
||||
// Include utility functions after class definition
|
||||
#include "dshot_utils.h"
|
||||
#include "dshot_utils.h" // Include for helper functions
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rmt_tx.h>
|
||||
#include <driver/rmt_rx.h>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <driver/rmt_common.h>
|
||||
|
||||
// DShot protocol definitions
|
||||
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.
|
||||
enum dshot_mode_t
|
||||
|
|
@ -13,8 +31,7 @@ enum dshot_mode_t
|
|||
DSHOT150,
|
||||
DSHOT300,
|
||||
DSHOT600,
|
||||
DSHOT1200,
|
||||
DSHOT_MODE_MAX
|
||||
DSHOT1200
|
||||
};
|
||||
|
||||
// Represents the 16-bit DShot data packet sent to the ESC.
|
||||
|
|
@ -43,7 +60,7 @@ typedef struct rmt_ticks
|
|||
} rmt_ticks_t;
|
||||
|
||||
// Enum class for specific error and success codes
|
||||
enum class dshot_msg_code_t
|
||||
enum dshot_msg_code_t
|
||||
{
|
||||
DSHOT_NONE = 0,
|
||||
DSHOT_UNKNOWN,
|
||||
|
|
@ -110,17 +127,9 @@ enum dshotCommands_e
|
|||
DSHOT_CMD_LED2_OFF,
|
||||
DSHOT_CMD_LED3_OFF,
|
||||
DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30,
|
||||
DSHOT_CMD_SILENT_MODE_ON_OFF = 31,
|
||||
DSHOT_CMD_MAX = 47
|
||||
DSHOT_CMD_SILENT_MODE_ON_OFF = 31
|
||||
};
|
||||
|
||||
// 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
|
||||
static constexpr int DSHOT_OK = 0;
|
||||
static constexpr int DSHOT_ERROR = 1;
|
||||
|
|
@ -128,7 +137,6 @@ static constexpr int DSHOT_ERROR = 1;
|
|||
// Configuration Constants
|
||||
static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
|
||||
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_RMT_RESOLUTION = 8000000; // 8 MHz resolution
|
||||
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
|
||||
{3.33, 2.50}, // DSHOT300
|
||||
{1.67, 1.25}, // DSHOT600
|
||||
{0.83, 0.67}, // DSHOT1200
|
||||
{0.00, 0.00} // DSHOT_MODE_MAX (dummy entry)
|
||||
{0.83, 0.67} // DSHOT1200
|
||||
};
|
||||
|
||||
// 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
|
||||
|
||||
#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
|
||||
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
|
||||
if (result.success && (result.erpm > 0 || result.motor_rpm > 0))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* @file ota_update.h
|
||||
* @brief Contains the HTML, CSS, and JavaScript for the OTA (Over-The-Air) update web page.
|
||||
* @author Wastl Kraus
|
||||
* @date 2025-09-13
|
||||
* @date 2025-10-04
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* @file web_content.h
|
||||
* @brief Contains the HTML, CSS, and JavaScript for the main DShot control web page.
|
||||
* @author Wastl Kraus
|
||||
* @date 2025-09-13
|
||||
* @date 2025-10-04
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue