release 0.9.0

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

View File

@ -46,7 +46,7 @@ jobs:
~/.arduino15/packages
~/.arduino15/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 }}"

View File

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

View File

@ -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

View File

@ -1,11 +1,15 @@
# DShotRMT - ESP32 RMT DShot Driver
[![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml/badge.svg)](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml)
[![Arduino Library](https://img.shields.io/badge/Arduino-Library-blue.svg)](https://www.arduinolibraries.com/libraries/dshot-rmt)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
An Arduino IDElibrary for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT encoder API** (`rmt_tx.h` / `rmt_rx.h`). This library specifically leverages the official `rmt_bytes_encoder` API for an efficient, hardware-timed, and maintainable implementation. It provides a simple way to control brushless motors in both Arduino and ESP-IDF projects.
An Arduino IDE library for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT Encoder API** (`rmt_tx.h` / `rmt_rx.h`). This library specifically leverages the official `rmt_bytes_encoder` API for an efficient, hardware-timed and maintainable implementation. It provides a simple way to control BLHeli ESCs in both Arduino and ESP-IDF projects.
The legacy version using the old `rmt.h` API is available in the `oldAPI` branch.
---
### 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.

View File

@ -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);

View File

@ -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");
}
@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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{};
// 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 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);
// 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,
};
// Include utility functions after class definition
#include "dshot_utils.h"
// 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
// Static Callback Function for RMT RX Events
void _cleanupRmtResources();
};
#include "dshot_utils.h" // Include for helper functions

View File

@ -1,10 +1,28 @@
/**
* @file dshot_definitions.h
* @brief Defines DShot protocol constants, data structures, and command enums for DShotRMT library
* @author Wastl Kraus
* @date 2025-10-04
* @license MIT
*/
#pragma once
#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;
}
}

107
src/dshot_init.cpp Normal file
View File

@ -0,0 +1,107 @@
/**
* @file dshot_init.cpp
* @brief RMT configuration and initialization functions for DShotRMT library
* @author Wastl Kraus
* @date 2025-10-04
* @license MIT
*/
#include "dshot_init.h"
// Function to initialize the RMT TX channel
dshot_result_t init_rmt_tx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, bool is_bidirectional)
{
rmt_tx_channel_config_t tx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_BUFFER_SYMBOLS,
.trans_queue_depth = RMT_QUEUE_DEPTH,
};
rmt_transmit_config_t rmt_tx_config = {}; // Initialize all members to zero
rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
rmt_tx_config.flags.eot_level = is_bidirectional ? 1 : 0;
if (rmt_new_tx_channel(&tx_channel_config, out_channel) != DSHOT_OK)
{
return {false, DSHOT_TX_INIT_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK)
{
return {false, DSHOT_TX_INIT_FAILED};
}
return {true, DSHOT_TX_INIT_SUCCESS};
}
// Function to initialize the RMT RX channel
dshot_result_t init_rmt_rx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, rmt_rx_event_callbacks_t *rx_event_callbacks, void *user_data)
{
rmt_rx_channel_config_t rx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_BUFFER_SYMBOLS,
};
if (rmt_new_rx_channel(&rx_channel_config, out_channel) != DSHOT_OK)
{
return {false, DSHOT_RX_INIT_FAILED};
}
if (rmt_rx_register_event_callbacks(*out_channel, rx_event_callbacks, user_data) != DSHOT_OK)
{
return {false, DSHOT_CALLBACK_REGISTERING_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK)
{
return {false, DSHOT_RX_INIT_FAILED};
}
// Start the receiver to wait for incoming telemetry data
rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME];
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
rmt_receive_config_t rmt_rx_config = {
.signal_range_min_ns = DSHOT_PULSE_MIN_NS,
.signal_range_max_ns = DSHOT_PULSE_MAX_NS,
};
if (rmt_receive(*out_channel, rx_symbols, rx_size_bytes, &rmt_rx_config) != DSHOT_OK)
{
return {false, DSHOT_RECEIVER_FAILED};
}
return {true, DSHOT_RX_INIT_SUCCESS};
}
// Function to initialize the DShot RMT encoder
dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder, const rmt_ticks_t &rmt_ticks, uint16_t pulse_level, uint16_t idle_level)
{
rmt_bytes_encoder_config_t encoder_config = {
.bit0 = {
.duration0 = rmt_ticks.t0h_ticks,
.level0 = pulse_level,
.duration1 = rmt_ticks.t0l_ticks,
.level1 = idle_level,
},
.bit1 = {
.duration0 = rmt_ticks.t1h_ticks,
.level0 = pulse_level,
.duration1 = rmt_ticks.t1l_ticks,
.level1 = idle_level,
},
.flags = {
.msb_first = 1 // DShot is MSB first
}};
if (rmt_new_bytes_encoder(&encoder_config, out_encoder) != DSHOT_OK)
{
return {false, DSHOT_ENCODER_INIT_FAILED};
}
return {true, DSHOT_ENCODER_INIT_SUCCESS};
}

23
src/dshot_init.h Normal file
View File

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

View File

@ -1,12 +1,109 @@
/**
* @file dshot_utils.h
* @brief Utility functions for DShotRMT library
* @author Wastl Kraus
* @date 2025-10-04
* @license MIT
*/
#pragma once
#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))

View File

@ -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
*/

View File

@ -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
*/