Rewrite ESP-IDF5
Now using "new" API (rmt_tx.h). "old" Version of this library is still available: Branch "oldAPI".
This commit is contained in:
parent
78bc06344c
commit
66482aeadb
338
DShotRMT.cpp
338
DShotRMT.cpp
|
|
@ -1,277 +1,113 @@
|
|||
//
|
||||
// Name: DShotRMT.cpp
|
||||
// Created: 20.03.2021 00:49:15
|
||||
// Author: derdoktor667
|
||||
//
|
||||
/**
|
||||
* @file DShotRMT.cpp
|
||||
* @brief Implementation of continuous DShot signal using ESP32 RMT encoder API with pause between frames
|
||||
* @author Wastl Kraus
|
||||
* @date 2025-06-11
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#include <DShotRMT.h>
|
||||
|
||||
// Constructor that takes gpio and rmtChannel as arguments
|
||||
DShotRMT::DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel)
|
||||
{
|
||||
// Initialize the dshot_config structure with the arguments passed to the constructor
|
||||
dshot_config.gpio_num = gpio;
|
||||
dshot_config.pin_num = static_cast<uint8_t>(gpio);
|
||||
dshot_config.rmt_channel = rmtChannel;
|
||||
dshot_config.mem_block_num = static_cast<uint8_t>(RMT_CHANNEL_MAX - static_cast<uint8_t>(rmtChannel));
|
||||
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional)
|
||||
: _gpio(gpio), _mode(mode), _isBidirectional(isBidirectional) {}
|
||||
|
||||
// Create an empty packet using the DSHOT_NULL_PACKET and the buildTxRmtItem function
|
||||
buildTxRmtItem(DSHOT_NULL_PACKET);
|
||||
void DShotRMT::begin()
|
||||
{
|
||||
rmt_tx_channel_config_t tx_config = {
|
||||
.gpio_num = _gpio,
|
||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = DEFAULT_RES_HZ,
|
||||
.mem_block_symbols = 64,
|
||||
.trans_queue_depth = 1,
|
||||
.flags = {
|
||||
.invert_out = _isBidirectional,
|
||||
.with_dma = false}};
|
||||
|
||||
rmt_new_tx_channel(&tx_config, &_channel);
|
||||
rmt_enable(_channel);
|
||||
|
||||
// Create encoder only once
|
||||
if (!_encoder)
|
||||
{
|
||||
rmt_copy_encoder_config_t enc_cfg = {};
|
||||
rmt_new_copy_encoder(&enc_cfg, &_encoder);
|
||||
}
|
||||
|
||||
_tx_config.loop_count = -1; // Infinite loop
|
||||
_tx_config.flags.eot_level = 0;
|
||||
}
|
||||
|
||||
// Constructor that takes pin and channel as arguments
|
||||
DShotRMT::DShotRMT(uint8_t pin, uint8_t channel)
|
||||
void DShotRMT::setThrottle(uint16_t throttle, bool telemetry)
|
||||
{
|
||||
// Initialize the dshot_config structure with the arguments passed to the constructor
|
||||
dshot_config.gpio_num = static_cast<gpio_num_t>(pin);
|
||||
dshot_config.pin_num = pin;
|
||||
dshot_config.rmt_channel = static_cast<rmt_channel_t>(channel);
|
||||
dshot_config.mem_block_num = RMT_CHANNEL_MAX - channel;
|
||||
// Clamp to 11 bits
|
||||
throttle &= 0x07FF;
|
||||
if (throttle == _lastThrottle && telemetry == _lastTelemetry)
|
||||
return;
|
||||
|
||||
// Create an empty packet using the DSHOT_NULL_PACKET and the buildTxRmtItem function
|
||||
buildTxRmtItem(DSHOT_NULL_PACKET);
|
||||
_lastThrottle = throttle;
|
||||
_lastTelemetry = telemetry;
|
||||
|
||||
// Build 16-bit DShot packet
|
||||
uint16_t packet = (throttle << 1) | (telemetry ? 1 : 0);
|
||||
uint8_t crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F;
|
||||
packet = (packet << 4) | crc;
|
||||
|
||||
// Build symbols
|
||||
rmt_symbol_word_t symbols[32] = {};
|
||||
size_t count = 0;
|
||||
|
||||
buildFrameSymbols(packet, symbols, count);
|
||||
|
||||
// Transmit
|
||||
rmt_disable(_channel); // Ensure safe restart
|
||||
rmt_enable(_channel);
|
||||
|
||||
rmt_transmit(_channel, _encoder, symbols, count * sizeof(rmt_symbol_word_t), &_tx_config);
|
||||
}
|
||||
|
||||
// ...simplest but only for testing
|
||||
DShotRMT::DShotRMT(uint8_t pin)
|
||||
void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count)
|
||||
{
|
||||
// Initialize the dshot_config structure with the arguments passed to the constructor
|
||||
dshot_config.gpio_num = static_cast<gpio_num_t>(pin);
|
||||
dshot_config.pin_num = pin;
|
||||
dshot_config.rmt_channel = static_cast<rmt_channel_t>(RMT_CHANNEL_MAX -1);
|
||||
dshot_config.mem_block_num = RMT_CHANNEL_MAX - 1;
|
||||
uint32_t ticks_per_bit = 0;
|
||||
uint32_t ticks_zero_high = 0;
|
||||
uint32_t ticks_one_high = 0;
|
||||
|
||||
// Create an empty packet using the DSHOT_NULL_PACKET and the buildTxRmtItem function
|
||||
buildTxRmtItem(DSHOT_NULL_PACKET);
|
||||
}
|
||||
|
||||
DShotRMT::~DShotRMT()
|
||||
{
|
||||
// Uninstall the RMT driver
|
||||
rmt_driver_uninstall(dshot_config.rmt_channel);
|
||||
}
|
||||
|
||||
DShotRMT::DShotRMT(DShotRMT const &)
|
||||
{
|
||||
// ...write me
|
||||
}
|
||||
|
||||
bool DShotRMT::begin(dshot_mode_t dshot_mode, bool is_bidirectional)
|
||||
{
|
||||
// Set DShot configuration parameters based on input parameters
|
||||
dshot_config.mode = dshot_mode;
|
||||
dshot_config.clk_div = DSHOT_CLK_DIVIDER;
|
||||
dshot_config.name_str = dshot_mode_name[dshot_mode];
|
||||
dshot_config.is_bidirectional = is_bidirectional;
|
||||
|
||||
// Set timing parameters based on selected DShot mode
|
||||
switch (dshot_config.mode)
|
||||
switch (_mode)
|
||||
{
|
||||
case DSHOT150:
|
||||
dshot_config.ticks_per_bit = 64;
|
||||
dshot_config.ticks_zero_high = 24;
|
||||
dshot_config.ticks_one_high = 48;
|
||||
ticks_per_bit = 67;
|
||||
ticks_zero_high = 25;
|
||||
ticks_one_high = 50;
|
||||
break;
|
||||
|
||||
case DSHOT300:
|
||||
dshot_config.ticks_per_bit = 32;
|
||||
dshot_config.ticks_zero_high = 12;
|
||||
dshot_config.ticks_one_high = 24;
|
||||
ticks_per_bit = 33;
|
||||
ticks_zero_high = 12;
|
||||
ticks_one_high = 25;
|
||||
break;
|
||||
|
||||
case DSHOT600:
|
||||
dshot_config.ticks_per_bit = 16;
|
||||
dshot_config.ticks_zero_high = 6;
|
||||
dshot_config.ticks_one_high = 12;
|
||||
break;
|
||||
|
||||
case DSHOT1200:
|
||||
dshot_config.ticks_per_bit = 8;
|
||||
dshot_config.ticks_zero_high = 3;
|
||||
dshot_config.ticks_one_high = 6;
|
||||
break;
|
||||
|
||||
// Default case to handle invalid input
|
||||
default:
|
||||
dshot_config.ticks_per_bit = 0;
|
||||
dshot_config.ticks_zero_high = 0;
|
||||
dshot_config.ticks_one_high = 0;
|
||||
ticks_per_bit = 17;
|
||||
ticks_zero_high = 6;
|
||||
ticks_one_high = 13;
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate low signal timing
|
||||
dshot_config.ticks_zero_low = (dshot_config.ticks_per_bit - dshot_config.ticks_zero_high);
|
||||
dshot_config.ticks_one_low = (dshot_config.ticks_per_bit - dshot_config.ticks_one_high);
|
||||
uint32_t ticks_zero_low = ticks_per_bit - ticks_zero_high;
|
||||
uint32_t ticks_one_low = ticks_per_bit - ticks_one_high;
|
||||
|
||||
// Set up RMT configuration for DShot transmission
|
||||
dshot_tx_rmt_config.rmt_mode = RMT_MODE_TX;
|
||||
dshot_tx_rmt_config.channel = dshot_config.rmt_channel;
|
||||
dshot_tx_rmt_config.gpio_num = dshot_config.gpio_num;
|
||||
dshot_tx_rmt_config.mem_block_num = dshot_config.mem_block_num;
|
||||
dshot_tx_rmt_config.clk_div = dshot_config.clk_div;
|
||||
dshot_tx_rmt_config.tx_config.loop_en = false;
|
||||
dshot_tx_rmt_config.tx_config.carrier_en = false;
|
||||
dshot_tx_rmt_config.tx_config.idle_output_en = true;
|
||||
|
||||
// Set idle level for RMT transmission based on input parameter
|
||||
if (dshot_config.is_bidirectional)
|
||||
// Encode 16 bits
|
||||
for (int i = 15; i >= 0; i--)
|
||||
{
|
||||
dshot_tx_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH;
|
||||
}
|
||||
else
|
||||
{
|
||||
dshot_tx_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
|
||||
bool bit = (dshot_packet >> i) & 0x01;
|
||||
symbols[count].level0 = 1;
|
||||
symbols[count].duration0 = bit ? ticks_one_high : ticks_zero_high;
|
||||
symbols[count].level1 = 0;
|
||||
symbols[count].duration1 = bit ? ticks_one_low : ticks_zero_low;
|
||||
count++;
|
||||
}
|
||||
|
||||
// Set up selected DShot mode
|
||||
rmt_config(&dshot_tx_rmt_config);
|
||||
|
||||
// Install RMT driver and return result
|
||||
return rmt_driver_install(dshot_tx_rmt_config.channel, 0, 0);
|
||||
}
|
||||
|
||||
// Define a function to send a DShot command over an RMT interface to control a brushless motor's speed.
|
||||
void DShotRMT::sendThrottleValue(uint16_t throttle_value)
|
||||
{
|
||||
dshot_packet_t dshot_rmt_packet = {};
|
||||
|
||||
// Check if the throttle value is less than the minimum allowed value for the DShot protocol.
|
||||
if (throttle_value < DSHOT_THROTTLE_MIN)
|
||||
{
|
||||
throttle_value = DSHOT_THROTTLE_MIN;
|
||||
}
|
||||
|
||||
// Check if the throttle value is greater than the maximum allowed value for the DShot protocol.
|
||||
if (throttle_value > DSHOT_THROTTLE_MAX)
|
||||
{
|
||||
throttle_value = DSHOT_THROTTLE_MAX;
|
||||
}
|
||||
|
||||
dshot_rmt_packet.throttle_value = throttle_value;
|
||||
|
||||
// Telemetric using additional pin on the ESC is not supported.
|
||||
dshot_rmt_packet.telemetric_request = NO_TELEMETRIC;
|
||||
|
||||
// Calculate the checksum for the DShot packet using the calculateCRC function.
|
||||
dshot_rmt_packet.checksum = calculateCRC(dshot_rmt_packet);
|
||||
|
||||
// Send the DShot packet over the RMT interface to control the motor's speed.
|
||||
sendRmtPaket(dshot_rmt_packet);
|
||||
}
|
||||
|
||||
// This method builds the RMT data transmission sequence for the DShot protocol
|
||||
rmt_item32_t *DShotRMT::buildTxRmtItem(uint16_t parsed_packet)
|
||||
{
|
||||
// Check if DShot is set to bidirectional mode
|
||||
if (dshot_config.is_bidirectional)
|
||||
{
|
||||
// If bidirectional, invert the high/low bits
|
||||
for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1)
|
||||
{
|
||||
if (parsed_packet & 0b1000000000000000)
|
||||
{
|
||||
// Set RMT item for a logic high signal
|
||||
dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_low;
|
||||
dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_high;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set RMT item for a logic low signal
|
||||
dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_low;
|
||||
dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_high;
|
||||
}
|
||||
|
||||
// Set level of RMT item
|
||||
dshot_tx_rmt_item[i].level0 = 0;
|
||||
dshot_tx_rmt_item[i].level1 = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not bidirectional, set the RMT items as usual
|
||||
for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1)
|
||||
{
|
||||
if (parsed_packet & 0b1000000000000000)
|
||||
{
|
||||
// Set RMT item for a logic high signal
|
||||
dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_high;
|
||||
dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_low;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set RMT item for a logic low signal
|
||||
dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_high;
|
||||
dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_low;
|
||||
}
|
||||
|
||||
// Set level of RMT item
|
||||
dshot_tx_rmt_item[i].level0 = 1;
|
||||
dshot_tx_rmt_item[i].level1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Set end marker for each frame
|
||||
if (dshot_config.is_bidirectional)
|
||||
{
|
||||
dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level0 = 1;
|
||||
dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level1 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level0 = 0;
|
||||
dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level1 = 1;
|
||||
}
|
||||
|
||||
// Add packet seperator aka DShot Pause.
|
||||
dshot_tx_rmt_item[DSHOT_PAUSE_BIT].duration1 = DSHOT_PAUSE;
|
||||
|
||||
// Return the rmt_item
|
||||
return dshot_tx_rmt_item;
|
||||
}
|
||||
|
||||
// Calculates a CRC value for a DShot digital control signal packet
|
||||
uint16_t DShotRMT::calculateCRC(const dshot_packet_t &dshot_packet)
|
||||
{
|
||||
uint16_t crc;
|
||||
|
||||
// Combine the throttle value and telemetric request flag into a 16-bit packet value
|
||||
const uint16_t packet = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request;
|
||||
|
||||
// Calculate the CRC value using different bitwise operations depending on the DShot configuration
|
||||
if (dshot_config.is_bidirectional)
|
||||
{
|
||||
// Bidirectional configuration: perform a bitwise negation of the result of XORing the packet with its right-shifted values by 4 and 8 bits,
|
||||
// and then bitwise AND the result with 0x0F
|
||||
const uint16_t intermediate_result = packet ^ (packet >> 4) ^ (packet >> 8);
|
||||
crc = (~intermediate_result) & 0x0F;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unidirectional configuration: XOR the packet with its right-shifted values by 4 and 8 bits,
|
||||
// and then bitwise AND the result with 0x0F
|
||||
crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F;
|
||||
}
|
||||
|
||||
// Return the calculated CRC value as a 16-bit unsigned integer
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t DShotRMT::parseRmtPaket(const dshot_packet_t &dshot_packet)
|
||||
{
|
||||
uint16_t parsedRmtPaket = DSHOT_NULL_PACKET;
|
||||
uint16_t crc = calculateCRC(dshot_packet);
|
||||
|
||||
// Complete the paket
|
||||
parsedRmtPaket = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request;
|
||||
parsedRmtPaket = (parsedRmtPaket << 4) | crc;
|
||||
|
||||
return parsedRmtPaket;
|
||||
}
|
||||
|
||||
// Output using ESP32 RMT
|
||||
void DShotRMT::sendRmtPaket(const dshot_packet_t &dshot_packet)
|
||||
{
|
||||
buildTxRmtItem(parseRmtPaket(dshot_packet));
|
||||
|
||||
rmt_write_items(dshot_tx_rmt_config.channel, dshot_tx_rmt_item, DSHOT_PACKET_LENGTH, false);
|
||||
// Add pause
|
||||
symbols[count].level0 = 0;
|
||||
symbols[count].duration0 = ticks_per_bit * PAUSE_BITS;
|
||||
symbols[count].level1 = 0;
|
||||
symbols[count].duration1 = 0;
|
||||
count++;
|
||||
}
|
||||
|
|
|
|||
212
DShotRMT.h
212
DShotRMT.h
|
|
@ -1,201 +1,53 @@
|
|||
//
|
||||
// Name: DShotRMT.h
|
||||
// Created: 20.03.2021 00:49:15
|
||||
// Author: derdoktor667
|
||||
//
|
||||
/**
|
||||
* @file DShotRMT.h
|
||||
* @brief DShot signal generation using ESP32 RMT with continuous repeat and pause between frames
|
||||
* @author Wastl Kraus
|
||||
* @date 2025-06-11
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#ifndef _DSHOTRMT_h
|
||||
#define _DSHOTRMT_h
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <driver/rmt_tx.h>
|
||||
|
||||
// The RMT (Remote Control) module library is used for generating the DShot signal.
|
||||
#include <driver/rmt.h>
|
||||
static constexpr auto DSHOT_THROTTLE_FAILSAVE = 0;
|
||||
static constexpr auto DSHOT_THROTTLE_MIN = 48;
|
||||
static constexpr auto DSHOT_THROTTLE_MAX = 2047;
|
||||
static constexpr auto DEFAULT_RES_HZ = 10 * 1000 * 1000; // 10 MHz resolution
|
||||
static constexpr auto PAUSE_BITS = 21;
|
||||
static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
|
||||
|
||||
// Defines the library version
|
||||
constexpr auto DSHOT_LIB_VERSION = "0.2.4";
|
||||
|
||||
// Constants related to the DShot protocol
|
||||
constexpr auto DSHOT_CLK_DIVIDER = 8; // Slow down RMT clock to 0.1 microseconds / 100 nanoseconds per cycle
|
||||
constexpr auto DSHOT_PACKET_LENGTH = 17; // Last pack is the pause
|
||||
constexpr auto DSHOT_THROTTLE_MIN = 48;
|
||||
constexpr auto DSHOT_THROTTLE_MAX = 2047;
|
||||
constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
|
||||
constexpr auto DSHOT_PAUSE = 21; // 21-bit is recommended
|
||||
constexpr auto DSHOT_PAUSE_BIT = 16;
|
||||
constexpr auto F_CPU_RMT = (80 * 1000 * 1000); // unit: Hz
|
||||
constexpr auto RMT_CYCLES_PER_SEC = (F_CPU_RMT / DSHOT_CLK_DIVIDER);
|
||||
constexpr auto RMT_CYCLES_PER_ESP_CYCLE = (F_CPU / RMT_CYCLES_PER_SEC);
|
||||
|
||||
// Enumeration for the DShot mode
|
||||
/// DShot Mode
|
||||
typedef enum dshot_mode_e
|
||||
{
|
||||
DSHOT_OFF,
|
||||
DSHOT150,
|
||||
DSHOT300,
|
||||
DSHOT600,
|
||||
DSHOT1200
|
||||
DSHOT600
|
||||
} dshot_mode_t;
|
||||
|
||||
// Array of human-readable DShot mode names
|
||||
static const char *const dshot_mode_name[] = {
|
||||
"DSHOT_OFF",
|
||||
"DSHOT150",
|
||||
"DSHOT300",
|
||||
"DSHOT600",
|
||||
"DSHOT1200"};
|
||||
|
||||
// Enumeration for telemetric request
|
||||
typedef enum telemetric_request_e
|
||||
{
|
||||
NO_TELEMETRIC,
|
||||
ENABLE_TELEMETRIC,
|
||||
} telemetric_request_t;
|
||||
|
||||
// Structure for DShot packets
|
||||
typedef struct dshot_packet_s
|
||||
{
|
||||
uint16_t throttle_value : 11;
|
||||
telemetric_request_t telemetric_request : 1;
|
||||
uint16_t checksum : 4;
|
||||
} dshot_packet_t;
|
||||
|
||||
// Structure for eRPM packets
|
||||
typedef struct eRPM_packet_s
|
||||
{
|
||||
uint16_t eRPM_data : 12;
|
||||
uint8_t checksum : 4;
|
||||
} eRPM_packet_t;
|
||||
|
||||
// return states of the RPM getting function
|
||||
typedef enum dshot_erpm_exit_mode_e
|
||||
{
|
||||
DECODE_SUCCESS = 0,
|
||||
ERR_EMPTY_QUEUE,
|
||||
ERR_NO_PACKETS,
|
||||
ERR_CHECKSUM_FAIL,
|
||||
ERR_BIDIRECTION_DISABLED,
|
||||
|
||||
} dshot_erpm_exit_mode_t;
|
||||
|
||||
// Structure for all settings for the DShot mode
|
||||
typedef struct dshot_config_s
|
||||
{
|
||||
dshot_mode_t mode;
|
||||
String name_str;
|
||||
bool is_bidirectional;
|
||||
gpio_num_t gpio_num;
|
||||
uint8_t pin_num;
|
||||
rmt_channel_t rmt_channel;
|
||||
uint8_t mem_block_num;
|
||||
uint16_t ticks_per_bit;
|
||||
uint8_t clk_div;
|
||||
uint16_t ticks_zero_high;
|
||||
uint16_t ticks_zero_low;
|
||||
uint16_t ticks_one_high;
|
||||
uint16_t ticks_one_low;
|
||||
} dshot_config_t;
|
||||
|
||||
// The official DShot Commands
|
||||
typedef enum dshot_cmd_e
|
||||
{
|
||||
DSHOT_CMD_MOTOR_STOP = 0, // Currently not implemented - STOP Motors
|
||||
DSHOT_CMD_BEEP1, // Wait at least length of beep (380ms) before next command
|
||||
DSHOT_CMD_BEEP2, // Wait at least length of beep (380ms) before next command
|
||||
DSHOT_CMD_BEEP3, // Wait at least length of beep (400ms) before next command
|
||||
DSHOT_CMD_BEEP4, // Wait at least length of beep (400ms) before next command
|
||||
DSHOT_CMD_BEEP5, // Wait at least length of beep (400ms) before next command
|
||||
DSHOT_CMD_ESC_INFO, // Currently not implemented
|
||||
DSHOT_CMD_SPIN_DIRECTION_1, // Need 6x, no wait required
|
||||
DSHOT_CMD_SPIN_DIRECTION_2, // Need 6x, no wait required
|
||||
DSHOT_CMD_3D_MODE_OFF, // Need 6x, no wait required
|
||||
DSHOT_CMD_3D_MODE_ON, // Need 6x, no wait required
|
||||
DSHOT_CMD_SETTINGS_REQUEST, // Currently not implemented
|
||||
DSHOT_CMD_SAVE_SETTINGS, // Need 6x, wait at least 12ms before next command
|
||||
DSHOT_CMD_SPIN_DIRECTION_NORMAL, // Need 6x, no wait required
|
||||
DSHOT_CMD_SPIN_DIRECTION_REVERSED, // Need 6x, no wait required
|
||||
DSHOT_CMD_LED0_ON, // Currently not implemented
|
||||
DSHOT_CMD_LED1_ON, // Currently not implemented
|
||||
DSHOT_CMD_LED2_ON, // Currently not implemented
|
||||
DSHOT_CMD_LED3_ON, // Currently not implemented
|
||||
DSHOT_CMD_LED0_OFF, // Currently not implemented
|
||||
DSHOT_CMD_LED1_OFF, // Currently not implemented
|
||||
DSHOT_CMD_LED2_OFF, // Currently not implemented
|
||||
DSHOT_CMD_LED3_OFF, // Currently not implemented
|
||||
DSHOT_CMD_36, // Not yet assigned
|
||||
DSHOT_CMD_37, // Not yet assigned
|
||||
DSHOT_CMD_38, // Not yet assigned
|
||||
DSHOT_CMD_39, // Not yet assigned
|
||||
DSHOT_CMD_40, // Not yet assigned
|
||||
DSHOT_CMD_41, // Not yet assigned
|
||||
DSHOT_CMD_SIGNAL_LINE_TEMPERATURE_TELEMETRY, // No wait required
|
||||
DSHOT_CMD_SIGNAL_LINE_VOLTAGE_TELEMETRY, // No wait required
|
||||
DSHOT_CMD_SIGNAL_LINE_CURRENT_TELEMETRY, // No wait required
|
||||
DSHOT_CMD_SIGNAL_LINE_CONSUMPTION_TELEMETRY, // No wait required
|
||||
DSHOT_CMD_SIGNAL_LINE_ERPM_TELEMETRY, // No wait required
|
||||
DSHOT_CMD_SIGNAL_LINE_ERPM_PERIOD_TELEMETRY, // No wait required (also command 47)
|
||||
DSHOT_CMD_MAX = 47
|
||||
} dshot_cmd_t;
|
||||
|
||||
// ...Mapping for GCR
|
||||
static const unsigned char GCR_encode[16] =
|
||||
{
|
||||
0x19, 0x1B, 0x12, 0x13,
|
||||
0x1D, 0x15, 0x16, 0x17,
|
||||
0x1A, 0x09, 0x0A, 0x0B,
|
||||
0x1E, 0x0D, 0x0E, 0x0F};
|
||||
|
||||
// ...shifting 5 bits > 4 bits (0xff => invalid)
|
||||
static const unsigned char GCR_decode[32] =
|
||||
{
|
||||
0xFF, 0xFF, 0xFF, 0xFF, // 0 - 3
|
||||
0xFF, 0xFF, 0xFF, 0xFF, // 4 - 7
|
||||
0xFF, 9, 10, 11, // 8 - 11
|
||||
0xFF, 13, 14, 15, // 12 - 15
|
||||
|
||||
0xFF, 0xFF, 2, 3, // 16 - 19
|
||||
0xFF, 5, 6, 7, // 20 - 23
|
||||
0xFF, 0, 8, 1, // 24 - 27
|
||||
0xFF, 4, 12, 0xFF, // 28 - 31
|
||||
};
|
||||
|
||||
// The main DShotRMT class
|
||||
class DShotRMT
|
||||
{
|
||||
public:
|
||||
// Constructor for the DShotRMT class
|
||||
DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel);
|
||||
DShotRMT(uint8_t pin, uint8_t channel);
|
||||
DShotRMT(uint8_t pin);
|
||||
DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool isBidirectional = false);
|
||||
|
||||
// Destructor for the DShotRMT class
|
||||
~DShotRMT();
|
||||
void begin();
|
||||
void setThrottle(uint16_t throttle, bool telemetry = false);
|
||||
|
||||
// Copy constructor for the DShotRMT class
|
||||
DShotRMT(DShotRMT const &);
|
||||
|
||||
// The begin() function initializes the DShotRMT class with
|
||||
// a given DShot mode (DSHOT_OFF, DSHOT150, DSHOT300, DSHOT600, DSHOT1200)
|
||||
// and a bidirectional flag. It returns a boolean value
|
||||
// indicating whether or not the initialization was successful.
|
||||
bool begin(dshot_mode_t dshot_mode = DSHOT_OFF, bool is_bidirectional = false);
|
||||
|
||||
// The sendThrottleValue() function sends a DShot packet with a given
|
||||
// throttle value (between 49 and 2047) and an optional telemetry
|
||||
// request flag.
|
||||
// void sendThrottleValue(uint16_t throttle_value, telemetric_request_t telemetric_request = NO_TELEMETRIC);
|
||||
void sendThrottleValue(uint16_t throttle_value);
|
||||
gpio_num_t getGPIO() const { return _gpio; }
|
||||
dshot_mode_t getDShotMode() const { return _mode; }
|
||||
|
||||
private:
|
||||
rmt_item32_t dshot_tx_rmt_item[DSHOT_PACKET_LENGTH]; // An array of RMT items used to send a DShot packet.
|
||||
rmt_config_t dshot_tx_rmt_config; // The RMT configuration used for sending DShot packets.
|
||||
dshot_config_t dshot_config; // The configuration for the DShot mode.
|
||||
gpio_num_t _gpio;
|
||||
dshot_mode_t _mode;
|
||||
bool _isBidirectional;
|
||||
|
||||
rmt_item32_t *buildTxRmtItem(uint16_t parsed_packet); // Constructs an RMT item from a parsed DShot packet.
|
||||
uint16_t calculateCRC(const dshot_packet_t &dshot_packet); // Calculates the CRC checksum for a DShot packet.
|
||||
uint16_t parseRmtPaket(const dshot_packet_t &dshot_packet); // Parses an RMT packet to obtain a DShot packet.
|
||||
rmt_channel_handle_t _channel = nullptr;
|
||||
rmt_encoder_handle_t _encoder = nullptr;
|
||||
rmt_transmit_config_t _tx_config = {};
|
||||
|
||||
void sendRmtPaket(const dshot_packet_t &dshot_packet); // Sends a DShot packet via RMT.
|
||||
uint16_t _lastThrottle = 0;
|
||||
bool _lastTelemetry = false;
|
||||
|
||||
void buildFrameSymbols(uint16_t frame, rmt_symbol_word_t *symbols, size_t &count);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
113
README.md
113
README.md
|
|
@ -1,44 +1,97 @@
|
|||
[](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml)
|
||||
[](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml)
|
||||
|
||||
## This is going to be deprecated soon. Rewriting for new IDF APIs. Status can be seen by using "reIDF5" branch.
|
||||
# DShot ESP32 Library using RMT (Rewrite for ESP-IDF 5)
|
||||
|
||||
## DShot ESP32 Library utilizing RMT
|
||||
This is a complete rewrite of the original DShotRMT library to support the new ESP-IDF 5 RMT encoder API (`rmt_tx.h`).
|
||||
The library sends continuous DShot frames with a configurable pause between them and supports all standard DShot modes (150, 300, 600).
|
||||
|
||||
### The DShot Protocol
|
||||
The DSHOT protocol consists of transmitting 16-bit packets to the ESCs: 11-bit throttle value, 1-bit to request telemetry and a 4-bit checksum. There are three major protocol speeds: DSHOT150, DSHOT300 and DSHOT600.
|
||||
The old Version without encoding (rmt.h) is still available by using "oldAPI" Branch.
|
||||
|
||||
| DSHOT | Bitrate | TH1 | TH0 | Bit Time µs | Frame Time µs |
|
||||
|-------|------------|-------|--------|------------|---------------|
|
||||
| 150 | 150kbit/s | 5.00 | 2.50 | 6.67 | 106.72 |
|
||||
| 300 | 300kbit/s | 2.50 | 1.25 | 3.33 | 53.28 |
|
||||
| 600 | 600kbit/s | 1.25 | 0.625 | 1.67 | 26.72 |
|
||||
| 1200 | 1200kbit/s | 0.625 | 0.313 | 0.83 | 13.28 |
|
||||
---
|
||||
|
||||
#### Calculating the CRC
|
||||
The checksum is calculated over the throttle value and the telemetry bit, so the “first” 12 bits our value in the following example:
|
||||
## The DShot Protocol
|
||||
|
||||
crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F;
|
||||
The DShot protocol transmits 16-bit packets to brushless ESCs:
|
||||
|
||||
### Bidirectional DSHOT
|
||||
Bidirectional DSHOT is also known as inverted DSHOT, because the signal level is inverted, so 1 is low and a 0 is high. This is done in order to let the ESC know, that we are operating in bidirectional mode and that it should be sending back eRPM telemetry packages.
|
||||
- 11-bit throttle value
|
||||
- 1-bit telemetry request
|
||||
- 4-bit checksum
|
||||
|
||||
#### Calculating the Bidirectional CRC
|
||||
The calculation of the checksum is basically the same, just before the last step the values are inverted:
|
||||
Data is transmitted MSB-first. Pulse timing depends on the selected DShot mode.
|
||||
|
||||
crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
|
||||
| DSHOT | Bitrate | TH1 | TH0 | Bit Time (µs) | Frame Time (µs) |
|
||||
|-------|-------------|-------|--------|---------------|-----------------|
|
||||
| 150 | 150 kbit/s | 5.00 | 2.50 | 6.67 | ~106.72 |
|
||||
| 300 | 300 kbit/s | 2.50 | 1.25 | 3.33 | ~53.28 |
|
||||
| 600 | 600 kbit/s | 1.25 | 0.625 | 1.67 | ~26.72 |
|
||||
|
||||
### Using RMT on ESP32
|
||||
The RMT (Remote Control) is a peripheral designed to generate accurate and stable signals to control external devices such as LEDs, motors, and other peripherals. It is well suited for generating the DShot signals in a high-performance and accurate way on the ESP32 platform.
|
||||
Each frame is followed by a 21-bit time pause at low level. This helps ESCs detect separate frames.
|
||||
|
||||
#### Advantages of using RMT
|
||||
- Generates accurate signals
|
||||
- Supports programmable timing
|
||||
- Configurable number of channels
|
||||
---
|
||||
|
||||
#### DShot RMT Library for ESP32
|
||||
The DShot RMT Library for ESP32 provides a convenient way of generating DShot signals using the RMT peripheral on the ESP32 platform. The library supports all three major DShot speeds: DSHOT150, DSHOT300, and DSHOT600.
|
||||
## Checksum Calculation
|
||||
|
||||
#### References
|
||||
- [DSHOT - the missing Handbook](https://brushlesswhoop.com/dshot-and-bidirectional-dshot/)
|
||||
- [DSHOT in the Dark](https://dmrlawson.co.uk/index.php/2017/12/04/dshot-in-the-dark/)
|
||||
The checksum is calculated over the first 12 bits (throttle + telemetry):
|
||||
|
||||
```c
|
||||
crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F;
|
||||
```
|
||||
|
||||
For Bidirectional DShot (not yet implemented), the CRC is inverted:
|
||||
|
||||
```c
|
||||
crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RMT on the ESP32
|
||||
|
||||
The RMT peripheral on the ESP32 is perfect for generating precise time-based signals like DShot.
|
||||
|
||||
### Advantages:
|
||||
|
||||
- Hardware-timed pulses
|
||||
- CPU-independent signal generation
|
||||
- Loop mode with inter-frame pause
|
||||
- Reliable under system load
|
||||
|
||||
---
|
||||
|
||||
## About This Library
|
||||
|
||||
This C++ library provides a simple class to generate DShot signals using any RMT-capable GPIO.
|
||||
It uses a `copy_encoder` to continuously send a prebuilt symbol buffer. New throttle values are applied only when they change.
|
||||
|
||||
### Supported Modes:
|
||||
|
||||
- DSHOT150
|
||||
- DSHOT300 (default)
|
||||
- DSHOT600
|
||||
|
||||
### Frame Structure:
|
||||
|
||||
- 16-bit DShot data
|
||||
- 21-bit times worth of pause (LOW)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [DSHOT – the missing Handbook](https://brushlesswhoop.com/dshot-and-bidirectional-dshot/)
|
||||
- [DSHOT in the Dark](https://dmrlawson.co.uk/index.php/2017/12/04/dshot-in-the-dark/)
|
||||
- [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License – see LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
Wastl Kraus
|
||||
GitHub: [@derdoktor667](https://github.com/derdoktor667)
|
||||
Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)
|
||||
|
|
|
|||
|
|
@ -1,64 +1,68 @@
|
|||
/*
|
||||
* Title: dshot300.ino
|
||||
* Author: derdoktor667
|
||||
* Date: 2023-04-13
|
||||
*
|
||||
* Description: A simple example of using the DShotRMT library to
|
||||
* generate a DShot300 signal for blheli_s escs.
|
||||
/**
|
||||
* @file dshot300.ino
|
||||
* @brief Demo sketch for continuous DShot signal using ESP32 and DShotRMT library
|
||||
* @author Wastl Kraus
|
||||
* @date 2025-06-07
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <DShotRMT.h>
|
||||
|
||||
// USB serial port needed for this example
|
||||
const auto USB_SERIAL_BAUD = 115200;
|
||||
// USB serial port settings
|
||||
#define USB_Serial Serial
|
||||
const uint32_t USB_SERIAL_BAUD = 115200;
|
||||
|
||||
// Define the GPIO pin connected to the motor and the DShot protocol used
|
||||
const auto MOTOR01_PIN = GPIO_NUM_17;
|
||||
const auto DSHOT_MODE = DSHOT300;
|
||||
// Motor configuration
|
||||
const gpio_num_t MOTOR01_PIN = GPIO_NUM_17;
|
||||
const dshot_mode_t DSHOT_MODE = DSHOT300;
|
||||
|
||||
// Define the failsafe and initial throttle values
|
||||
const auto FAILSAFE_THROTTLE = 999;
|
||||
const auto INITIAL_THROTTLE = 48;
|
||||
|
||||
// Initialize a DShotRMT object for the motor
|
||||
DShotRMT motor01(MOTOR01_PIN, RMT_CHANNEL_0);
|
||||
// Create DShotRMT instance
|
||||
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE);
|
||||
|
||||
void setup()
|
||||
{
|
||||
USB_Serial.begin(USB_SERIAL_BAUD);
|
||||
|
||||
// Start generating DShot signal for the motor
|
||||
motor01.begin(DSHOT_MODE);
|
||||
// Wait for serial port
|
||||
while (!USB_Serial)
|
||||
delay(10);
|
||||
|
||||
Serial.println("DShotRMT Demo started.");
|
||||
Serial.println("Enter a throttle value (0–2047):");
|
||||
USB_Serial.println("DShotRMT Demo started.");
|
||||
USB_Serial.println("Enter a throttle value (48–2047):");
|
||||
|
||||
motor01.begin();
|
||||
|
||||
// Arm ESC with minimum throttle
|
||||
motor01.setThrottle(DSHOT_THROTTLE_MIN);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// Read the throttle value from the USB serial input
|
||||
int throttle_input = read_SerialThrottle();
|
||||
// Simple as can be
|
||||
int throttle_input = readSerialThrottle();
|
||||
|
||||
// Send the throttle value to the motor
|
||||
motor01.sendThrottleValue(throttle_input);
|
||||
motor01.setThrottle(throttle_input);
|
||||
}
|
||||
|
||||
// ...just for this example
|
||||
// Read the throttle value from the USB serial input
|
||||
int read_SerialThrottle()
|
||||
// Reads throttle value from serial input
|
||||
int readSerialThrottle()
|
||||
{
|
||||
static int last_throttle = INITIAL_THROTTLE;
|
||||
static int last_throttle = DSHOT_THROTTLE_MIN;
|
||||
|
||||
if (USB_Serial.available() > 0)
|
||||
{
|
||||
auto throttle_input = (USB_Serial.readStringUntil('\n')).toInt();
|
||||
String input = USB_Serial.readStringUntil('\n');
|
||||
int throttle_input = input.toInt();
|
||||
|
||||
// Clamp the value to the DShot range
|
||||
throttle_input = constrain(throttle_input, 48, 2047);
|
||||
last_throttle = throttle_input;
|
||||
Serial.print("Throttle set to: ");
|
||||
Serial.println(last_throttle);
|
||||
Serial.println(" ");
|
||||
Serial.println("Enter a throttle value (0–2047):");
|
||||
|
||||
USB_Serial.print("Throttle set to: ");
|
||||
USB_Serial.println(last_throttle);
|
||||
|
||||
USB_Serial.println("Enter a throttle value (48–2047):");
|
||||
}
|
||||
return last_throttle;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
name=DShotRMT
|
||||
version=0.2.4
|
||||
version=0.4.0
|
||||
author=derdoktor667
|
||||
maintainer=derdoktor667
|
||||
sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S.
|
||||
paragraph=This library can control a BlHeli_S by using encoded DShot commands.
|
||||
category=Device Control
|
||||
category=Signal Input/Output
|
||||
url=https://github.com/derdoktor667/DShotRMT
|
||||
architectures=esp32
|
||||
|
|
|
|||
Loading…
Reference in New Issue