diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index 5f037f7..77a4fdb 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -16,31 +16,34 @@ constexpr auto FAILSAVE_THROTTLE = 0x3E7; void setup() { - // ...always start the onboard usb support - USB_Serial.begin(USB_SERIAL_BAUD); + // ...always start the onboard usb support + USB_Serial.begin(USB_SERIAL_BAUD); + + // ...start the dshot generation + motor01.begin(DSHOT300); } void loop() { - read_SerialThrottle(); + readSerialThrottle(); - motor01.send_dshot_value(throttle_value); + motor01.sendThrottleValue(throttle_value); - // ...print to console - USB_Serial.println(throttle_value); + // ...print to console + USB_Serial.println(throttle_value); } // // -uint16_t read_SerialThrottle() +uint16_t readSerialThrottle() { - if (USB_Serial.available() > 0) - { - auto throttle_input = (USB_Serial.readStringUntil('\n')).toInt(); - return throttle_input; - } - else - { - return FAILSAVE_THROTTLE; - } + if (USB_Serial.available() > 0) + { + auto throttle_input = (USB_Serial.readStringUntil('\n')).toInt(); + return throttle_input; + } + else + { + return FAILSAVE_THROTTLE; + } } diff --git a/library.properties b/library.properties index 65b4409..6e1c2ce 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=DShotRMT -version=0.2.3 +version=0.2.4 author=derdoktor667 maintainer=derdoktor667 sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S. diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp index 58aa009..4524a76 100644 --- a/src/DShotRMT.cpp +++ b/src/DShotRMT.cpp @@ -6,36 +6,33 @@ #include -DShotRMT::DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel, dshot_mode_t dshot_mode, bool is_bidirectional) +DShotRMT::DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel) { + // Initialize the configuration structure dshot_config.gpio_num = gpio; dshot_config.pin_num = uint8_t(gpio); dshot_config.rmt_channel = rmtChannel; dshot_config.mem_block_num = uint8_t(RMT_CHANNEL_MAX - uint8_t(rmtChannel)); // ...create empty packet - encode_dshot_to_rmt(DSHOT_NULL_PACKET); - - // install the RMT driver with dshot values - install_dshot_driver(dshot_mode, is_bidirectional); + buildTxRmtItem(DSHOT_NULL_PACKET); } -DShotRMT::DShotRMT(uint8_t pin, uint8_t channel, dshot_mode_t dshot_mode, bool is_bidirectional) +DShotRMT::DShotRMT(uint8_t pin, uint8_t channel) { + // Initialize the configuration structure dshot_config.gpio_num = gpio_num_t(pin); dshot_config.pin_num = pin; dshot_config.rmt_channel = rmt_channel_t(channel); dshot_config.mem_block_num = (RMT_CHANNEL_MAX - channel); // ...create empty packet - encode_dshot_to_rmt(DSHOT_NULL_PACKET); - - // install the RMT driver with dshot values - install_dshot_driver(dshot_mode, is_bidirectional); + buildTxRmtItem(DSHOT_NULL_PACKET); } DShotRMT::~DShotRMT() { + // Uninstall the RMT driver rmt_driver_uninstall(dshot_config.rmt_channel); } @@ -44,150 +41,12 @@ DShotRMT::DShotRMT(DShotRMT const &) // ...write me } -// ...the config part is done, now the calculating and sending part -void DShotRMT::send_dshot_value(uint16_t throttle_value, telemetric_request_t telemetric_request) -{ - dshot_packet_t dshot_rmt_packet = {}; - - if (throttle_value < DSHOT_THROTTLE_MIN) - { - throttle_value = DSHOT_THROTTLE_MIN; - } - - if (throttle_value > DSHOT_THROTTLE_MAX) - { - throttle_value = DSHOT_THROTTLE_MAX; - } - - // ...packets are the same for bidirectional mode - dshot_rmt_packet.throttle_value = throttle_value; - dshot_rmt_packet.telemetric_request = telemetric_request; - dshot_rmt_packet.checksum = this->calc_dshot_chksum(dshot_rmt_packet); - - output_rmt_data(dshot_rmt_packet); -} - -// ...get all setup data about current mode -dshot_config_t *DShotRMT::get_dshot_info() -{ - return &dshot_config; -} - -// ...get the ABP_Clock Divider for further calculations -uint8_t *DShotRMT::get_dshot_clock_div() -{ - return &dshot_config.clk_div; -} - -rmt_item32_t *DShotRMT::encode_dshot_to_rmt(uint16_t parsed_packet) -{ - // ...is bidirecional mode activated - if (dshot_config.bidirectional) - { - // ..."invert" the signal duration - for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) - { - if (parsed_packet & 0b1000000000000000) - { - // set one - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_low; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_high; - } - else - { - // set zero - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_low; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_high; - } - - dshot_tx_rmt_item[i].level0 = 0; - dshot_tx_rmt_item[i].level1 = 1; - } - } - - // ..."normal" DShot mode / "bidirectional" mode OFF - else - { - for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) - { - if (parsed_packet & 0b1000000000000000) - { - // set one - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_high; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_low; - } - else - { - // set zero - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_high; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_low; - } - - dshot_tx_rmt_item[i].level0 = 1; - dshot_tx_rmt_item[i].level1 = 0; - } - } - - if (dshot_config.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; - } - - // ...end marker added to each frame - dshot_tx_rmt_item[DSHOT_PAUSE_BIT].duration1 = (DSHOT_PAUSE / 2); - - return dshot_tx_rmt_item; -} - -// ...just returns the checksum -// DOES NOT APPEND CHECKSUM!!! -uint16_t DShotRMT::calc_dshot_chksum(const dshot_packet_t &dshot_packet) -{ - // ...start with two emprty "container" - uint16_t packet = DSHOT_NULL_PACKET; - uint16_t chksum = DSHOT_NULL_PACKET; - - // ...same initial 12bit data for bidirectional or "normal" mode - packet = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request; - - if (dshot_config.bidirectional) - { - // ...calc the checksum "inverted" / bidirectional mode - chksum = (~(packet ^ (packet >> 4) ^ (packet >> 8))) & 0x0F; - } - else - { - // ...calc the checksum "normal" mode - chksum = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F; - } - - return chksum; -} - -uint16_t DShotRMT::prepare_rmt_data(const dshot_packet_t &dshot_packet) -{ - uint16_t prepared_to_encode = DSHOT_NULL_PACKET; - uint16_t chksum = calc_dshot_chksum(dshot_packet); - - // ..."construct" the packet - prepared_to_encode = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request; - prepared_to_encode = (prepared_to_encode << 4) | chksum; - - return prepared_to_encode; -} - -void DShotRMT::install_dshot_driver(dshot_mode_t dshot_mode, bool is_bidirectional) +bool DShotRMT::begin(dshot_mode_t dshot_mode, bool is_bidirectional) { dshot_config.mode = dshot_mode; dshot_config.clk_div = DSHOT_CLK_DIVIDER; dshot_config.name_str = dshot_mode_name[dshot_mode]; - dshot_config.bidirectional = is_bidirectional; + dshot_config.is_bidirectional = is_bidirectional; switch (dshot_config.mode) { @@ -237,7 +96,7 @@ void DShotRMT::install_dshot_driver(dshot_mode_t dshot_mode, bool is_bidirection dshot_tx_rmt_config.tx_config.carrier_en = false; dshot_tx_rmt_config.tx_config.idle_output_en = true; - if (dshot_config.bidirectional) + if (dshot_config.is_bidirectional) { dshot_tx_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; } @@ -249,15 +108,145 @@ void DShotRMT::install_dshot_driver(dshot_mode_t dshot_mode, bool is_bidirection // ...setup selected dshot mode rmt_config(&dshot_tx_rmt_config); - // ...essential step, install rmt driver - rmt_driver_install(dshot_tx_rmt_config.channel, 0, 0); + // ...essential step, return the result + return rmt_driver_install(dshot_tx_rmt_config.channel, 0, 0); } -// ...finally output using ESP32 RMT -void DShotRMT::output_rmt_data(const dshot_packet_t &dshot_packet) +// ...the config part is done, now the calculating and sending part +void DShotRMT::sendThrottleValue(uint16_t throttle_value, telemetric_request_t telemetric_request) { - encode_dshot_to_rmt(prepare_rmt_data(dshot_packet)); + dshot_packet_t dshot_rmt_packet = {}; + + if (throttle_value < DSHOT_THROTTLE_MIN) + { + throttle_value = DSHOT_THROTTLE_MIN; + } + + if (throttle_value > DSHOT_THROTTLE_MAX) + { + throttle_value = DSHOT_THROTTLE_MAX; + } + + // ...packets are the same for bidirectional mode + dshot_rmt_packet.throttle_value = throttle_value; + dshot_rmt_packet.telemetric_request = telemetric_request; + dshot_rmt_packet.checksum = this->calculateCRC(dshot_rmt_packet); + + sendRmtPaket(dshot_rmt_packet); +} + +rmt_item32_t *DShotRMT::buildTxRmtItem(uint16_t parsed_packet) +{ + // ...for bidirectional mode + if (dshot_config.is_bidirectional) + { + // ..."invert" the high / low bits + for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) + { + if (parsed_packet & 0b1000000000000000) + { + // set one + dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_low; + dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_high; + } + else + { + // set zero + dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_low; + dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_high; + } + + dshot_tx_rmt_item[i].level0 = 0; + dshot_tx_rmt_item[i].level1 = 1; + } + } + + // ..."normal" DShot mode / "bidirectional" mode OFF + else + { + for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) + { + if (parsed_packet & 0b1000000000000000) + { + // set one + dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_high; + dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_low; + } + else + { + // set zero + dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_high; + dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_low; + } + + dshot_tx_rmt_item[i].level0 = 1; + dshot_tx_rmt_item[i].level1 = 0; + } + } + + 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; + } + + // ...end marker added to each frame + dshot_tx_rmt_item[DSHOT_PAUSE_BIT].duration1 = DSHOT_PAUSE; + + return dshot_tx_rmt_item; +} + +// Legacy CRC calculation +// uint16_t DShotRMT::calculateCRC(const dshot_packet_t &dshot_packet) +// { +// uint16_t packet = DSHOT_NULL_PACKET; +// uint16_t crc = DSHOT_NULL_PACKET; +// // Same initial 11 bits for both bidirectional and normal mode +// packet = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request; +// if (dshot_config.is_bidirectional) +// { +// // Calculate checksum in inverted/bidirectional mode +// crc = (~(packet ^ (packet >> 4) ^ (packet >> 8))) & 0x0F; +// } +// else +// { +// // Calculate checksum in normal mode +// crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F; +// } +// return crc; +// } + +// New way of calculating CRC +uint16_t DShotRMT::calculateCRC(const dshot_packet_t &dshot_packet) +{ + const uint16_t packet = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request; + const uint16_t crc = dshot_config.is_bidirectional + ? (~(packet ^ (packet >> 4) ^ (packet >> 8))) & 0x0F + : (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F; + 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); } diff --git a/src/DShotRMT.h b/src/DShotRMT.h index 98e0486..4f5810b 100644 --- a/src/DShotRMT.h +++ b/src/DShotRMT.h @@ -4,35 +4,30 @@ // Author: derdoktor667 // -#pragma once +#ifndef _DSHOTRMT_h +#define _DSHOTRMT_h #include -// Use the RMT Module library for generating the DShot signal +// The RMT (Remote Control) module library is used for generating the DShot signal. #include -// Define library version -constexpr auto DSHOT_LIB_VERSION = "0.2.3"; +// Defines the library version +constexpr auto DSHOT_LIB_VERSION = "0.2.4"; -// Define constants for generating the DShot signal +// 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 - -// Define constants for DShot throttle range and null packet constexpr auto DSHOT_THROTTLE_MIN = 48; constexpr auto DSHOT_THROTTLE_MAX = 2047; constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; - -// Define constants for DShot pause -constexpr auto DSHOT_PAUSE = 21; // 21bit is recommended, but to be sure +constexpr auto DSHOT_PAUSE = 21; // 21-bit is recommended constexpr auto DSHOT_PAUSE_BIT = 16; - -// Define constants for RMT timing constexpr auto F_CPU_RMT = APB_CLK_FREQ; 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); -// Define enumeration for DShot mode +// Enumeration for the DShot mode typedef enum dshot_mode_e { DSHOT_OFF, @@ -42,24 +37,22 @@ typedef enum dshot_mode_e DSHOT1200 } dshot_mode_t; -// Define human-readable names for DShot modes +// Array of human-readable DShot mode names static const char *const dshot_mode_name[] = { "DSHOT_OFF", "DSHOT150", "DSHOT300", "DSHOT600", - "DSHOT1200" -}; + "DSHOT1200"}; -// Define type aliases for DShot name and telemetry request -typedef String dshot_name_t; +// Enumeration for telemetric request typedef enum telemetric_request_e { NO_TELEMETRIC, ENABLE_TELEMETRIC, } telemetric_request_t; -// Define structure for DShot packet +// Structure for DShot packets typedef struct dshot_packet_s { uint16_t throttle_value : 11; @@ -67,19 +60,19 @@ typedef struct dshot_packet_s uint16_t checksum : 4; } dshot_packet_t; -// Define structure for eRPM packet +// Structure for eRPM packets typedef struct eRPM_packet_s { uint16_t eRPM_data : 12; uint8_t checksum : 4; } eRPM_packet_t; -// Define structure for DShot configuration +// Structure for all settings for the DShot mode typedef struct dshot_config_s { dshot_mode_t mode; - dshot_name_t name_str; - bool bidirectional; + String name_str; + bool is_bidirectional; gpio_num_t gpio_num; uint8_t pin_num; rmt_channel_t rmt_channel; @@ -92,30 +85,41 @@ typedef struct dshot_config_s uint16_t ticks_one_low; } dshot_config_t; -// Define class for DShot generation using RMT +// The main DShotRMT class class DShotRMT { public: - DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel, dshot_mode_t dshot_mode = DSHOT300, bool is_bidirectional = false); - DShotRMT(uint8_t pin, uint8_t channel, dshot_mode_t dshot_mode = DSHOT300, bool is_bidirectional = false); + // Constructor for the DShotRMT class + DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel); + DShotRMT(uint8_t pin, uint8_t channel); + + // Destructor for the DShotRMT class ~DShotRMT(); + + // Copy constructor for the DShotRMT class DShotRMT(DShotRMT const &); - // ...safety first ...no parameters, no DShot - void send_dshot_value(uint16_t throttle_value, telemetric_request_t telemetric_request = NO_TELEMETRIC); + // 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); - dshot_config_t *get_dshot_info(); - uint8_t *get_dshot_clock_div(); + // The sendThrottleValue() function sends a DShot packet with a given + // throttle value (between 0 and 2047) and an optional telemetry + // request flag. + void sendThrottleValue(uint16_t throttle_value, telemetric_request_t telemetric_request = NO_TELEMETRIC); private: - rmt_item32_t dshot_tx_rmt_item[DSHOT_PACKET_LENGTH]; - rmt_config_t dshot_tx_rmt_config; - dshot_config_t dshot_config; + 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. - rmt_item32_t *encode_dshot_to_rmt(uint16_t parsed_packet); - uint16_t calc_dshot_chksum(const dshot_packet_t &dshot_packet); - uint16_t prepare_rmt_data(const dshot_packet_t &dshot_packet); + 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. - void install_dshot_driver(dshot_mode_t dshot_mode, bool is_bidirectional); - void output_rmt_data(const dshot_packet_t &dshot_packet); + void sendRmtPaket(const dshot_packet_t &dshot_packet); // Sends a DShot packet via RMT. }; + +#endif