prepare release 0.8.3

prepare release 0.8.3
This commit is contained in:
Wastl Kraus 2025-09-24 00:38:25 +02:00 committed by GitHub
commit 607b2ef2fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1091 additions and 2326 deletions

View File

@ -15,31 +15,78 @@ jobs:
# ============================================================================
# Code Quality & Linting
# ============================================================================
quality-check:
name: Arduino Lint Check
quality-check-linux:
name: Arduino Lint Check (Linux)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- uses: arduino/setup-arduino-cli@v2
- name: Cache Arduino Core
uses: actions/cache@v4
- name: Restore Arduino Core Cache
id: cache-core-linux
uses: actions/cache/restore@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
key: arduino-core-${{ runner.os }}-esp32-v1
key: arduino-core-linux-esp32-v1
restore-keys: |
arduino-core-${{ runner.os }}-
- name: Install ESP32 Core
arduino-core-linux-
- name: Ensure ESP32 Core is installed (Linux)
shell: bash
run: |
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli core install esp32:esp32
if ! arduino-cli core list | grep -q 'esp32'; then
arduino-cli core install esp32:esp32
fi
- name: Save Arduino Core Cache
if: always() && steps.cache-core-linux.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
key: arduino-core-linux-esp32-v1
- uses: arduino/arduino-lint-action@v2
with:
path: ${{ github.workspace }}
compliance: strict
library-manager: update
verbose: true
quality-check-windows:
name: Arduino Lint Check (Windows)
runs-on: windows-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- uses: arduino/setup-arduino-cli@v2
- name: Restore Arduino Core Cache
id: cache-core-win
uses: actions/cache/restore@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
key: arduino-core-windows-esp32-v1
restore-keys: |
arduino-core-windows-
- name: Ensure ESP32 Core is installed (Windows)
shell: pwsh
run: |
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
if (-not (arduino-cli core list | Select-String 'esp32')) {
arduino-cli core install esp32:esp32
}
- name: Save Arduino Core Cache
if: always() && steps.cache-core-win.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
key: arduino-core-windows-esp32-v1
- uses: arduino/arduino-lint-action@v2
with:
path: ${{ github.workspace }}
@ -50,59 +97,101 @@ jobs:
# ============================================================================
# Compilation Test
# ============================================================================
compile-test:
name: Compile Example Sketches
compile-test-linux:
name: Compile Sketches (Linux)
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
example:
- examples/dshot300/dshot300.ino
- examples/command_manager/command_manager.ino
- examples/web_control/web_control.ino
- examples/web_client/web_client.ino
steps:
- uses: actions/checkout@v5
- uses: arduino/setup-arduino-cli@v2
- name: Cache Arduino Core & Libraries
uses: actions/cache@v4
- name: Restore Arduino Core & Libraries Cache
id: cache-all-linux
uses: actions/cache/restore@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-${{ runner.os }}-esp32-v1
key: arduino-full-linux-esp32-v1
restore-keys: |
arduino-full-${{ runner.os }}-
- name: Install ESP32 Core and Dependencies
arduino-full-linux-
- name: Ensure ESP32 Core and Dependencies (Linux)
shell: bash
run: |
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli core install esp32:esp32
arduino-cli lib install "ArduinoJson"
if ! arduino-cli core list | grep -q 'esp32'; then
arduino-cli core install esp32:esp32
fi
mkdir -p ~/Arduino/libraries
# Cached repository check
if [ ! -d ~/Arduino/libraries/ArduinoJson ]; then
git clone --depth=1 https://github.com/bblanchon/ArduinoJson.git ~/Arduino/libraries/ArduinoJson
fi
if [ ! -d ~/Arduino/libraries/ESPAsyncWebServer ]; then
git clone --depth=1 https://github.com/ESP32Async/ESPAsyncWebServer ~/Arduino/libraries/ESPAsyncWebServer
fi
if [ ! -d ~/Arduino/libraries/AsyncTCP ]; then
git clone --depth=1 https://github.com/ESP32Async/AsyncTCP ~/Arduino/libraries/AsyncTCP
fi
- name: Save Arduino Core & Libraries Cache
if: always() && steps.cache-all-linux.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-linux-esp32-v1
- name: Compile Sketch
run: |
arduino-cli compile \
--fqbn esp32:esp32:esp32 \
--library ${{ github.workspace }} \
${{ matrix.example }}
arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} examples/dshot300/dshot300.ino
compile-test-windows:
name: Compile Sketches (Windows)
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
- uses: arduino/setup-arduino-cli@v2
- name: Restore Arduino Core & Libraries Cache
id: cache-all-win
uses: actions/cache/restore@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-windows-esp32-v1
restore-keys: |
arduino-full-windows-
- name: Ensure ESP32 Core and Dependencies (Windows)
shell: pwsh
run: |
arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
if (-not (arduino-cli core list | Select-String 'esp32')) {
arduino-cli core install esp32:esp32
}
if (-not (Test-Path -Path "$env:USERPROFILE/Arduino/libraries/ArduinoJson")) {
git clone --depth=1 https://github.com/bblanchon/ArduinoJson.git "$env:USERPROFILE/Arduino/libraries/ArduinoJson"
}
if (-not (Test-Path -Path "$env:USERPROFILE/Arduino/libraries/ESPAsyncWebServer")) {
git clone --depth=1 https://github.com/ESP32Async/ESPAsyncWebServer "$env:USERPROFILE/Arduino/libraries/ESPAsyncWebServer"
}
if (-not (Test-Path -Path "$env:USERPROFILE/Arduino/libraries/AsyncTCP")) {
git clone --depth=1 https://github.com/ESP32Async/AsyncTCP "$env:USERPROFILE/Arduino/libraries/AsyncTCP"
}
- name: Save Arduino Core & Libraries Cache
if: always() && steps.cache-all-win.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.arduino15/packages
~/.arduino15/cache
~/Arduino/libraries
key: arduino-full-windows-esp32-v1
- name: Compile Sketch
run: |
arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} examples/dshot300/dshot300.ino
# ============================================================================
# Build Status Report
@ -111,8 +200,11 @@ jobs:
name: Build Summary
runs-on: ubuntu-latest
if: always()
needs: [quality-check, compile-test]
needs:
- quality-check-linux
- quality-check-windows
- compile-test-linux
- compile-test-windows
steps:
- name: Create Build Summary
run: |
@ -120,18 +212,19 @@ jobs:
echo "| Check | Status | Details |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|---------|" >> $GITHUB_STEP_SUMMARY
[[ "${{ needs.quality-check.result }}" == "success" ]] \
[[ "${{ needs.quality-check-linux.result }}" == "success" && "${{ needs.quality-check-windows.result }}" == "success" ]] \
&& echo "| 📋 Quality Check | ✅ Passed | Arduino Lint completed successfully |" >> $GITHUB_STEP_SUMMARY \
|| echo "| 📋 Quality Check | ❌ Failed | Check Arduino Lint report |" >> $GITHUB_STEP_SUMMARY
[[ "${{ needs.compile-test.result }}" == "success" ]] \
[[ "${{ needs.compile-test-linux.result }}" == "success" && "${{ needs.compile-test-windows.result }}" == "success" ]] \
&& echo "| 🔨 Compilation | ✅ Passed | All examples compiled successfully |" >> $GITHUB_STEP_SUMMARY \
|| echo "| 🔨 Compilation | ❌ Failed | Compilation errors detected |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
[[ "${{ needs.quality-check.result }}" == "success" && "${{ needs.compile-test.result }}" == "success" ]] \
[[ "${{ needs.quality-check-linux.result }}" == "success" && "${{ needs.quality-check-windows.result }}" == "success" && "${{ needs.compile-test-linux.result }}" == "success" && "${{ needs.compile-test-windows.result }}" == "success" ]] \
&& echo "## 🎉 All Checks Passed!" >> $GITHUB_STEP_SUMMARY \
&& echo "Your DShotRMT library is ready for deployment." >> $GITHUB_STEP_SUMMARY \
|| echo "## ⚠️ Action Required" >> $GITHUB_STEP_SUMMARY \
&& echo "Please review the failed checks and address any issues." >> $GITHUB_STEP_SUMMARY

16
.gitignore vendored
View File

@ -1,14 +1,26 @@
.vs
.vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Builds
*.code-workspace
# Caching ESP32 Builds
buildCache
build
examples/dshot300/debug.cfg
examples/dshot300/esp32.svd
examples/dshot300/debug_custom.json
examples/dshot300/debug.svd
GEMINI.md
.gemini/
gha-creds-*.json

View File

@ -1,7 +0,0 @@
{
"configuration": "JTAGAdapter=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,LoopCore=1,EventsCore=1,DebugLevel=none,EraseFlash=all,ZigbeeMode=default",
"board": "esp32:esp32:esp32",
"sketch": "examples/dshot300/dshot300.ino",
"output": "build",
"port": "/dev/ttyUSB0"
}

View File

@ -1,899 +0,0 @@
{
"version": 4,
"configurations": [
{
"name": "ESP32",
"compilerPath": "~/.arduino15/packages/esp32/tools/esp-x32/2411/bin/xtensa-esp32-elf-g++",
"compilerArgs": [
"-MMD",
"-w",
"-Werror=return-type",
"-iprefix"
],
"intelliSenseMode": "linux-gcc-x64",
"includePath": [
"~/.arduino15/packages/esp32/hardware/esp32/3.3.0/cores/esp32/**",
"~/.arduino15/packages/esp32/hardware/esp32/3.3.0/variants/esp32/**",
"~/.arduino15/packages/esp32/hardware/esp32/3.3.0/libraries/**",
"~/.arduino15/packages/esp32/hardware/esp32/3.3.0/**",
"~/.arduino15/packages/esp32/tools/**",
"~/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include/c++/14.2.0/**",
"~/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include/c++/14.2.0/xtensa-esp-elf/esp32/**",
"~/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include/c++/14.2.0/backward/**",
"~/.arduino15/packages/esp32/tools/esp-x32/2411/lib/gcc/xtensa-esp-elf/14.2.0/include/**",
"~/.arduino15/packages/esp32/tools/esp-x32/2411/lib/gcc/xtensa-esp-elf/14.2.0/include-fixed/**",
"~/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include/**",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.5-b66b5448-v1/esp32/include/**",
"~/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.5-b66b5448-v1/esp32/include/freertos/FreeRTOS-Kernel/portable/xtensa/include/freertos/",
"~/Arduino/libraries/**",
"${workspaceFolder}/**"
],
"forcedInclude": [
"~/.arduino15/packages/esp32/hardware/esp32/3.3.0/cores/esp32/Arduino.h"
],
"cStandard": "c17",
"cppStandard": "c++20",
"defines": [
"F_CPU=240000000L",
"ESP32",
"ARDUINO=10607",
"ARDUINO_ESP32_DEV",
"ARDUINO_ARCH_ESP32",
"ARDUINO_BOARD=\"ESP32_DEV\"",
"ARDUINO_VARIANT=\"esp32\"",
"ARDUINO_PARTITION_default",
"ARDUINO_HOST_OS=\"linux\"",
"ARDUINO_FQBN=\"esp32:esp32:esp32:JTAGAdapter=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,LoopCore=1,EventsCore=1,DebugLevel=none,EraseFlash=none,ZigbeeMode=default\"",
"ESP32=ESP32",
"CORE_DEBUG_LEVEL=0",
"ARDUINO_RUNNING_CORE=1",
"ARDUINO_EVENT_RUNNING_CORE=1",
"ARDUINO_USB_CDC_ON_BOOT=0",
"__DBL_MIN_EXP__=(-1021)",
"__XCHAL_HAVE_FP=1",
"__cpp_nontype_template_parameter_auto=201606L",
"__UINT_LEAST16_MAX__=0xffff",
"__ATOMIC_ACQUIRE=2",
"__FLT_MIN__=1.1754943508222875e-38F",
"__GCC_IEC_559_COMPLEX=0",
"__XCHAL_HAVE_PREDICTED_BRANCHES=0",
"__cpp_aggregate_nsdmi=201304L",
"__UINT_LEAST8_TYPE__=unsigned char",
"__INTMAX_C(c)=c ## LL",
"__XCHAL_HAVE_ADDX=1",
"__CHAR_BIT__=8",
"__XCHAL_DCACHE_LINESIZE=16",
"__XTENSA_MARCH_EARLIEST=260003",
"__XCHAL_DCACHE_LINEWIDTH=4",
"__UINT8_MAX__=0xff",
"__WINT_MAX__=0xffffffffU",
"__FLT32_MIN_EXP__=(-125)",
"__cpp_static_assert=201411L",
"__ORDER_LITTLE_ENDIAN__=1234",
"__SIZE_MAX__=0xffffffffU",
"__WCHAR_MAX__=0xffff",
"__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1=1",
"__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2=1",
"__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4=1",
"__DBL_DENORM_MIN__=double(4.9406564584124654e-324L)",
"__GCC_ATOMIC_CHAR_LOCK_FREE=2",
"__GCC_IEC_559=0",
"__FLT32X_DECIMAL_DIG__=17",
"__FLT_EVAL_METHOD__=0",
"__cpp_binary_literals=201304L",
"__FLT64_DECIMAL_DIG__=17",
"__cpp_noexcept_function_type=201510L",
"__GCC_ATOMIC_CHAR32_T_LOCK_FREE=2",
"__cpp_variadic_templates=200704L",
"__UINT_FAST64_MAX__=0xffffffffffffffffULL",
"__SIG_ATOMIC_TYPE__=int",
"__DBL_MIN_10_EXP__=(-307)",
"__FINITE_MATH_ONLY__=0",
"__cpp_variable_templates=201304L",
"__XCHAL_HAVE_L32R=1",
"__FLT32X_MAX_EXP__=1024",
"__GNUC_PATCHLEVEL__=0",
"__FLT32_HAS_DENORM__=1",
"__UINT_FAST8_MAX__=0xffffffffU",
"__cpp_rvalue_reference=200610L",
"__XCHAL_HAVE_LOOPS=1",
"__cpp_nested_namespace_definitions=201411L",
"__XCHAL_DEBUGLEVEL=6",
"__INT8_C(c)=c",
"__XCHAL_HAVE_DFP_RECIP=0",
"__INT_LEAST8_WIDTH__=8",
"__cpp_variadic_using=201611L",
"__UINT_LEAST64_MAX__=0xffffffffffffffffULL",
"__INT_LEAST8_MAX__=0x7f",
"__cpp_attributes=200809L",
"__cpp_capture_star_this=201603L",
"__SHRT_MAX__=0x7fff",
"__LDBL_MAX__=1.7976931348623157e+308L",
"__cpp_if_constexpr=201606L",
"__XCHAL_ICACHE_LINESIZE=16",
"__LDBL_IS_IEC_60559__=1",
"__UINT_LEAST8_MAX__=0xff",
"__GCC_ATOMIC_BOOL_LOCK_FREE=2",
"__UINTMAX_TYPE__=long long unsigned int",
"__cpp_nsdmi=200809L",
"__FLT_EVAL_METHOD_TS_18661_3__=0",
"__UINT32_MAX__=0xffffffffUL",
"__GXX_EXPERIMENTAL_CXX0X__=1",
"__LDBL_MAX_EXP__=1024",
"__WINT_MIN__=0U",
"__FLT32X_IS_IEC_60559__=1",
"__XCHAL_HAVE_THREADPTR=1",
"__INT_LEAST16_WIDTH__=16",
"__SCHAR_MAX__=0x7f",
"__WCHAR_MIN__=0",
"__XCHAL_ICACHE_LINEWIDTH=4",
"__INT64_C(c)=c ## LL",
"__GCC_ATOMIC_POINTER_LOCK_FREE=2",
"__ATOMIC_SEQ_CST=5",
"__SIZEOF_INT__=4",
"__FLT32X_MANT_DIG__=53",
"__GCC_ATOMIC_CHAR16_T_LOCK_FREE=2",
"__cpp_aligned_new=201606L",
"__XCHAL_HAVE_FP_RECIP=1",
"__FLT32_MAX_10_EXP__=38",
"__STDC_HOSTED__=1",
"__XCHAL_HAVE_MUL32=1",
"__XTENSA_EL__=1",
"__cpp_decltype_auto=201304L",
"__DBL_DIG__=15",
"__XCHAL_HAVE_MUL16=1",
"__FLT_EPSILON__=1.1920928955078125e-7F",
"__GXX_WEAK__=1",
"__SHRT_WIDTH__=16",
"__FLT32_IS_IEC_60559__=1",
"__LDBL_MIN__=2.2250738585072014e-308L",
"__DBL_IS_IEC_60559__=1",
"__cpp_threadsafe_static_init=200806L",
"__cpp_enumerator_attributes=201411L",
"__XCHAL_HAVE_MMU=0",
"__FLT32X_HAS_INFINITY__=1",
"__INT32_MAX__=0x7fffffffL",
"__XCHAL_HAVE_DIV32=1",
"__INT_WIDTH__=32",
"__XTENSA_MARCH_LATEST=260003",
"__DECIMAL_DIG__=17",
"__FLT64_EPSILON__=2.2204460492503131e-16F64",
"__INT16_MAX__=0x7fff",
"__FLT64_MIN_EXP__=(-1021)",
"__XCHAL_DCACHE_SIZE=0",
"__LDBL_HAS_QUIET_NAN__=1",
"__cpp_return_type_deduction=201304L",
"__XCHAL_HAVE_BOOLEANS=1",
"__FLT64_MANT_DIG__=53",
"__XTHAL_ABI_WINDOWED=0",
"__GNUC__=14",
"__GXX_RTTI=1",
"__FLT_HAS_DENORM__=1",
"__SIZEOF_LONG_DOUBLE__=8",
"__XCHAL_HAVE_CONST16=0",
"__BIGGEST_ALIGNMENT__=16",
"__STDC_UTF_16__=1",
"__FLT64_MAX_10_EXP__=308",
"__cpp_delegating_constructors=200604L",
"__DBL_MAX__=double(1.7976931348623157e+308L)",
"__cpp_raw_strings=200710L",
"__INT_FAST32_MAX__=0x7fffffff",
"__DBL_HAS_INFINITY__=1",
"__cpp_deduction_guides=201703L",
"__HAVE_SPECULATION_SAFE_VALUE=1",
"__cpp_fold_expressions=201603L",
"__INTPTR_WIDTH__=32",
"__UINT_LEAST32_MAX__=0xffffffffUL",
"__FLT32X_HAS_DENORM__=1",
"__INT_FAST16_TYPE__=int",
"__XCHAL_HAVE_RELEASE_SYNC=1",
"__LDBL_HAS_DENORM__=1",
"__cplusplus=201703L",
"__cpp_ref_qualifiers=200710L",
"__INT_LEAST32_MAX__=0x7fffffffL",
"__DEPRECATED=1",
"__cpp_rvalue_references=200610L",
"__DBL_MAX_EXP__=1024",
"__WCHAR_WIDTH__=16",
"__FLT32_MAX__=3.4028234663852886e+38F32",
"__GCC_ATOMIC_LONG_LOCK_FREE=2",
"__PTRDIFF_MAX__=0x7fffffff",
"__FLT32_HAS_QUIET_NAN__=1",
"__GNUG__=14",
"__LONG_LONG_MAX__=0x7fffffffffffffffLL",
"__SIZEOF_SIZE_T__=4",
"__SIZEOF_WINT_T__=4",
"__FLT32X_DIG__=15",
"__LONG_LONG_WIDTH__=64",
"__cpp_initializer_lists=200806L",
"__FLT32_MAX_EXP__=128",
"__XCHAL_HAVE_MINMAX=1",
"__cpp_hex_float=201603L",
"__XCHAL_NUM_IBREAK=2",
"__GXX_ABI_VERSION=1019",
"__FLT_MIN_EXP__=(-125)",
"__cpp_lambdas=200907L",
"__INT_FAST64_TYPE__=long long int",
"__FP_FAST_FMAF=1",
"__FLT64_DENORM_MIN__=4.9406564584124654e-324F64",
"__DBL_MIN__=double(2.2250738585072014e-308L)",
"__SIZEOF_POINTER__=4",
"__DBL_HAS_QUIET_NAN__=1",
"__FLT32X_EPSILON__=2.2204460492503131e-16F32x",
"__XSHAL_HAVE_TEXT_SECTION_LITERALS=1",
"__FLT64_MIN_10_EXP__=(-307)",
"__REGISTER_PREFIX__",
"__UINT16_MAX__=0xffff",
"__XSHAL_USE_ABSOLUTE_LITERALS=0",
"__LDBL_HAS_INFINITY__=1",
"__FLT32_MIN__=1.1754943508222875e-38F32",
"__UINT8_TYPE__=unsigned char",
"__FLT_DIG__=6",
"__NO_INLINE__=1",
"__DEC_EVAL_METHOD__=2",
"__FLT_MANT_DIG__=24",
"__LDBL_DECIMAL_DIG__=17",
"__VERSION__=\"14.2.0\"",
"__UINT64_C(c)=c ## ULL",
"__XCHAL_NUM_AREGS=64",
"__cpp_unicode_characters=201411L",
"__XCHAL_HAVE_XEA3=0",
"__GCC_ATOMIC_INT_LOCK_FREE=2",
"__XCHAL_HAVE_DENSITY=1",
"__FLT32_MANT_DIG__=24",
"__FLOAT_WORD_ORDER__=__ORDER_LITTLE_ENDIAN__",
"__XCHAL_HAVE_CLAMPS=0",
"__XCHAL_HAVE_DFP_RSQRT=0",
"__cpp_aggregate_bases=201603L",
"__XCHAL_HAVE_NSA=1",
"__XCHAL_HAVE_WINDOWED=1",
"__SCHAR_WIDTH__=8",
"__INT32_C(c)=c ## L",
"__ORDER_PDP_ENDIAN__=3412",
"__INT_FAST32_TYPE__=int",
"__UINT_LEAST16_TYPE__=short unsigned int",
"__DBL_HAS_DENORM__=1",
"__XCHAL_HAVE_DEBUG=1",
"__cpp_rtti=199711L",
"__SIZE_TYPE__=unsigned int",
"__UINT64_MAX__=0xffffffffffffffffULL",
"__FLT_IS_IEC_60559__=1",
"__GNUC_WIDE_EXECUTION_CHARSET_NAME=\"UTF-16LE\"",
"__INT8_TYPE__=signed char",
"__cpp_digit_separators=201309L",
"__ELF__=1",
"__XSHAL_ABI=0",
"__xtensa__=1",
"__FLT_RADIX__=2",
"__INT_LEAST16_TYPE__=short int",
"__LDBL_EPSILON__=2.2204460492503131e-16L",
"__UINTMAX_C(c)=c ## ULL",
"__FLT32X_MIN__=2.2250738585072014e-308F32x",
"__XCHAL_HAVE_DFP_SQRT=0",
"__SIG_ATOMIC_MAX__=0x7fffffff",
"__XCHAL_HAVE_MAC16=1",
"__GCC_ATOMIC_WCHAR_T_LOCK_FREE=2",
"__USER_LABEL_PREFIX__",
"__SIZEOF_PTRDIFF_T__=4",
"__XCHAL_MMU_MIN_PTE_PAGE_SIZE=1",
"__XCHAL_DCACHE_IS_WRITEBACK=0",
"__SIZEOF_LONG__=4",
"__LDBL_DIG__=15",
"__FLT64_IS_IEC_60559__=1",
"__XCHAL_MAX_INSTRUCTION_SIZE=3",
"__FLT32X_MIN_EXP__=(-1021)",
"__INT_FAST16_MAX__=0x7fffffff",
"__GCC_CONSTRUCTIVE_SIZE=32",
"__FLT64_DIG__=15",
"__UINT_FAST32_MAX__=0xffffffffU",
"__UINT_LEAST64_TYPE__=long long unsigned int",
"__FLT_HAS_QUIET_NAN__=1",
"__FLT_MAX_10_EXP__=38",
"__FLT_HAS_INFINITY__=1",
"__GNUC_EXECUTION_CHARSET_NAME=\"UTF-8\"",
"__CHAR_UNSIGNED__=1",
"__cpp_unicode_literals=200710L",
"__UINT_FAST16_TYPE__=unsigned int",
"__INT_FAST32_WIDTH__=32",
"__CHAR16_TYPE__=short unsigned int",
"__PRAGMA_REDEFINE_EXTNAME=1",
"__SIZE_WIDTH__=32",
"__INT_LEAST16_MAX__=0x7fff",
"__INT64_MAX__=0x7fffffffffffffffLL",
"__FLT32_DENORM_MIN__=1.4012984643248171e-45F32",
"__SIG_ATOMIC_WIDTH__=32",
"__INT_LEAST64_TYPE__=long long int",
"__INT16_TYPE__=short int",
"__INT_LEAST8_TYPE__=signed char",
"__cpp_structured_bindings=201606L",
"__INT_FAST8_MAX__=0x7fffffff",
"__INTPTR_MAX__=0x7fffffff",
"__cpp_sized_deallocation=201309L",
"__cpp_guaranteed_copy_elision=201606L",
"__FLT64_HAS_QUIET_NAN__=1",
"__FLT32_MIN_10_EXP__=(-37)",
"__EXCEPTIONS=1",
"__UINT16_C(c)=c",
"__XCHAL_M_STAGE=3",
"__PTRDIFF_WIDTH__=32",
"__LDBL_MANT_DIG__=53",
"__cpp_range_based_for=201603L",
"__FLT64_HAS_INFINITY__=1",
"__STDCPP_DEFAULT_NEW_ALIGNMENT__=8",
"__SIG_ATOMIC_MIN__=(-__SIG_ATOMIC_MAX__ - 1)",
"__XCHAL_ICACHE_SIZE=0",
"__cpp_nontype_template_args=201411L",
"__INTPTR_TYPE__=int",
"__UINT16_TYPE__=short unsigned int",
"__WCHAR_TYPE__=short unsigned int",
"__XCHAL_HAVE_DEPBITS=0",
"__SIZEOF_FLOAT__=4",
"__UINTPTR_MAX__=0xffffffffU",
"__INT_FAST64_WIDTH__=64",
"__cpp_decltype=200707L",
"__FLT32_DECIMAL_DIG__=9",
"__INT_FAST64_MAX__=0x7fffffffffffffffLL",
"__GCC_ATOMIC_TEST_AND_SET_TRUEVAL=1",
"__FLT_NORM_MAX__=3.4028234663852886e+38F",
"__XCHAL_HAVE_DFP=0",
"__FLT32_HAS_INFINITY__=1",
"__UINT_FAST64_TYPE__=long long unsigned int",
"__cpp_inline_variables=201606L",
"__INT_MAX__=0x7fffffff",
"__XCHAL_HAVE_EXCLUSIVE=0",
"__STDCPP_THREADS__=1",
"__INT64_TYPE__=long long int",
"__XCHAL_HAVE_DFP_DIV=0",
"__FLT_MAX_EXP__=128",
"__XCHAL_INST_FETCH_WIDTH=4",
"__DBL_MANT_DIG__=53",
"__cpp_inheriting_constructors=201511L",
"__INT_LEAST64_MAX__=0x7fffffffffffffffLL",
"__FP_FAST_FMAF32=1",
"__WINT_TYPE__=unsigned int",
"__UINT_LEAST32_TYPE__=long unsigned int",
"__SIZEOF_SHORT__=2",
"__FLT32_NORM_MAX__=3.4028234663852886e+38F32",
"__LDBL_MIN_EXP__=(-1021)",
"__XCHAL_HAVE_S32C1I=1",
"__FLT64_MAX__=1.7976931348623157e+308F64",
"__WINT_WIDTH__=32",
"__cpp_template_auto=201606L",
"__INT_LEAST64_WIDTH__=64",
"__FLT32X_MAX_10_EXP__=308",
"__cpp_namespace_attributes=201411L",
"__WCHAR_UNSIGNED__=1",
"__LDBL_MAX_10_EXP__=308",
"__ATOMIC_RELAXED=0",
"__DBL_EPSILON__=double(2.2204460492503131e-16L)",
"__XCHAL_HAVE_SEXT=1",
"__INT_LEAST32_TYPE__=long int",
"__XTENSA_WINDOWED_ABI__=1",
"__UINT8_C(c)=c",
"__FLT64_MAX_EXP__=1024",
"__SIZEOF_WCHAR_T__=2",
"__XCHAL_HAVE_FP_POSTINC=1",
"__FLT64_NORM_MAX__=1.7976931348623157e+308F64",
"__INTMAX_MAX__=0x7fffffffffffffffLL",
"__INT_FAST8_TYPE__=int",
"__XCHAL_HAVE_MUL32_HIGH=1",
"__GNUC_STDC_INLINE__=1",
"__FLT64_HAS_DENORM__=1",
"__FLT32_EPSILON__=1.1920928955078125e-7F32",
"__DBL_DECIMAL_DIG__=17",
"__STDC_UTF_32__=1",
"__XCHAL_HAVE_FP_DIV=1",
"__INT_FAST8_WIDTH__=32",
"__FLT32X_MAX__=1.7976931348623157e+308F32x",
"__DBL_NORM_MAX__=double(1.7976931348623157e+308L)",
"__BYTE_ORDER__=__ORDER_LITTLE_ENDIAN__",
"__GCC_DESTRUCTIVE_SIZE=32",
"__XTENSA__=1",
"__INTMAX_WIDTH__=64",
"__ORDER_BIG_ENDIAN__=4321",
"__XTHAL_ABI_CALL0=1",
"__cpp_runtime_arrays=198712L",
"__FLT32_DIG__=6",
"__UINT64_TYPE__=long long unsigned int",
"__UINT32_C(c)=c ## UL",
"__cpp_alias_templates=200704L",
"__FLT_DENORM_MIN__=1.4012984643248171e-45F",
"__INT8_MAX__=0x7f",
"__LONG_WIDTH__=32",
"__UINT_FAST32_TYPE__=unsigned int",
"__FLT32X_NORM_MAX__=1.7976931348623157e+308F32x",
"__CHAR32_TYPE__=long unsigned int",
"__FLT_MAX__=3.4028234663852886e+38F",
"__cpp_constexpr=201603L",
"__XCHAL_HAVE_FP_RSQRT=1",
"__INT32_TYPE__=long int",
"__SIZEOF_DOUBLE__=8",
"__cpp_exceptions=199711L",
"__FLT_MIN_10_EXP__=(-37)",
"__FLT64_MIN__=2.2250738585072014e-308F64",
"__INT_LEAST32_WIDTH__=32",
"__INTMAX_TYPE__=long long int",
"__XCHAL_HAVE_ABS=1",
"__FLT32X_HAS_QUIET_NAN__=1",
"__ATOMIC_CONSUME=1",
"__XCHAL_NUM_DBREAK=2",
"__XCHAL_HAVE_WIDE_BRANCHES=0",
"__GNUC_MINOR__=2",
"__INT_FAST16_WIDTH__=32",
"__UINTMAX_MAX__=0xffffffffffffffffULL",
"__FLT32X_DENORM_MIN__=4.9406564584124654e-324F32x",
"__cpp_template_template_args=201611L",
"__DBL_MAX_10_EXP__=308",
"__LDBL_DENORM_MIN__=4.9406564584124654e-324L",
"__INT16_C(c)=c",
"__STDC__=1",
"__PTRDIFF_TYPE__=int",
"__LONG_MAX__=0x7fffffffL",
"__XCHAL_HAVE_FP_SQRT=1",
"__UINT32_TYPE__=long unsigned int",
"__FLT32X_MIN_10_EXP__=(-307)",
"__UINTPTR_TYPE__=unsigned int",
"__LDBL_MIN_10_EXP__=(-307)",
"__cpp_generic_lambdas=201304L",
"__SIZEOF_LONG_LONG__=8",
"__cpp_USER_defined_literals=200809L",
"__GCC_ATOMIC_LLONG_LOCK_FREE=1",
"__FLT_DECIMAL_DIG__=9",
"__UINT_FAST16_MAX__=0xffffffffU",
"__LDBL_NORM_MAX__=1.7976931348623157e+308L",
"__GCC_ATOMIC_SHORT_LOCK_FREE=2",
"__XCHAL_HAVE_BE=0",
"__UINT_FAST8_TYPE__=unsigned int",
"__cpp_init_captures=201304L",
"__ATOMIC_ACQ_REL=4",
"__ATOMIC_RELEASE=3",
"RMT_CLK_SRC_DEFAULT = SOC_MOD_CLK_APB",
"USBCON"
]
},
{
"name": "Arduino",
"compilerPath": "/home/derdoktor667/.arduino15/packages/esp32/tools/esp-x32/2411/bin/xtensa-esp32-elf-g++",
"compilerArgs": [
"-MMD",
"-w",
"-Werror=return-type",
"-iprefix"
],
"intelliSenseMode": "gcc-x64",
"includePath": [
"/home/derdoktor667/Github/DShotRMT/examples/dshot300",
"/home/derdoktor667/.arduino15/packages/esp32/tools/esp32-arduino-libs/idf-release_v5.5-b66b5448-v1/esp32/qio_qspi/include",
"/home/derdoktor667/.arduino15/packages/esp32/hardware/esp32/3.3.0/cores/esp32",
"/home/derdoktor667/.arduino15/packages/esp32/hardware/esp32/3.3.0/variants/esp32",
"/home/derdoktor667/Arduino/libraries/DShotRMT/src",
"/home/derdoktor667/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include/c++/14.2.0",
"/home/derdoktor667/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include/c++/14.2.0/xtensa-esp-elf/esp32",
"/home/derdoktor667/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include/c++/14.2.0/backward",
"/home/derdoktor667/.arduino15/packages/esp32/tools/esp-x32/2411/lib/gcc/xtensa-esp-elf/14.2.0/include",
"/home/derdoktor667/.arduino15/packages/esp32/tools/esp-x32/2411/lib/gcc/xtensa-esp-elf/14.2.0/include-fixed",
"/home/derdoktor667/.arduino15/packages/esp32/tools/esp-x32/2411/xtensa-esp-elf/include"
],
"forcedInclude": [
"/home/derdoktor667/.arduino15/packages/esp32/hardware/esp32/3.3.0/cores/esp32/Arduino.h"
],
"cStandard": "c11",
"cppStandard": "c++11",
"defines": [
"F_CPU=240000000L",
"ARDUINO=10607",
"ARDUINO_ESP32_DEV",
"ARDUINO_ARCH_ESP32",
"ARDUINO_BOARD=\"ESP32_DEV\"",
"ARDUINO_VARIANT=\"esp32\"",
"ARDUINO_PARTITION_default",
"ARDUINO_HOST_OS=\"linux\"",
"ARDUINO_FQBN=\"esp32:esp32:esp32:JTAGAdapter=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,LoopCore=1,EventsCore=1,DebugLevel=none,EraseFlash=all,ZigbeeMode=default\"",
"ESP32=ESP32",
"CORE_DEBUG_LEVEL=0",
"ARDUINO_RUNNING_CORE=1",
"ARDUINO_EVENT_RUNNING_CORE=1",
"ARDUINO_USB_CDC_ON_BOOT=0",
"__DBL_MIN_EXP__=(-1021)",
"__XCHAL_HAVE_FP=1",
"__cpp_nontype_template_parameter_auto=201606L",
"__UINT_LEAST16_MAX__=0xffff",
"__ATOMIC_ACQUIRE=2",
"__FLT_MIN__=1.1754943508222875e-38F",
"__GCC_IEC_559_COMPLEX=0",
"__XCHAL_HAVE_PREDICTED_BRANCHES=0",
"__cpp_aggregate_nsdmi=201304L",
"__UINT_LEAST8_TYPE__=unsigned char",
"__INTMAX_C(c)=c ## LL",
"__XCHAL_HAVE_ADDX=1",
"__CHAR_BIT__=8",
"__XCHAL_DCACHE_LINESIZE=16",
"__XTENSA_MARCH_EARLIEST=260003",
"__XCHAL_DCACHE_LINEWIDTH=4",
"__UINT8_MAX__=0xff",
"__WINT_MAX__=0xffffffffU",
"__FLT32_MIN_EXP__=(-125)",
"__cpp_static_assert=201411L",
"__ORDER_LITTLE_ENDIAN__=1234",
"__SIZE_MAX__=0xffffffffU",
"__WCHAR_MAX__=0xffff",
"__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1=1",
"__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2=1",
"__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4=1",
"__DBL_DENORM_MIN__=double(4.9406564584124654e-324L)",
"__GCC_ATOMIC_CHAR_LOCK_FREE=2",
"__GCC_IEC_559=0",
"__FLT32X_DECIMAL_DIG__=17",
"__FLT_EVAL_METHOD__=0",
"__cpp_binary_literals=201304L",
"__FLT64_DECIMAL_DIG__=17",
"__cpp_noexcept_function_type=201510L",
"__GCC_ATOMIC_CHAR32_T_LOCK_FREE=2",
"__cpp_variadic_templates=200704L",
"__UINT_FAST64_MAX__=0xffffffffffffffffULL",
"__SIG_ATOMIC_TYPE__=int",
"__DBL_MIN_10_EXP__=(-307)",
"__FINITE_MATH_ONLY__=0",
"__cpp_variable_templates=201304L",
"__XCHAL_HAVE_L32R=1",
"__FLT32X_MAX_EXP__=1024",
"__GNUC_PATCHLEVEL__=0",
"__FLT32_HAS_DENORM__=1",
"__UINT_FAST8_MAX__=0xffffffffU",
"__cpp_rvalue_reference=200610L",
"__XCHAL_HAVE_LOOPS=1",
"__cpp_nested_namespace_definitions=201411L",
"__XCHAL_DEBUGLEVEL=6",
"__INT8_C(c)=c",
"__XCHAL_HAVE_DFP_RECIP=0",
"__INT_LEAST8_WIDTH__=8",
"__cpp_variadic_using=201611L",
"__UINT_LEAST64_MAX__=0xffffffffffffffffULL",
"__INT_LEAST8_MAX__=0x7f",
"__cpp_attributes=200809L",
"__cpp_capture_star_this=201603L",
"__SHRT_MAX__=0x7fff",
"__LDBL_MAX__=1.7976931348623157e+308L",
"__cpp_if_constexpr=201606L",
"__XCHAL_ICACHE_LINESIZE=16",
"__LDBL_IS_IEC_60559__=1",
"__UINT_LEAST8_MAX__=0xff",
"__GCC_ATOMIC_BOOL_LOCK_FREE=2",
"__UINTMAX_TYPE__=long long unsigned int",
"__cpp_nsdmi=200809L",
"__FLT_EVAL_METHOD_TS_18661_3__=0",
"__UINT32_MAX__=0xffffffffUL",
"__GXX_EXPERIMENTAL_CXX0X__=1",
"__LDBL_MAX_EXP__=1024",
"__WINT_MIN__=0U",
"__FLT32X_IS_IEC_60559__=1",
"__XCHAL_HAVE_THREADPTR=1",
"__INT_LEAST16_WIDTH__=16",
"__SCHAR_MAX__=0x7f",
"__WCHAR_MIN__=0",
"__XCHAL_ICACHE_LINEWIDTH=4",
"__INT64_C(c)=c ## LL",
"__GCC_ATOMIC_POINTER_LOCK_FREE=2",
"__ATOMIC_SEQ_CST=5",
"__SIZEOF_INT__=4",
"__FLT32X_MANT_DIG__=53",
"__GCC_ATOMIC_CHAR16_T_LOCK_FREE=2",
"__cpp_aligned_new=201606L",
"__XCHAL_HAVE_FP_RECIP=1",
"__FLT32_MAX_10_EXP__=38",
"__STDC_HOSTED__=1",
"__XCHAL_HAVE_MUL32=1",
"__XTENSA_EL__=1",
"__cpp_decltype_auto=201304L",
"__DBL_DIG__=15",
"__XCHAL_HAVE_MUL16=1",
"__FLT_EPSILON__=1.1920928955078125e-7F",
"__GXX_WEAK__=1",
"__SHRT_WIDTH__=16",
"__FLT32_IS_IEC_60559__=1",
"__LDBL_MIN__=2.2250738585072014e-308L",
"__DBL_IS_IEC_60559__=1",
"__cpp_threadsafe_static_init=200806L",
"__cpp_enumerator_attributes=201411L",
"__XCHAL_HAVE_MMU=0",
"__FLT32X_HAS_INFINITY__=1",
"__INT32_MAX__=0x7fffffffL",
"__XCHAL_HAVE_DIV32=1",
"__INT_WIDTH__=32",
"__XTENSA_MARCH_LATEST=260003",
"__DECIMAL_DIG__=17",
"__FLT64_EPSILON__=2.2204460492503131e-16F64",
"__INT16_MAX__=0x7fff",
"__FLT64_MIN_EXP__=(-1021)",
"__XCHAL_DCACHE_SIZE=0",
"__LDBL_HAS_QUIET_NAN__=1",
"__cpp_return_type_deduction=201304L",
"__XCHAL_HAVE_BOOLEANS=1",
"__FLT64_MANT_DIG__=53",
"__XTHAL_ABI_WINDOWED=0",
"__GNUC__=14",
"__GXX_RTTI=1",
"__FLT_HAS_DENORM__=1",
"__SIZEOF_LONG_DOUBLE__=8",
"__XCHAL_HAVE_CONST16=0",
"__BIGGEST_ALIGNMENT__=16",
"__STDC_UTF_16__=1",
"__FLT64_MAX_10_EXP__=308",
"__cpp_delegating_constructors=200604L",
"__DBL_MAX__=double(1.7976931348623157e+308L)",
"__cpp_raw_strings=200710L",
"__INT_FAST32_MAX__=0x7fffffff",
"__DBL_HAS_INFINITY__=1",
"__cpp_deduction_guides=201703L",
"__HAVE_SPECULATION_SAFE_VALUE=1",
"__cpp_fold_expressions=201603L",
"__INTPTR_WIDTH__=32",
"__UINT_LEAST32_MAX__=0xffffffffUL",
"__FLT32X_HAS_DENORM__=1",
"__INT_FAST16_TYPE__=int",
"__XCHAL_HAVE_RELEASE_SYNC=1",
"__LDBL_HAS_DENORM__=1",
"__cplusplus=201703L",
"__cpp_ref_qualifiers=200710L",
"__INT_LEAST32_MAX__=0x7fffffffL",
"__DEPRECATED=1",
"__cpp_rvalue_references=200610L",
"__DBL_MAX_EXP__=1024",
"__WCHAR_WIDTH__=16",
"__FLT32_MAX__=3.4028234663852886e+38F32",
"__GCC_ATOMIC_LONG_LOCK_FREE=2",
"__PTRDIFF_MAX__=0x7fffffff",
"__FLT32_HAS_QUIET_NAN__=1",
"__GNUG__=14",
"__LONG_LONG_MAX__=0x7fffffffffffffffLL",
"__SIZEOF_SIZE_T__=4",
"__SIZEOF_WINT_T__=4",
"__FLT32X_DIG__=15",
"__LONG_LONG_WIDTH__=64",
"__cpp_initializer_lists=200806L",
"__FLT32_MAX_EXP__=128",
"__XCHAL_HAVE_MINMAX=1",
"__cpp_hex_float=201603L",
"__XCHAL_NUM_IBREAK=2",
"__GXX_ABI_VERSION=1019",
"__FLT_MIN_EXP__=(-125)",
"__cpp_lambdas=200907L",
"__INT_FAST64_TYPE__=long long int",
"__FP_FAST_FMAF=1",
"__FLT64_DENORM_MIN__=4.9406564584124654e-324F64",
"__DBL_MIN__=double(2.2250738585072014e-308L)",
"__SIZEOF_POINTER__=4",
"__DBL_HAS_QUIET_NAN__=1",
"__FLT32X_EPSILON__=2.2204460492503131e-16F32x",
"__XSHAL_HAVE_TEXT_SECTION_LITERALS=1",
"__FLT64_MIN_10_EXP__=(-307)",
"__REGISTER_PREFIX__",
"__UINT16_MAX__=0xffff",
"__XSHAL_USE_ABSOLUTE_LITERALS=0",
"__LDBL_HAS_INFINITY__=1",
"__FLT32_MIN__=1.1754943508222875e-38F32",
"__UINT8_TYPE__=unsigned char",
"__FLT_DIG__=6",
"__NO_INLINE__=1",
"__DEC_EVAL_METHOD__=2",
"__FLT_MANT_DIG__=24",
"__LDBL_DECIMAL_DIG__=17",
"__VERSION__=\"14.2.0\"",
"__UINT64_C(c)=c ## ULL",
"__XCHAL_NUM_AREGS=64",
"__cpp_unicode_characters=201411L",
"__XCHAL_HAVE_XEA3=0",
"__GCC_ATOMIC_INT_LOCK_FREE=2",
"__XCHAL_HAVE_DENSITY=1",
"__FLT32_MANT_DIG__=24",
"__FLOAT_WORD_ORDER__=__ORDER_LITTLE_ENDIAN__",
"__XCHAL_HAVE_CLAMPS=0",
"__XCHAL_HAVE_DFP_RSQRT=0",
"__cpp_aggregate_bases=201603L",
"__XCHAL_HAVE_NSA=1",
"__XCHAL_HAVE_WINDOWED=1",
"__SCHAR_WIDTH__=8",
"__INT32_C(c)=c ## L",
"__ORDER_PDP_ENDIAN__=3412",
"__INT_FAST32_TYPE__=int",
"__UINT_LEAST16_TYPE__=short unsigned int",
"__DBL_HAS_DENORM__=1",
"__XCHAL_HAVE_DEBUG=1",
"__cpp_rtti=199711L",
"__SIZE_TYPE__=unsigned int",
"__UINT64_MAX__=0xffffffffffffffffULL",
"__FLT_IS_IEC_60559__=1",
"__GNUC_WIDE_EXECUTION_CHARSET_NAME=\"UTF-16LE\"",
"__INT8_TYPE__=signed char",
"__cpp_digit_separators=201309L",
"__ELF__=1",
"__XSHAL_ABI=0",
"__xtensa__=1",
"__FLT_RADIX__=2",
"__INT_LEAST16_TYPE__=short int",
"__LDBL_EPSILON__=2.2204460492503131e-16L",
"__UINTMAX_C(c)=c ## ULL",
"__FLT32X_MIN__=2.2250738585072014e-308F32x",
"__XCHAL_HAVE_DFP_SQRT=0",
"__SIG_ATOMIC_MAX__=0x7fffffff",
"__XCHAL_HAVE_MAC16=1",
"__GCC_ATOMIC_WCHAR_T_LOCK_FREE=2",
"__USER_LABEL_PREFIX__",
"__SIZEOF_PTRDIFF_T__=4",
"__XCHAL_MMU_MIN_PTE_PAGE_SIZE=1",
"__XCHAL_DCACHE_IS_WRITEBACK=0",
"__SIZEOF_LONG__=4",
"__LDBL_DIG__=15",
"__FLT64_IS_IEC_60559__=1",
"__XCHAL_MAX_INSTRUCTION_SIZE=3",
"__FLT32X_MIN_EXP__=(-1021)",
"__INT_FAST16_MAX__=0x7fffffff",
"__GCC_CONSTRUCTIVE_SIZE=32",
"__FLT64_DIG__=15",
"__UINT_FAST32_MAX__=0xffffffffU",
"__UINT_LEAST64_TYPE__=long long unsigned int",
"__FLT_HAS_QUIET_NAN__=1",
"__FLT_MAX_10_EXP__=38",
"__FLT_HAS_INFINITY__=1",
"__GNUC_EXECUTION_CHARSET_NAME=\"UTF-8\"",
"__CHAR_UNSIGNED__=1",
"__cpp_unicode_literals=200710L",
"__UINT_FAST16_TYPE__=unsigned int",
"__INT_FAST32_WIDTH__=32",
"__CHAR16_TYPE__=short unsigned int",
"__PRAGMA_REDEFINE_EXTNAME=1",
"__SIZE_WIDTH__=32",
"__INT_LEAST16_MAX__=0x7fff",
"__INT64_MAX__=0x7fffffffffffffffLL",
"__FLT32_DENORM_MIN__=1.4012984643248171e-45F32",
"__SIG_ATOMIC_WIDTH__=32",
"__INT_LEAST64_TYPE__=long long int",
"__INT16_TYPE__=short int",
"__INT_LEAST8_TYPE__=signed char",
"__cpp_structured_bindings=201606L",
"__INT_FAST8_MAX__=0x7fffffff",
"__INTPTR_MAX__=0x7fffffff",
"__cpp_sized_deallocation=201309L",
"__cpp_guaranteed_copy_elision=201606L",
"__FLT64_HAS_QUIET_NAN__=1",
"__FLT32_MIN_10_EXP__=(-37)",
"__EXCEPTIONS=1",
"__UINT16_C(c)=c",
"__XCHAL_M_STAGE=3",
"__PTRDIFF_WIDTH__=32",
"__LDBL_MANT_DIG__=53",
"__cpp_range_based_for=201603L",
"__FLT64_HAS_INFINITY__=1",
"__STDCPP_DEFAULT_NEW_ALIGNMENT__=8",
"__SIG_ATOMIC_MIN__=(-__SIG_ATOMIC_MAX__ - 1)",
"__XCHAL_ICACHE_SIZE=0",
"__cpp_nontype_template_args=201411L",
"__INTPTR_TYPE__=int",
"__UINT16_TYPE__=short unsigned int",
"__WCHAR_TYPE__=short unsigned int",
"__XCHAL_HAVE_DEPBITS=0",
"__SIZEOF_FLOAT__=4",
"__UINTPTR_MAX__=0xffffffffU",
"__INT_FAST64_WIDTH__=64",
"__cpp_decltype=200707L",
"__FLT32_DECIMAL_DIG__=9",
"__INT_FAST64_MAX__=0x7fffffffffffffffLL",
"__GCC_ATOMIC_TEST_AND_SET_TRUEVAL=1",
"__FLT_NORM_MAX__=3.4028234663852886e+38F",
"__XCHAL_HAVE_DFP=0",
"__FLT32_HAS_INFINITY__=1",
"__UINT_FAST64_TYPE__=long long unsigned int",
"__cpp_inline_variables=201606L",
"__INT_MAX__=0x7fffffff",
"__XCHAL_HAVE_EXCLUSIVE=0",
"__STDCPP_THREADS__=1",
"__INT64_TYPE__=long long int",
"__XCHAL_HAVE_DFP_DIV=0",
"__FLT_MAX_EXP__=128",
"__XCHAL_INST_FETCH_WIDTH=4",
"__DBL_MANT_DIG__=53",
"__cpp_inheriting_constructors=201511L",
"__INT_LEAST64_MAX__=0x7fffffffffffffffLL",
"__FP_FAST_FMAF32=1",
"__WINT_TYPE__=unsigned int",
"__UINT_LEAST32_TYPE__=long unsigned int",
"__SIZEOF_SHORT__=2",
"__FLT32_NORM_MAX__=3.4028234663852886e+38F32",
"__LDBL_MIN_EXP__=(-1021)",
"__XCHAL_HAVE_S32C1I=1",
"__FLT64_MAX__=1.7976931348623157e+308F64",
"__WINT_WIDTH__=32",
"__cpp_template_auto=201606L",
"__INT_LEAST64_WIDTH__=64",
"__FLT32X_MAX_10_EXP__=308",
"__cpp_namespace_attributes=201411L",
"__WCHAR_UNSIGNED__=1",
"__LDBL_MAX_10_EXP__=308",
"__ATOMIC_RELAXED=0",
"__DBL_EPSILON__=double(2.2204460492503131e-16L)",
"__XCHAL_HAVE_SEXT=1",
"__INT_LEAST32_TYPE__=long int",
"__XTENSA_WINDOWED_ABI__=1",
"__UINT8_C(c)=c",
"__FLT64_MAX_EXP__=1024",
"__SIZEOF_WCHAR_T__=2",
"__XCHAL_HAVE_FP_POSTINC=1",
"__FLT64_NORM_MAX__=1.7976931348623157e+308F64",
"__INTMAX_MAX__=0x7fffffffffffffffLL",
"__INT_FAST8_TYPE__=int",
"__XCHAL_HAVE_MUL32_HIGH=1",
"__GNUC_STDC_INLINE__=1",
"__FLT64_HAS_DENORM__=1",
"__FLT32_EPSILON__=1.1920928955078125e-7F32",
"__DBL_DECIMAL_DIG__=17",
"__STDC_UTF_32__=1",
"__XCHAL_HAVE_FP_DIV=1",
"__INT_FAST8_WIDTH__=32",
"__FLT32X_MAX__=1.7976931348623157e+308F32x",
"__DBL_NORM_MAX__=double(1.7976931348623157e+308L)",
"__BYTE_ORDER__=__ORDER_LITTLE_ENDIAN__",
"__GCC_DESTRUCTIVE_SIZE=32",
"__XTENSA__=1",
"__INTMAX_WIDTH__=64",
"__ORDER_BIG_ENDIAN__=4321",
"__XTHAL_ABI_CALL0=1",
"__cpp_runtime_arrays=198712L",
"__FLT32_DIG__=6",
"__UINT64_TYPE__=long long unsigned int",
"__UINT32_C(c)=c ## UL",
"__cpp_alias_templates=200704L",
"__FLT_DENORM_MIN__=1.4012984643248171e-45F",
"__INT8_MAX__=0x7f",
"__LONG_WIDTH__=32",
"__UINT_FAST32_TYPE__=unsigned int",
"__FLT32X_NORM_MAX__=1.7976931348623157e+308F32x",
"__CHAR32_TYPE__=long unsigned int",
"__FLT_MAX__=3.4028234663852886e+38F",
"__cpp_constexpr=201603L",
"__XCHAL_HAVE_FP_RSQRT=1",
"__INT32_TYPE__=long int",
"__SIZEOF_DOUBLE__=8",
"__cpp_exceptions=199711L",
"__FLT_MIN_10_EXP__=(-37)",
"__FLT64_MIN__=2.2250738585072014e-308F64",
"__INT_LEAST32_WIDTH__=32",
"__INTMAX_TYPE__=long long int",
"__XCHAL_HAVE_ABS=1",
"__FLT32X_HAS_QUIET_NAN__=1",
"__ATOMIC_CONSUME=1",
"__XCHAL_NUM_DBREAK=2",
"__XCHAL_HAVE_WIDE_BRANCHES=0",
"__GNUC_MINOR__=2",
"__INT_FAST16_WIDTH__=32",
"__UINTMAX_MAX__=0xffffffffffffffffULL",
"__FLT32X_DENORM_MIN__=4.9406564584124654e-324F32x",
"__cpp_template_template_args=201611L",
"__DBL_MAX_10_EXP__=308",
"__LDBL_DENORM_MIN__=4.9406564584124654e-324L",
"__INT16_C(c)=c",
"__STDC__=1",
"__PTRDIFF_TYPE__=int",
"__LONG_MAX__=0x7fffffffL",
"__XCHAL_HAVE_FP_SQRT=1",
"__UINT32_TYPE__=long unsigned int",
"__FLT32X_MIN_10_EXP__=(-307)",
"__UINTPTR_TYPE__=unsigned int",
"__LDBL_MIN_10_EXP__=(-307)",
"__cpp_generic_lambdas=201304L",
"__SIZEOF_LONG_LONG__=8",
"__cpp_user_defined_literals=200809L",
"__GCC_ATOMIC_LLONG_LOCK_FREE=1",
"__FLT_DECIMAL_DIG__=9",
"__UINT_FAST16_MAX__=0xffffffffU",
"__LDBL_NORM_MAX__=1.7976931348623157e+308L",
"__GCC_ATOMIC_SHORT_LOCK_FREE=2",
"__XCHAL_HAVE_BE=0",
"__UINT_FAST8_TYPE__=unsigned int",
"__cpp_init_captures=201304L",
"__ATOMIC_ACQ_REL=4",
"__ATOMIC_RELEASE=3",
"USBCON"
]
}
]
}

View File

@ -1,8 +0,0 @@
{
"C_Cpp.errorSquiggles": "enabled",
"C_Cpp.default.compilerPath": "~/.arduino15/packages/esp32/tools/esp-x32/2411/bin/xtensa-esp32-elf-g++",
"files.associations": {
".fantomasignore": "ignore",
"string_view": "cpp"
}
}

355
README.md
View File

@ -1,313 +1,118 @@
[![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml)
# DShotRMT - ESP32 RMT DShot Driver
# DShotRMT - ESP32 Library (Rewrite for ESP-IDF 5)
[![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml/badge.svg)](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml)
A modern, robust C++ library for generating DShot signals on the ESP32 using the new ESP-IDF 5 RMT encoder API (`rmt_tx.h` / `rmt_rx.h`).
Supports all standard DShot modes (150, 300, 600, 1200) and features continuous frame transmission with configurable timing.
A C++ library for generating DShot signals on ESP32 microcontrollers using the RMT (Remote Control) peripheral. It's designed for both Arduino and ESP-IDF projects, providing a simple and efficient way to control brushless motors.
**Now with BiDirectional DShot support, advanced command management, and modern web control interface!**
This library is a rewrite using the modern ESP-IDF 5 RMT encoder API (`rmt_tx.h` / `rmt_rx.h`) for improved performance and flexibility. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch.
> The legacy version (using the old `rmt.h` API) is still available in the `oldAPI` branch.
## 🚀 Core Features
---
## 🚀 Features
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, DSHOT1200
- **BiDirectional DShot:** Full support for RPM telemetry feedback
- **Web Control Interface:** Modern responsive web UI with WiFi access point
- **Advanced Command Manager:** High-level API for ESC configuration and control
- **Safety Features:** Arming/disarming system with motor lockout protection
- **Dual Control Options:** Web interface and serial console control
- **Real-time Telemetry:** Live RPM monitoring and data display
- **Hardware-Timed Signals:** Independent, precise signal generation using ESP32 RMT peripheral
- **Configurable Timing:** Ensures ESCs can reliably detect frame boundaries
- **Error Handling:** Comprehensive result reporting with success/failure status
- **Simple API:** Easy integration into your Arduino or ESP-IDF project
---
- **Multiple DShot Modes:** Supports DSHOT150, DSHOT300, DSHOT600, and DSHOT1200.
- **Bidirectional DShot:** Implemented, but currently not officially supported due to instability and external hardware requirements.
- **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()`.
- **Efficient and Lightweight:** The core library has no external dependencies.
- **Arduino and ESP-IDF Compatible:** Can be used in both Arduino and ESP-IDF projects.
## 📦 Installation
### Arduino IDE
1. Search "Arduino Library Manager" for "DShotRMT"
or
1. Clone this repository or download as ZIP
2. Place in your Arduino libraries folder (`~/Arduino/libraries/DShotRMT/`)
3. Restart Arduino IDE
1. Open the Arduino Library Manager (`Sketch` > `Include Library` > `Manage Libraries...`).
2. Search for "DShotRMT" and click "Install".
3. Alternatively, you can clone this repository or download it as a ZIP file and place it in your Arduino libraries folder (`~/Arduino/libraries/DShotRMT/`).
### PlatformIO
Add to your `platformio.ini`:
Add the following to your `platformio.ini` file:
```ini
lib_deps =
https://github.com/derdoktor667/DShotRMT.git
```
### Manual Installation
```sh
git clone https://github.com/derdoktor667/DShotRMT.git
## ⚡ Quick Start
Here's a basic example of how to use the `DShotRMT` library to control a motor:
```cpp
#include <Arduino.h>
#include <DShotRMT.h>
// Define the GPIO pin connected to the motor ESC
const gpio_num_t MOTOR_PIN = GPIO_NUM_27;
// Create a DShotRMT instance for DSHOT300
DShotRMT motor(MOTOR_PIN, DSHOT300);
void setup() {
Serial.begin(115200);
// Initialize the DShot motor
motor.begin();
Serial.println("Motor initialized. Ramping up to 25% throttle...");
// Ramp up to 25% throttle over 2.5 seconds
for (int i = 0; i <= 25; i++) {
motor.sendThrottlePercent(i);
delay(100);
}
Serial.println("Stopping motor.");
motor.sendThrottlePercent(0);
}
void loop() {
// Your main code here
}
```
### Dependencies
## 🎮 Examples
The library requires these additional libraries for full functionality:
The `examples` folder contains more advanced examples:
**Core DShotRMT (always required):**
- ESP32 Arduino Core
- **`throttle_percent`:** A focused example showing how to control motor speed using percentage values (0-100) via the serial monitor.
- **`dshot300`:** A more advanced example demonstrating how to send raw DShot commands and receive telemetry via the serial monitor.
- **`web_control`:** A full-featured web application for controlling a motor from a web browser. It creates a WiFi access point and serves a web page with a throttle slider and arming switch.
- **`web_client`:** A variation of the `web_control` example that connects to an existing WiFi network instead of creating its own access point.
### Dependencies for Web Examples
The `web_control` and `web_client` examples require the following additional libraries:
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
- [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer)
- [AsyncTCP](https://github.com/ESP32Async/AsyncTCP)
You can install these libraries using the Arduino Library Manager or by adding them to your `platformio.ini` file:
**Web Interface Example (web_control.ino / web_client.ino):**
```ini
lib_deps =
https://github.com/derdoktor667/DShotRMT
https://github.com/derdoktor667/DShotRMT.git
https://github.com/bblanchon/ArduinoJson
https://github.com/ESP32Async/ESPAsyncWebServer
https://github.com/ESP32Async/AsyncTCP
```
**Command Manager Example:**
- No additional dependencies required
## 📚 API Reference
---
The main class is `DShotRMT`. Here are the most important methods:
## ⚡ Quick Start
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional = false)`: Constructor to create a new DShotRMT instance. (Note: Bidirectional DShot is currently not officially supported.)
- `begin()`: Initializes the RMT peripheral and the DShot encoder.
- `sendThrottlePercent(float percent)`: Sends a throttle value as a percentage (0.0-100.0).
- `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the motor.
- `sendCommand(uint16_t command)`: Sends a DShot command (0-47) to the motor.
- `getTelemetry(uint16_t magnet_count)`: Receives and parses telemetry data from the motor (for bidirectional DShot, which is currently not officially supported).
### Basic Usage (DShotRMT)
```cpp
#include <DShotRMT.h>
// Create motor instance (GPIO 17, DSHOT300, non-bidirectional)
DShotRMT motor(17, DSHOT300, false);
void setup() {
Serial.begin(115200);
// Initialize the motor
dshot_result_t result = motor.begin();
if (result.success) {
Serial.println("Motor initialized successfully");
} else {
Serial.printf("Motor init failed: %s\n", result.msg);
}
}
void loop() {
// Send throttle value (48-2047)
dshot_result_t result = motor.sendThrottle(1000);
if (!result.success) {
Serial.printf("Throttle command failed: %s\n", result.msg);
}
}
```
---
## 🌐 Web Control Interface
The DShotRMT library now includes a modern web interface for wireless motor control:
### Features
- **Responsive Design:** Works on mobile phones, tablets, and desktop computers
- **WiFi Access Point:** Creates hotspot "DShotRMT Control" (Password: 12345678)
- **Safety System:** Arming/disarming switch prevents accidental motor activation
- **Real-time Control:** Instant throttle response via WebSocket communication
- **Live Telemetry:** Real-time RPM display (bidirectional mode only)
- **Auto-reconnect:** Automatically reconnects on connection loss
### Web Interface Access
1. Connect to WiFi network: **"DShotRMT Control"**
2. Password: **12345678**
3. Open browser and navigate to: **http://10.10.10.1**
### Safety Features
- Motor control is **disabled by default** (disarmed state)
- Toggle the **ARMING SWITCH** to enable motor control
- Throttle slider is **locked** when disarmed
- **Emergency stop** resets all values to safe state
### Technical Implementation
- **AsyncWebServer** for HTTP requests
- **WebSocket** communication for real-time data
- **JSON** message format for data exchange
- **WiFi SoftAP** mode for standalone operation
- **Automatic client cleanup** prevents memory leaks
### ⚠️ Known Issus
Make sure you are using these libraries for [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) and [AsyncTCP](https://github.com/ESP32Async/AsyncTCP) to use "web_control.ino" example sketch.
---
## 📚 Examples
The library includes comprehensive examples:
### 1. Basic DShot Control with Web Interface (`web_control.ino`)
- **Web Control Interface:** Modern responsive web UI accessible at `http://10.10.10.1`
- **WiFi Access Point:** Creates hotspot "DShotRMT Control" for wireless control
- **Safety Features:** Arming/disarming system with motor safety lockout
- **Real-time Data:** Live RPM telemetry display (bidirectional mode)
- **Dual Control:** Both web interface and serial console control
- **WebSocket Communication:** Real-time bidirectional data exchange
**Web Interface Features:**
- Responsive design optimized for mobile and desktop
- Visual arming switch with safety lockout
- Smooth throttle slider with real-time feedback
- Live RPM monitoring display
- Automatic reconnection on connection loss
### 2. Advanced Command Management (`command_manager.ino`)
Interactive ESC control with full menu system:
```
=== DShot Command Manager Menu ===
1 - Stop Motor
2 - Activate Beacon 1
3 - Set Normal Spin Direction
4 - Set Reversed Spin Direction
5 - Get ESC Info
6 - Turn LED 0 ON
7 - Turn LED 0 OFF
0 - Emergency Stop
Advanced Commands:
cmd <number> - Send DShot command (0 - 47)
throttle <value> - Set throttle (48 - 2047)
repeat cmd <num> count <count> - Repeat command
```
---
## 🔧 Hardware Configuration
### Supported DShot Modes
| 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 |
For DShot, T1H length is always double T0H length.
### GPIO Configuration
```cpp
// Using GPIO number
DShotRMT motor(17, DSHOT300);
// Using GPIO enum
DShotRMT motor(GPIO_NUM_17, DSHOT300);
// With bidirectional support
DShotRMT motor(17, DSHOT300, true);
```
---
## 🎯 DShot Commands
| Command | Value | Description | Usage |
|---------|-------|-------------|-------|
| MOTOR_STOP | 0 | Stop motor | Always available |
| BEACON 1 - 5 | 1 - 5 | Motor beeping | Motor identification |
| ESC_INFO | 6 | Request ESC info | Get ESC version/settings |
| SPIN_DIRECTION_1/2 | 7 - 8 | Set spin direction | Motor configuration |
| 3D_MODE_OFF/ON | 9 - 10 | 3D mode control | Bidirectional flight |
| SAVE_SETTINGS | 12 | Save to EEPROM | Permanent configuration |
| EXTENDED_TELEMETRY_ENABLE/DISABLE | 13 - 14 | Telemetry control | Data transmission |
| SPIN_DIRECTION_NORMAL/REVERSED | 20 - 21 | Spin direction | Alias commands |
| LED 0-3_ON/OFF | 22 - 29 | LED control | BLHeli32 only |
| AUDIO_STREAM_MODE | 30 | Audio mode toggle | KISS ESCs |
| SILENT_MODE | 31 | Silent mode toggle | KISS ESCs |
---
## 📚 DShot Protocol Details
![DShotRMT](https://raw.githubusercontent.com/derdoktor667/DShotRMT/refs/heads/main/img/dshot300.png)
### Packet Structure
Each DShot frame consists of 16 bits:
- **11 bits:** Throttle/command value (0-2047)
- **1 bit:** Telemetry request flag
- **4 bits:** CRC checksum
### Checksum Calculation
```cpp
// Standard DShot CRC
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & 0x0F;
// Bidirectional DShot (inverted CRC)
uint16_t crc = (~(data ^ (data >> 4) ^ (data >> 8))) & 0x0F;
```
### Bidirectional DShot
- **Inverted Logic:** High/low levels are inverted
- **GCR Encoding:** Telemetry uses Group Code Recording
- **21-bit Response:** 1 start + 16 data + 4 CRC bits
- **eRPM Data:** Electrical RPM transmitted back to controller
---
## 🛠️ ESP32 RMT Peripheral
The library utilizes the ESP32's RMT (Remote Control) peripheral for precise signal generation:
### Advantages
- **Hardware Timing:** No CPU intervention during transmission
- **Concurrent Operation:** Multiple channels can run simultaneously
- **DMA Support:** Efficient, automatic memory-to-peripheral transfers
---
## 📖 References & Documentation
### DShot Protocol
- [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/)
- [Betaflight DShot Implementation](https://github.com/betaflight/betaflight)
### ESP32 Documentation
- [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
- [ESP-IDF RMT Driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html)
- [Arduino ESP32 Core](https://github.com/espressif/arduino-esp32)
---
For more details, please refer to the `DShotRMT.h` header file.
## 🤝 Contributing
We welcome contributions! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes with tests
4. Submit a pull request
### Development Guidelines
- Follow existing code style
- Add documentation for new features
- Include examples where appropriate
- Test with real hardware when possible
### Reporting Issues
When reporting issues, please include:
- ESP32 board type and version
- Arduino/ESP-IDF version
- ESC type and firmware
- Complete error messages
- Minimal reproduction code
---
Contributions are welcome! Please fork the repository, create a feature branch, and submit a pull request.
## 📄 License
MIT License see [LICENSE](LICENSE)
---
## 👤 Author
**Wastl Kraus**
- GitHub: [@derdoktor667](https://github.com/derdoktor667)
- Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

View File

@ -1,345 +0,0 @@
/*
* command_manager.ino
* Example sketch for DShotCommandManager
* Author: Wastl Kraus
* Date: 2025-09-04
* License: MIT
*/
#include <Arduino.h>
#include <DShotRMT.h>
#include <DShotCommandManager.h>
// USB serial port settings
static constexpr auto &USB_SERIAL = Serial0;
static constexpr auto USB_SERIAL_BAUD = 115200;
// Motor configuration
static constexpr auto MOTOR01_PIN = 17;
static constexpr auto IS_BIDIRECTIONAL = false;
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Create motor and command manager instances
DShotRMT motor01(MOTOR01_PIN, DSHOT300, IS_BIDIRECTIONAL);
DShotCommandManager commandManager(motor01);
// Global variable to store the desired continuous throttle value
static volatile uint16_t throttle_now = 0;
// Helper function to print telemetry results
void printTelemetryResult(const dshot_result_t &result)
{
if (result.success && (result.erpm > 0 || result.motor_rpm > 0))
{
USB_SERIAL.printf("Telemetry: eRPM=%u, Motor RPM=%u\n", result.erpm, result.motor_rpm);
}
else
{
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.msg);
}
}
//
void setup()
{
// Start USB Serial Port
USB_SERIAL.begin(USB_SERIAL_BAUD);
// Initialize DShot
motor01.begin();
// Init Command Manager
commandManager.begin();
// Print Menu
printMenu();
}
//
void loop()
{
// Time Measurement
static uint64_t last_stats_print = 0;
// Check for serial input
if (USB_SERIAL.available() > 0)
{
String input = USB_SERIAL.readStringUntil('\n');
input.trim();
handleUserInput(input);
}
// Continuously send the stored throttle value
if (throttle_now != 0)
{
dshot_result_t result = motor01.sendThrottle(throttle_now);
// Only print errors to avoid spam
if (!result.success)
{
printResult(result);
}
// Print motor stats every 2 seconds
if (esp_timer_get_time() - last_stats_print >= 2000000)
{
motor01.printDShotInfo();
// Get Motor RPM
if (IS_BIDIRECTIONAL)
{
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
printTelemetryResult(telem_result);
}
// Time Stamp
last_stats_print = esp_timer_get_time();
}
}
}
//
void handleUserInput(const String &input)
{
dshot_result_t cmd_result;
if (input == "1")
{
// Stop motor command should also reset the continuous throttle value
throttle_now = 0;
USB_SERIAL.print("Stopping motor... ");
cmd_result = commandManager.stopMotor();
printResult(cmd_result);
return;
}
if (input == "2")
{
USB_SERIAL.print("Activating beacon 1... ");
cmd_result = commandManager.activateBeacon(1);
printResult(cmd_result);
return;
}
if (input == "3")
{
USB_SERIAL.print("Setting normal spin direction... ");
cmd_result = commandManager.setSpinDirection(false);
printResult(cmd_result);
return;
}
if (input == "4")
{
USB_SERIAL.print("Setting reversed spin direction... ");
cmd_result = commandManager.setSpinDirection(true);
printResult(cmd_result);
return;
}
if (input == "5")
{
USB_SERIAL.print("Getting ESC Info... ");
cmd_result = commandManager.requestESCInfo();
printResult(cmd_result);
return;
}
if (input == "6")
{
USB_SERIAL.print("Turning LED 0 ON... ");
cmd_result = commandManager.setLED(0, true);
printResult(cmd_result);
return;
}
if (input == "7")
{
USB_SERIAL.print("Turning LED 0 OFF... ");
cmd_result = commandManager.setLED(0, false);
printResult(cmd_result);
return;
}
if (input == "h" || input == "help")
{
printMenu();
return;
}
if (input == "info")
{
motor01.printDShotInfo();
return;
}
if (input == "rpm" && IS_BIDIRECTIONAL)
{
dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
printTelemetryResult(result);
return;
}
if (input.startsWith("cmd "))
{
// Direct command execution: "cmd 5" sends command 5
int cmd_num = input.substring(4).toInt();
if (DShotCommandManager::isValidCommand(static_cast<dshot_commands_t>(cmd_num)))
{
USB_SERIAL.printf("Sending command %d (%s)... ", cmd_num,
DShotCommandManager::getCommandName(static_cast<dshot_commands_t>(cmd_num)));
cmd_result = commandManager.sendCommand(static_cast<dshot_commands_t>(cmd_num));
printResult(cmd_result);
}
else
{
USB_SERIAL.printf("Invalid command number: %d (valid range: 0 - %d)\n", cmd_num, DSHOT_CMD_MAX);
}
return;
}
if (input.startsWith("throttle "))
{
// Throttle control: "throttle 1000" sets throttle to 1000
int throttle_value = input.substring(9).toInt();
if (throttle_value >= DSHOT_THROTTLE_MIN && throttle_value <= DSHOT_THROTTLE_MAX)
{
throttle_now = throttle_value;
USB_SERIAL.printf("Setting continuous throttle to %d\n", throttle_now);
// Send first throttle command and show result
dshot_result_t result = motor01.sendThrottle(throttle_now);
printResult(result);
if (result.success)
{
USB_SERIAL.println("Continuous throttle mode enabled. Send '0' or 'throttle 0' to stop.");
}
return;
}
if (throttle_value == 0)
{
throttle_now = 0;
USB_SERIAL.println("Continuous throttle stopped.");
// Send stop command
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
printResult(result);
return;
}
USB_SERIAL.printf("Invalid throttle value: %d (valid range: %d-%d, use 0 to stop)\n",
throttle_value, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
return;
}
if (input == "0")
{
// Quick stop
throttle_now = 0;
USB_SERIAL.print("Emergency stop... ");
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
printResult(result);
return;
}
if (input.startsWith("repeat "))
{
// Repeat command: "repeat cmd 5 count 10" - sends command 5 ten times
String params = input.substring(7);
if (!params.startsWith("cmd "))
{
USB_SERIAL.println("Usage: repeat cmd <number> count <repeat_count>");
return;
}
int space_pos = params.indexOf(' ', 4);
if (space_pos <= 0 || !params.substring(space_pos + 1).startsWith("count "))
{
USB_SERIAL.println("Usage: repeat cmd <number> count <repeat_count>");
return;
}
int cmd_num = params.substring(4, space_pos).toInt();
int repeat_count = params.substring(space_pos + 7).toInt();
if (!DShotCommandManager::isValidCommand(static_cast<dshot_commands_t>(cmd_num)) ||
repeat_count <= 0 || repeat_count > 100)
{
USB_SERIAL.println("Invalid command or repeat count (1-100)");
return;
}
USB_SERIAL.printf("Sending command %d (%s) %d times... ", cmd_num,
DShotCommandManager::getCommandName(static_cast<dshot_commands_t>(cmd_num)),
repeat_count);
cmd_result = commandManager.sendCommand(static_cast<dshot_commands_t>(cmd_num), repeat_count);
printResult(cmd_result);
return;
}
// Unknown command
USB_SERIAL.printf("Unknown command: '%s'. Type 'h' or 'help' for help.\n", input.c_str());
}
//
void printResult(const dshot_result_t &result)
{
if (result.success)
{
USB_SERIAL.printf("SUCCESS\n");
}
else
{
USB_SERIAL.printf("FAILED - %s \n", result.msg);
}
}
//
void printSystemStatus()
{
USB_SERIAL.println("\n=== System Status ===");
USB_SERIAL.printf("Current throttle: %u\n", throttle_now);
USB_SERIAL.printf("Continuous mode: %s\n", throttle_now > 0 ? "ACTIVE" : "INACTIVE");
USB_SERIAL.printf("Free heap: %u bytes\n", ESP.getFreeHeap());
USB_SERIAL.printf("Uptime: %lu seconds\n", millis() / 1000);
}
//
void printMenu()
{
USB_SERIAL.println("**********************************************");
USB_SERIAL.println(" DShot Command Manager Menu ");
USB_SERIAL.println("**********************************************");
USB_SERIAL.println(" 1 - Stop Motor");
USB_SERIAL.println(" 2 - Activate Beacon 1");
USB_SERIAL.println(" 3 - Set Normal Spin");
USB_SERIAL.println(" 4 - Set Reversed Spin");
USB_SERIAL.println(" 5 - Get ESC Info");
USB_SERIAL.println(" 6 - Turn LED 0 ON");
USB_SERIAL.println(" 7 - Turn LED 0 OFF");
USB_SERIAL.println(" 0 - Emergency Stop");
USB_SERIAL.println("**********************************************");
USB_SERIAL.println(" cmd <number> - Send Command (0 - 47)");
USB_SERIAL.println(" throttle <value> - Set throttle (48 - 2047)");
USB_SERIAL.println("**********************************************");
USB_SERIAL.println(" info - Show DShot signal info");
USB_SERIAL.println(" status - Show system status");
if (IS_BIDIRECTIONAL)
{
USB_SERIAL.println(" rpm - Get telemetry data");
}
USB_SERIAL.println(" h / help - Show this Menu");
USB_SERIAL.println("**********************************************");
USB_SERIAL.println("EXAMPLE INPUT:");
USB_SERIAL.println(" cmd 5 - Get ESC Info");
USB_SERIAL.println(" throttle 1000 - Set throttle to 1000");
USB_SERIAL.println("**********************************************");
}

View File

@ -14,20 +14,22 @@ static constexpr auto &USB_SERIAL = Serial0;
static constexpr auto USB_SERIAL_BAUD = 115200;
// Motor configuration - Pin number or GPIO_PIN
// static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_17;
static constexpr auto MOTOR01_PIN = 17;
static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// static constexpr auto MOTOR01_PIN = 17;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false)
// Note: Bidirectional DShot is currently not officially supported
// due to instability and external hardware requirements.
static constexpr auto IS_BIDIRECTIONAL = false;
// Motor magnet count for RPM calculation
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);
//
void setup()
@ -50,6 +52,8 @@ void loop()
{
// Safety first
static uint16_t throttle = DSHOT_CMD_MOTOR_STOP;
// Initialize the esc with "0"
static bool continuous_throttle = true;
// Time Measurement
@ -83,7 +87,7 @@ void loop()
// Get Motor RPM if bidirectional
if (IS_BIDIRECTIONAL)
{
dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
dshot_result_t telem_result = motor01.getTelemetry();
printDShotResult(telem_result);
}
@ -132,7 +136,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
}
else if (input == "rpm" && IS_BIDIRECTIONAL)
{
dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
dshot_result_t result = motor01.getTelemetry();
printDShotResult(result);
}
else if (input.startsWith("cmd "))

View File

@ -0,0 +1,144 @@
/**
* @file throttle_percent.ino
* @brief Demo sketch for DShotRMT library using percentage throttle.
* @author Wastl Kraus
* @date 2025-09-20
* @license MIT
*/
#include <Arduino.h>
#include <DShotRMT.h>
// USB serial port settings
static constexpr auto &USB_SERIAL = Serial0;
static constexpr auto USB_SERIAL_BAUD = 115200;
// Motor configuration - Pin number or GPIO_PIN
static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false)
// Note: Bidirectional DShot is currently not officially supported
// due to instability and external hardware requirements.
static constexpr auto IS_BIDIRECTIONAL = false;
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Creates the motor instance
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL, MOTOR01_MAGNET_COUNT);
// Forward declaration
void handleSerialInput(const String &input);
void printMenu();
//
void setup()
{
// Starts the USB Serial Port
USB_SERIAL.begin(USB_SERIAL_BAUD);
// Initialize DShot Signal
motor01.begin();
// Print CPU Info
motor01.printCpuInfo();
//
printMenu();
}
//
void loop()
{
// Handle serial input
if (USB_SERIAL.available() > 0)
{
String input = USB_SERIAL.readStringUntil('\n');
input.trim();
if (input.length() > 0)
{
handleSerialInput(input);
}
}
}
//
void printMenu()
{
USB_SERIAL.println(" ");
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" DShotRMT Percent Demo ");
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" <value> - Set throttle (0 - 100)");
USB_SERIAL.println(" 0 - Stop motor");
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" cmd <number> - Send DShot command (0 - 47)");
USB_SERIAL.println(" info - Show motor info");
if (IS_BIDIRECTIONAL)
{
USB_SERIAL.println(" rpm - Get telemetry data");
}
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" h / help - Show this Menu");
USB_SERIAL.println("*******************************************");
}
//
void handleSerialInput(const String &input)
{
if (input == "0")
{
// Stop motor
dshot_result_t result = motor01.sendThrottlePercent(0.0f);
printDShotResult(result);
}
else if (input == "info")
{
motor01.printDShotInfo();
}
else if (input == "rpm" && IS_BIDIRECTIONAL)
{
dshot_result_t result = motor01.getTelemetry();
printDShotResult(result);
}
else if (input.startsWith("cmd "))
{
// Send DShot command
int cmd_num = input.substring(4).toInt();
if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX)
{
dshot_result_t result = motor01.sendCommand(cmd_num);
printDShotResult(result);
}
else
{
USB_SERIAL.printf("Invalid command: %d (valid range: 0 - %d)\n", cmd_num, DSHOT_CMD_MAX);
}
}
else if (input == "h" || input == "help")
{
printMenu();
}
else
{
// Parse input throttle value as a percentage
float throttle_percent = input.toFloat();
if (throttle_percent >= 0.0f && throttle_percent <= 100.0f)
{
dshot_result_t result = motor01.sendThrottlePercent(throttle_percent);
printDShotResult(result);
}
else
{
USB_SERIAL.println(" ");
USB_SERIAL.printf("Invalid input: '%s'\n", input.c_str());
USB_SERIAL.printf("Valid throttle range: 0.0 - 100.0\n");
}
}
}

View File

@ -6,10 +6,21 @@
* @license MIT
*/
/******************************************************************
* SECURITY WARNING
* This example provides a web interface to control a motor
* without any authentication. It is intended for use on a
* trusted local network only.
*
* DO NOT EXPOSE THIS DEVICE DIRECTLY TO THE INTERNET.
******************************************************************/
#include <Arduino.h>
#include <Update.h>
#include <WiFi.h>
#include <DShotRMT.h>
#include <ota_update.h>
#include <web_content.h>
#include <ArduinoJson.h>
@ -34,7 +45,7 @@ static constexpr auto MOTOR01_PIN = 17;
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false)
static constexpr auto IS_BIDIRECTIONAL = false;
static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements.
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
@ -62,6 +73,8 @@ void handleWebSocketMessage(void *arg, uint8_t *data, size_t len);
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
bool connectToWiFi();
void printWiFiStatus();
void setupOTA();
void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
//
void setup()
@ -79,6 +92,9 @@ void setup()
if (wifi_connected)
{
// Setup OTA first
setupOTA();
// Init WebSockets and Webserver
USB_SERIAL.println("\nStarting Webserver...");
@ -96,7 +112,7 @@ void setup()
else
{
USB_SERIAL.println("\n*** WARNING: WiFi connection failed! ***");
USB_SERIAL.println("*** Web interface not available ***");
USB_SERIAL.println("*** Web interface and OTA not available ***");
USB_SERIAL.println("*** Only serial control available ***");
}
@ -215,6 +231,87 @@ void loop()
}
}
// Setup OTA Update functionality
void setupOTA()
{
USB_SERIAL.println("Setting up OTA Update...");
// Serve OTA update page
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send_P(200, "text/html", ota_html); });
// Handle OTA update upload
server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request)
{
bool shouldReboot = !Update.hasError();
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain",
shouldReboot ? "OK" : "FAIL");
response->addHeader("Connection", "close");
request->send(response);
if (shouldReboot) {
USB_SERIAL.println("OTA Update successful! Rebooting...");
delay(1000);
ESP.restart();
} else {
USB_SERIAL.println("OTA Update failed!");
} }, handleOTAUpload);
USB_SERIAL.println("OTA Update ready at: /update");
}
// Handle OTA upload process
void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
static unsigned long ota_progress_millis = 0;
if (!index)
{
// Safety: Ensure motor is stopped during update
motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
setArmingStatus(false);
USB_SERIAL.printf("OTA Update Start: %s\n", filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN))
{
Update.printError(USB_SERIAL);
return;
}
}
if (len)
{
if (Update.write(data, len) != len)
{
Update.printError(USB_SERIAL);
return;
}
// Print progress every 2 seconds to avoid spam
if (millis() - ota_progress_millis > 2000)
{
size_t progress = index + len;
USB_SERIAL.printf("OTA Progress: %zu bytes\n", progress);
ota_progress_millis = millis();
}
}
if (final)
{
if (Update.end(true))
{
USB_SERIAL.printf("OTA Update Success: %zu bytes\n", index + len);
}
else
{
Update.printError(USB_SERIAL);
}
}
}
// Connect to WiFi network
bool connectToWiFi()
{
@ -262,6 +359,7 @@ void printWiFiStatus()
USB_SERIAL.printf("MAC Address: %s\n", WiFi.macAddress().c_str());
USB_SERIAL.println("***********************************************");
USB_SERIAL.printf("Web Interface: http://%s\n", WiFi.localIP().toString().c_str());
USB_SERIAL.printf("OTA Update: http://%s/update\n", WiFi.localIP().toString().c_str());
USB_SERIAL.println("***********************************************");
}
else
@ -305,10 +403,12 @@ void printMenu()
if (wifi_connected)
{
USB_SERIAL.printf(" Web Interface: http://%s \n", WiFi.localIP().toString().c_str());
USB_SERIAL.printf(" OTA Update: http://%s/update \n", WiFi.localIP().toString().c_str());
}
else
{
USB_SERIAL.println(" Web Interface: NOT AVAILABLE ");
USB_SERIAL.println(" OTA Update: NOT AVAILABLE ");
}
USB_SERIAL.println("***********************************************");
@ -321,6 +421,7 @@ void printMenu()
USB_SERIAL.println(" info - Show motor info");
USB_SERIAL.println(" wifi - Show WiFi status");
USB_SERIAL.println(" reconnect - Reconnect to WiFi");
USB_SERIAL.println(" ota - Show OTA info");
if (IS_BIDIRECTIONAL)
{
USB_SERIAL.println(" rpm - Get telemetry data");
@ -362,6 +463,24 @@ void handleSerialInput(const String &input)
return;
}
if (input == "ota")
{
if (wifi_connected)
{
USB_SERIAL.println(" ");
USB_SERIAL.println("=== OTA UPDATE INFO ===");
USB_SERIAL.printf("OTA Update URL: http://%s/update\n", WiFi.localIP().toString().c_str());
USB_SERIAL.printf("Free Sketch Space: %u bytes\n", ESP.getFreeSketchSpace());
USB_SERIAL.printf("Sketch Size: %u bytes\n", ESP.getSketchSize());
USB_SERIAL.println("========================");
}
else
{
USB_SERIAL.println("OTA Update not available - WiFi not connected!");
}
return;
}
if (input == "reconnect")
{
USB_SERIAL.println("Reconnecting to WiFi...");
@ -431,6 +550,7 @@ void handleSerialInput(const String &input)
if (wifi_connected)
{
USB_SERIAL.printf("IP Address: %s\n", WiFi.localIP().toString().c_str());
USB_SERIAL.printf("OTA URL: http://%s/update\n", WiFi.localIP().toString().c_str());
}
return;
}

View File

@ -6,6 +6,15 @@
* @license MIT
*/
/******************************************************************
* SECURITY WARNING
* This example provides a web interface to control a motor
* without any authentication. It is intended for use on a
* trusted local network only.
*
* DO NOT EXPOSE THIS DEVICE DIRECTLY TO THE INTERNET.
******************************************************************/
#include <Arduino.h>
#include <WiFi.h>
@ -35,7 +44,7 @@ static constexpr auto MOTOR01_PIN = 17;
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false)
static constexpr auto IS_BIDIRECTIONAL = false;
static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements.
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14;

View File

@ -1,12 +1,11 @@
name=DShotRMT
version=0.8.0
version=0.8.3
author=Wastl Kraus <wir-sind-die-matrix.de>
maintainer=Wastl Kraus <wir-sind-die-matrix.de>
license=MIT
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. Features bidirectional DShot support for RPM telemetry.
paragraph=This library can control a BlHeli_S by using encoded DShot commands.
category=Signal Input/Output
url=https://github.com/derdoktor667/DShotRMT
architectures=esp32
provides_includes=DShotRMT.h, DShotCommandManager.h, dshot_commands.h
depends=ArduinoJson
provides_includes=DShotRMT.h, dshot_commands.h, web_content.h, ota_update.h

View File

@ -1,347 +0,0 @@
/*
* DShotCommandManager.cpp
* Advanced DShot command management for DShotRMT library
* Author: Wastl Kraus
* Date: 2025-09-04
* License: MIT
*/
#include <DShotCommandManager.h>
// Constructor
DShotCommandManager::DShotCommandManager(DShotRMT &dshot_instance)
: _dshot(dshot_instance),
_total_commands_sent(0),
_failed_commands(0),
_last_command_timestamp(0)
{
}
// Init command manager
dshot_result_t DShotCommandManager::begin()
{
dshot_result_t result;
result.success = true;
result.msg = "Success";
return result;
}
// --- BASIC COMMAND METHODS ---
dshot_result_t DShotCommandManager::sendCommand(dshot_commands_t command, uint16_t repeat_count)
{
return sendCommandWithDelay(command, repeat_count, DEFAULT_COMMAND_DELAY_MS);
}
//
dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms)
{
dshot_result_t result = {false, "Unknown error"};
if (!isValidCommand(command))
{
result.msg = "Invalid command";
return result;
}
bool all_successful = true;
// Send command multiple times with delay
for (uint16_t i = 0; i < repeat_count; i++)
{
dshot_result_t single_result = _executeCommand(command);
if (!single_result.success)
{
all_successful = false;
result.msg = single_result.msg;
break;
}
// Add delay between repetitions (except for last repetition)
if (i < repeat_count - 1)
{
_delay_ms(delay_ms);
}
}
//
result.success = all_successful;
if (result.success)
{
result.msg = "Success";
}
return result;
}
// --- MOTOR CONTROL COMMANDS ---
dshot_result_t DShotCommandManager::stopMotor()
{
return sendCommand(DSHOT_CMD_MOTOR_STOP);
}
//
dshot_result_t DShotCommandManager::set3DMode(bool enable)
{
dshot_commands_t command = enable ? DSHOT_CMD_3D_MODE_ON : DSHOT_CMD_3D_MODE_OFF;
return sendCommandWithDelay(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
}
//
dshot_result_t DShotCommandManager::setSpinDirection(bool reversed)
{
dshot_commands_t command = reversed ? DSHOT_CMD_SPIN_DIRECTION_REVERSED : DSHOT_CMD_SPIN_DIRECTION_NORMAL;
return sendCommandWithDelay(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
}
//
dshot_result_t DShotCommandManager::saveSettings()
{
return sendCommandWithDelay(DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
}
// --- TELEMETRY COMMANDS ---
dshot_result_t DShotCommandManager::setExtendedTelemetry(bool enable)
{
dshot_commands_t command = enable ? DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE : DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE;
return sendCommand(command);
}
//
dshot_result_t DShotCommandManager::requestESCInfo()
{
return sendCommand(DSHOT_CMD_ESC_INFO);
}
// --- LED CONTROL COMMANDS ---
dshot_result_t DShotCommandManager::setLED(uint8_t led_number, bool state)
{
if (led_number > 3)
{
dshot_result_t result = {false, "Invalid LED number (0-3)"};
return result;
}
dshot_commands_t command;
if (state)
{
// LED ON commands
switch (led_number)
{
case 0:
command = DSHOT_CMD_LED0_ON;
break;
case 1:
command = DSHOT_CMD_LED1_ON;
break;
case 2:
command = DSHOT_CMD_LED2_ON;
break;
case 3:
command = DSHOT_CMD_LED3_ON;
break;
}
}
else
{
// LED OFF commands
switch (led_number)
{
case 0:
command = DSHOT_CMD_LED0_OFF;
break;
case 1:
command = DSHOT_CMD_LED1_OFF;
break;
case 2:
command = DSHOT_CMD_LED2_OFF;
break;
case 3:
command = DSHOT_CMD_LED3_OFF;
break;
}
}
return sendCommand(command);
}
// --- BEACON COMMANDS ---
dshot_result_t DShotCommandManager::activateBeacon(uint8_t beacon_number)
{
if (beacon_number < 1 || beacon_number > 5)
{
dshot_result_t result = {false, "Invalid beacon number (1-5)"};
return result;
}
dshot_commands_t command = static_cast<dshot_commands_t>(DSHOT_CMD_BEACON1 + beacon_number - 1);
return sendCommand(command);
}
// --- KISS ESC SPECIFIC COMMANDS ---
dshot_result_t DShotCommandManager::setAudioStreamMode(bool enable)
{
// KISS audio stream mode is a toggle command
return sendCommand(DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF);
}
//
dshot_result_t DShotCommandManager::setSilentMode(bool enable)
{
// KISS silent mode is a toggle command
return sendCommand(DSHOT_CMD_SILENT_MODE_ON_OFF);
}
// --- SEQUENCE COMMANDS ---
dshot_result_t DShotCommandManager::executeSequence(const dshot_commandmanager_item_t *sequence, size_t sequence_length)
{
dshot_result_t result = {true, "Success"};
uint64_t total_start_time = esp_timer_get_time();
for (size_t i = 0; i < sequence_length; i++)
{
dshot_result_t item_result = sendCommandWithDelay(
sequence[i].command,
sequence[i].repeat_count,
DEFAULT_COMMAND_DELAY_MS);
if (!item_result.success)
{
result.success = false;
result.msg = item_result.msg;
break;
}
// Add delay after command if specified
if (sequence[i].delay_ms > 0)
{
_delay_ms(sequence[i].delay_ms);
}
}
uint64_t total_end_time = esp_timer_get_time();
return result;
}
//
dshot_result_t DShotCommandManager::executeInitSequence()
{
// Basic ESC initialization sequence
dshot_commandmanager_item_t init_sequence[] = {
{DSHOT_CMD_MOTOR_STOP, 5, 100}, // Stop motor, repeat 5 times, wait 100ms
{DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE, 1, 50}, // Enable telemetry, wait 50ms
{DSHOT_CMD_ESC_INFO, 1, 100} // Request ESC info, wait 100ms
};
return executeSequence(init_sequence, sizeof(init_sequence) / sizeof(init_sequence[0]));
}
//
dshot_result_t DShotCommandManager::executeCalibrationSequence()
{
// Basic ESC calibration sequence
dshot_commandmanager_item_t calibration_sequence[] = {
{DSHOT_CMD_MOTOR_STOP, 10, 500}, // Ensure motor is stopped
{DSHOT_CMD_SPIN_DIRECTION_NORMAL, 10, 100}, // Set normal spin direction
{DSHOT_CMD_3D_MODE_OFF, 10, 100}, // Disable 3D mode
{DSHOT_CMD_SAVE_SETTINGS, 10, 1000}, // Save settings
{DSHOT_CMD_MOTOR_STOP, 5, 100} // Final stop
};
return executeSequence(calibration_sequence, sizeof(calibration_sequence) / sizeof(calibration_sequence[0]));
}
// --- UTILITY METHODS ---
const char *DShotCommandManager::getCommandName(dshot_commands_t command)
{
switch (command)
{
case DSHOT_CMD_MOTOR_STOP:
return "MOTOR_STOP";
case DSHOT_CMD_BEACON1:
return "BEACON1";
case DSHOT_CMD_BEACON2:
return "BEACON2";
case DSHOT_CMD_BEACON3:
return "BEACON3";
case DSHOT_CMD_BEACON4:
return "BEACON4";
case DSHOT_CMD_BEACON5:
return "BEACON5";
case DSHOT_CMD_ESC_INFO:
return "ESC_INFO";
case DSHOT_CMD_SPIN_DIRECTION_1:
return "SPIN_DIRECTION_1";
case DSHOT_CMD_SPIN_DIRECTION_2:
return "SPIN_DIRECTION_2";
case DSHOT_CMD_3D_MODE_OFF:
return "3D_MODE_OFF";
case DSHOT_CMD_3D_MODE_ON:
return "3D_MODE_ON";
case DSHOT_CMD_SETTINGS_REQUEST:
return "SETTINGS_REQUEST";
case DSHOT_CMD_SAVE_SETTINGS:
return "SAVE_SETTINGS";
case DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE:
return "EXTENDED_TELEMETRY_ENABLE";
case DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE:
return "EXTENDED_TELEMETRY_DISABLE";
case DSHOT_CMD_SPIN_DIRECTION_NORMAL:
return "SPIN_DIRECTION_NORMAL";
case DSHOT_CMD_SPIN_DIRECTION_REVERSED:
return "SPIN_DIRECTION_REVERSED";
case DSHOT_CMD_LED0_ON:
return "LED0_ON";
case DSHOT_CMD_LED1_ON:
return "LED1_ON";
case DSHOT_CMD_LED2_ON:
return "LED2_ON";
case DSHOT_CMD_LED3_ON:
return "LED3_ON";
case DSHOT_CMD_LED0_OFF:
return "LED0_OFF";
case DSHOT_CMD_LED1_OFF:
return "LED1_OFF";
case DSHOT_CMD_LED2_OFF:
return "LED2_OFF";
case DSHOT_CMD_LED3_OFF:
return "LED3_OFF";
case DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF:
return "AUDIO_STREAM_MODE_ON_OFF";
case DSHOT_CMD_SILENT_MODE_ON_OFF:
return "SILENT_MODE_ON_OFF";
default:
return "UNKNOWN";
}
}
//
bool DShotCommandManager::isValidCommand(dshot_commands_t command)
{
return (command >= DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX);
}
// --- PRIVATE METHODS ---
dshot_result_t DShotCommandManager::_executeCommand(dshot_commands_t command)
{
uint64_t start_time = esp_timer_get_time();
// Execute the command using the DShotRMT instance
dshot_result_t result = _dshot.sendCommand(static_cast<uint16_t>(command));
uint64_t end_time = esp_timer_get_time();
_last_command_timestamp = end_time;
return result;
}
//
void DShotCommandManager::_delay_ms(uint32_t delay_ms)
{
if (delay_ms > 0)
{
delay(delay_ms);
}
}

View File

@ -1,123 +0,0 @@
/*
* DShotCommandManager.h
* Advanced DShot command management for DShotRMT library
* Author: Wastl Kraus
* Date: 2025-09-04
* License: MIT
*/
#pragma once
#include <Arduino.h>
#include <DShotRMT.h>
// Command item
typedef struct
{
dshot_commands_t command;
uint16_t repeat_count;
uint32_t delay_ms;
} dshot_commandmanager_item_t;
// Advanced DShot command manager class
class DShotCommandManager
{
public:
// Constructor
explicit DShotCommandManager(DShotRMT &dshot_instance);
// Initialize command manager
dshot_result_t begin();
void handleMenuInput(const String &input, Stream &output = Serial);
// Send a single DShot command
dshot_result_t sendCommand(dshot_commands_t command, uint16_t repeat_count = 1);
// Send command with specified delay between repetitions
dshot_result_t sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms);
// --- MOTOR CONTROL COMMANDS ---
// Stop motor (send MOTOR_STOP command)
dshot_result_t stopMotor();
// Enable/disable 3D mode
dshot_result_t set3DMode(bool enable);
// Set motor spin direction
dshot_result_t setSpinDirection(bool reversed);
// Save current settings to ESC
dshot_result_t saveSettings();
// --- TELEMETRY COMMANDS ---
// Enable/disable extended telemetry
dshot_result_t setExtendedTelemetry(bool enable);
// Request ESC information
dshot_result_t requestESCInfo();
// --- LED CONTROL COMMANDS (BLHeli32 only) ---
// Control ESC LEDs (BLHeli32 only)
dshot_result_t setLED(uint8_t led_number, bool state);
// --- BEACON COMMANDS ---
// Activate beacon (motor beeping)
dshot_result_t activateBeacon(uint8_t beacon_number);
// --- KISS ESC SPECIFIC COMMANDS ---
// Enable/disable audio stream mode (KISS ESCs)
dshot_result_t setAudioStreamMode(bool enable);
// Enable/disable silent mode (KISS ESCs)
dshot_result_t setSilentMode(bool enable);
// --- SEQUENCE COMMANDS ---
// Execute a sequence of DShot commands
dshot_result_t executeSequence(const dshot_commandmanager_item_t *sequence, size_t sequence_length);
// Execute ESC initialization sequence
dshot_result_t executeInitSequence();
// Execute ESC calibration sequence
dshot_result_t executeCalibrationSequence();
// --- UTILITY METHODS ---
// Get command name as string
static const char *getCommandName(dshot_commands_t command);
// Check if command is valid
static bool isValidCommand(dshot_commands_t command);
// --- GETTERS ---
// Get total number of commands sent
uint32_t getTotalCommandCount() const { return _total_commands_sent; }
// Get number of failed commands
uint32_t getFailedCommandCount() const { return _failed_commands; }
// Get reference to underlying DShotRMT instance
DShotRMT &getDShotRMT() { return _dshot; }
const DShotRMT &getDShotRMT() const { return _dshot; }
private:
// --- PRIVATE MEMBERS ---
DShotRMT &_dshot; // Reference to DShotRMT instance
uint32_t _total_commands_sent; // Total commands sent counter
uint32_t _failed_commands; // Failed commands counter
uint64_t _last_command_timestamp; // Timestamp of last command
// --- PRIVATE METHODS ---
// Execute single command with timing
dshot_result_t _executeCommand(dshot_commands_t command);
// Wait for specified delay
void _delay_ms(uint32_t delay_ms);
// --- CONSTANTS ---
static constexpr uint32_t DEFAULT_COMMAND_DELAY_MS = 10;
static constexpr uint16_t DEFAULT_REPEAT_COUNT = 1;
static constexpr uint16_t SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats
static constexpr uint32_t SETTINGS_COMMAND_DELAY_MS = 5;
};

View File

@ -6,17 +6,16 @@
* @license MIT
*/
#include "DShotRMT.h"
#include <DShotRMT.h>
// Static Data & Helper Functions
// Timing parameters for each DShot mode
// Format: {bit_length_us, t1h_length_us}
static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = {
{0.00, 0.00},
{6.67, 5.00},
{3.33, 2.50},
{1.67, 1.25},
{0.83, 0.67}};
{0.00, 0.00}, // DSHOT_OFF
{6.67, 5.00}, // DSHOT150
{3.33, 2.50}, // DSHOT300
{1.67, 1.25}, // DSHOT600
{0.83, 0.67}}; // DSHOT1200
// Helper function to print DShot results and telemetry
void printDShotResult(dshot_result_t &result, Stream &output)
@ -34,20 +33,22 @@ void printDShotResult(dshot_result_t &result, Stream &output)
// Constructors & Destructor
// Constructor with GPIO number
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count)
: _gpio(gpio),
_mode(mode),
_is_bidirectional(is_bidirectional),
_motor_magnet_count(magnet_count),
_dshot_timing(DSHOT_TIMING_US[mode]),
_frame_timer_us(0),
_rmt_ticks{0},
_last_throttle(DSHOT_CMD_MOTOR_STOP),
_last_transmission_time_us(0),
_last_command_timestamp(0),
_parsed_packet(0),
_packet{0},
_bitPositions{0},
_level0(_is_bidirectional ? 0 : 1),
_level1(_is_bidirectional ? 1 : 0),
_level0(1), // DShot standard: signal is idle-low, so pulses start by going HIGH
_level1(0), // DShot standard: signal returns to LOW after the high pulse
_rmt_tx_channel(nullptr),
_rmt_rx_channel(nullptr),
_dshot_encoder(nullptr),
@ -59,13 +60,17 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
_last_erpm_atomic(0),
_telemetry_ready_flag_atomic(false)
{
// Configure RMT ticks for DShot timings
// Pre-calculate timing and bit positions for performance
_preCalculateRMTTicks();
_preCalculateBitPositions();
// Activate internal pullup resistor
// gpio_set_pull_mode(_gpio, GPIO_PULLUP_ONLY);
}
// Constructor using pin number
DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional)
: DShotRMT(static_cast<gpio_num_t>(pin_nr), mode, is_bidirectional)
DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count)
: DShotRMT(static_cast<gpio_num_t>(pin_nr), mode, is_bidirectional, magnet_count)
{
// Delegates to primary constructor with type cast
}
@ -78,8 +83,8 @@ DShotRMT::~DShotRMT()
{
if (rmt_disable(_rmt_tx_channel) == DSHOT_OK)
{
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
}
}
@ -88,8 +93,8 @@ DShotRMT::~DShotRMT()
{
if (rmt_disable(_rmt_rx_channel) == DSHOT_OK)
{
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
}
}
@ -101,34 +106,42 @@ DShotRMT::~DShotRMT()
}
}
// Public Core Functions
// Initialize DShotRMT
dshot_result_t DShotRMT::begin()
{
// Init RX channel first (for bidirectional mode)
if (_is_bidirectional)
{
if (!_initRXChannel().success)
{
return {false, RX_INIT_FAILED};
}
}
// Init TX channel
if (!_initTXChannel().success)
{
return {false, TX_INIT_FAILED};
}
// Init DShot encoder
if (!_initDShotEncoder().success)
if (_is_bidirectional)
{
return {false, ENCODER_INIT_FAILED};
if (!_initRXChannel().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, RX_INIT_FAILED};
}
}
// Bit positions precalculation
_preCalculateBitPositions();
if (!_initDShotEncoder().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;
}
return {false, ENCODER_INIT_FAILED};
}
return {true, INIT_SUCCESS};
}
@ -136,62 +149,112 @@ dshot_result_t DShotRMT::begin()
// Send throttle value
dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
{
// Special case: if throttle is 0, use sendCommand() instead
// A throttle value of 0 is a disarm command
if (throttle == 0)
{
return sendCommand(DSHOT_CMD_MOTOR_STOP);
}
// Always store the original throttle value
_last_throttle = throttle;
// Constrain throttle for transmission and send
uint16_t new_throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
_packet = _buildDShotPacket(new_throttle);
// Constrain throttle to the valid DShot range
_last_throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
_packet = _buildDShotPacket(_last_throttle);
return _sendDShotFrame(_packet);
}
// Send throttle value as a percentage
dshot_result_t DShotRMT::sendThrottlePercent(float percent)
{
if (percent < 0.0f || percent > 100.0f)
{
return {false, PERCENT_NOT_IN_RANGE};
}
// Map percent to DShot throttle range
uint16_t throttle = static_cast<uint16_t>(DSHOT_THROTTLE_MIN + ((DSHOT_THROTTLE_MAX - DSHOT_THROTTLE_MIN) / 100.0f) * percent);
return sendThrottle(throttle);
}
// Send DShot command to ESC
dshot_result_t DShotRMT::sendCommand(uint16_t command)
{
// Validate command is within DShot specification range
if (command < DSHOT_CMD_MOTOR_STOP || command > DSHOT_CMD_MAX)
if (command > DSHOT_CMD_MAX)
{
return {false, COMMAND_NOT_VALID};
}
// Build packet and transmit
_packet = _buildDShotPacket(command);
return _sendDShotFrame(_packet);
}
// Send full DShot commands for setup etc
dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t repeat_count, uint16_t delay_us)
{
dshot_result_t result = {false, UNKNOWN_ERROR};
if (!_isValidCommand(dshot_command))
{
result.msg = INVALID_COMMAND;
return result;
}
bool all_successful = true;
// Send command multiple times with delay
for (uint16_t i = 0; i < repeat_count; i++)
{
dshot_result_t single_result = _executeCommand(dshot_command);
if (!single_result.success)
{
all_successful = false;
result.msg = single_result.msg;
break;
}
// Add delay between repetitions (except for last repetition)
if (i < repeat_count - 1)
{
delayMicroseconds(delay_us);
}
}
//
result.success = all_successful;
if (result.success)
{
result.msg = COMMAND_SUCCESS;
}
return result;
}
// Get telemetry data
dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
{
// Result container with unified structure
dshot_result_t result = {false, TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY};
// Check if bidirectional mode is enabled
if (!_is_bidirectional)
{
result.msg = BIDIR_NOT_ENABLED;
return result;
}
// Check for new telemetry data
// Use stored magnet count if parameter is 0 (default)
uint16_t final_magnet_count = (magnet_count == 0) ? _motor_magnet_count : magnet_count;
// Check if the callback has set the flag for new data
if (_telemetry_ready_flag_atomic)
{
_telemetry_ready_flag_atomic = false;
_telemetry_ready_flag_atomic = false; // Reset the flag
uint16_t erpm = _last_erpm_atomic; // Read the atomic variable
uint16_t erpm = _last_erpm_atomic;
// Calculate motor RPM from eRPM
if (erpm != DSHOT_NULL_PACKET && magnet_count >= 1)
if (erpm != DSHOT_NULL_PACKET && final_magnet_count >= MAGNETS_PER_POLE_PAIR)
{
uint8_t pole_pairs = max(POLE_PAIRS_MIN, (magnet_count / MAGNETS_PER_POLE_PAIR));
// Calculate motor RPM from eRPM and magnet count
uint8_t pole_pairs = final_magnet_count / MAGNETS_PER_POLE_PAIR;
uint32_t motor_rpm = (erpm / pole_pairs);
result.success = true;
@ -204,47 +267,55 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
return result;
}
// Public Info & Debug Functions
// Print timing diagnostic information to specified stream
void DShotRMT::printDShotInfo(Stream &output) const
// Reverse motor direction directly
dshot_result_t DShotRMT::setMotorSpinDirection(bool reversed)
{
output.println(" ");
output.println(" === DShot Signal Info === ");
// Use command as a yes / no switch
dshot_commands_t command = reversed ? DSHOT_CMD_SPIN_DIRECTION_REVERSED : DSHOT_CMD_SPIN_DIRECTION_NORMAL;
// Current DShot mode
output.printf("Current Mode: DSHOT%d\n",
_mode == DSHOT150 ? 150 :
_mode == DSHOT300 ? 300 :
_mode == DSHOT600 ? 600 :
_mode == DSHOT1200 ? 1200 : 0);
output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO");
// Packet Info
output.printf("Current Packet: ");
// Print bit by bit
for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
{
if ((_parsed_packet >> i) & 0b0000000000000001)
{
output.print("1");
}
else
{
output.print("0");
}
}
output.printf("\n");
output.printf("Current Value: %u\n", _packet.throttle_value);
return sendCommand(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
}
// Print CPU information
dshot_result_t DShotRMT::getESCInfo()
{
return sendCommand(DSHOT_CMD_ESC_INFO);
}
// Use with caution
dshot_result_t DShotRMT::saveESCSettings()
{
return sendCommand(DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
}
// Public Info & Debug Functions
void DShotRMT::setMotorMagnetCount(uint16_t magnet_count)
{
_motor_magnet_count = magnet_count;
}
void DShotRMT::printDShotInfo(Stream &output) const
{
output.println("\n === DShot Signal Info === ");
output.printf("Current Mode: DSHOT%d\n", _mode == DSHOT150 ? 150 :
_mode == DSHOT300 ? 300 :
_mode == DSHOT600 ? 600 :
_mode == DSHOT1200 ? 1200 : 0);
output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO");
output.printf("Current Packet: ");
for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
{
output.print((_parsed_packet >> i) & 1);
}
output.printf("\nCurrent Value: %u\n", _packet.throttle_value);
}
//
void DShotRMT::printCpuInfo(Stream &output) const
{
output.println(" ");
output.println(" === CPU Info === ");
output.println("\n === CPU Info === ");
output.printf("Chip Model: %s\n", ESP.getChipModel());
output.printf("Chip Revision: %d\n", ESP.getChipRevision());
output.printf("CPU Freq = %lu MHz\n", ESP.getCpuFreqMHz());
@ -252,28 +323,42 @@ void DShotRMT::printCpuInfo(Stream &output) const
output.printf("APB Freq = %lu Hz\n", getApbFrequency());
}
// Simple check
bool DShotRMT::_isValidCommand(dshot_commands_t command)
{
return (command >= DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX);
}
//
dshot_result_t DShotRMT::_executeCommand(dshot_commands_t command)
{
uint64_t start_time = esp_timer_get_time();
// Execute the command using the DShotRMT instance
dshot_result_t result = sendCommand(static_cast<uint16_t>(command));
uint64_t end_time = esp_timer_get_time();
_last_command_timestamp = end_time;
return result;
}
// Private Initialization Functions
// Initialize RMT TX channel
dshot_result_t DShotRMT::_initTXChannel()
{
// Configure TX channel
_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;
// Config RMT TX
_rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
_rmt_tx_config.flags.eot_level = _is_bidirectional ? 1 : 0; // Telemetric Bit used as bidir flag
_rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
_rmt_tx_config.flags.eot_level = _is_bidirectional ? 1 : 0;
// Create RMT TX channel
if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
{
return {false, TX_INIT_FAILED};
}
// Enable TX channel
if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
{
return {false, TX_INIT_FAILED};
@ -282,44 +367,55 @@ dshot_result_t DShotRMT::_initTXChannel()
return {true, TX_INIT_SUCCESS};
}
// Initialize RMT RX channel
dshot_result_t DShotRMT::_initRXChannel()
{
// Direct RMT symbol processing - Performance optimized
_rx_event_callbacks.on_recv_done = _on_rx_done;
// Double check if bidirectional mode is enabled
if (!_is_bidirectional)
{
return {true, NONE};
}
// Config RMT RX
_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;
// Config RMT RX parameters
// Filter for pulses that are within a reasonable range for DShot telemetry
_rmt_rx_config.signal_range_min_ns = DSHOT_PULSE_MIN;
_rmt_rx_config.signal_range_max_ns = DSHOT_PULSE_MAX;
// Create RMT RX channel
if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
{
return {false, RX_INIT_FAILED};
}
// Enable RX channel
// 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, CALLBACK_REGISTERING_FAILED};
}
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{
return {false, 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, RECEIVER_FAILED};
}
return {true, RX_INIT_SUCCESS};
}
// Initialize DShot encoder
dshot_result_t DShotRMT::_initDShotEncoder()
{
// Create copy encoder configuration
rmt_copy_encoder_config_t encoder_config = {};
// Create encoder instance
if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
{
return {false, ENCODER_INIT_FAILED};
@ -329,77 +425,63 @@ dshot_result_t DShotRMT::_initDShotEncoder()
}
// Private Packet Management Functions
// Build a complete DShot packet from a valid value
dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value)
{
// Init packet structure
dshot_packet_t packet = {};
// Re-check for valid value
if (value > DSHOT_THROTTLE_MAX)
{
// Something is really wrong
return packet;
}
// Build packet
packet.throttle_value = value & DSHOT_THROTTLE_MAX;
packet.telemetric_request = _is_bidirectional ? 1 : 0;
// CRC is calculated over 12bit
uint16_t data = (packet.throttle_value << 1) | packet.telemetric_request;
packet.checksum = _calculateCRC(data);
// The data for CRC calculation includes the 11-bit value and the 1-bit telemetry flag
uint16_t data_for_crc = (packet.throttle_value << 1) | packet.telemetric_request;
packet.checksum = _calculateCRC(data_for_crc);
return packet;
}
// Parse DShot packet into 16-bit format
uint16_t DShotRMT::_parseDShotPacket(const dshot_packet_t &packet)
{
// Parse DShot frame into "raw" 16 bit value
// Combine throttle, telemetry bit, and CRC into a single 16-bit frame
uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request;
return (data_and_telemetry << 4) | packet.checksum;
}
// Calculate CRC
uint16_t DShotRMT::_calculateCRC(const uint16_t &data)
{
// DShot CRC
// Standard DShot CRC calculation using XOR
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & DSHOT_CRC_MASK;
// Invert CRC for bidirectional DShot mode
// For bidirectional DShot, the CRC is inverted
if (_is_bidirectional)
{
crc = (~crc) & DSHOT_CRC_MASK;
}
return crc;
}
// Configure RMT ticks for DShot timings
void DShotRMT::_preCalculateRMTTicks()
{
// Convert DShot timings (us) to RMT ticks
// 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 that of a 0
_rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double of 0
_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
_frame_timer_us = (static_cast<uint64_t>(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US;
// Double frame time for bidirectional mode (includes response time)
// For bidirectional, double up
if (_is_bidirectional)
{
_frame_timer_us = (_frame_timer_us << 1);
}
}
// Precalculate bit positions for performance optimization
void DShotRMT::_preCalculateBitPositions()
{
// Pre-calculate bit positions to avoid redundant calculations in the encoding loop
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
_bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i;
@ -407,173 +489,125 @@ void DShotRMT::_preCalculateBitPositions()
}
// Private Frame Processing Functions
// Transmit DShot packet via RMT
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
{
// Check timing requirements
// Ensure enough time has passed since the last transmission
if (!_timer_signal())
{
return {false, TIMING_CORRECTION};
return {true, NONE};
}
// Enable RMT RX before RMT TX (bidirectional mode)
if (_is_bidirectional)
{
// Calculate transmission data size
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
// Performance reasons
rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME];
if (rmt_receive(_rmt_rx_channel, rx_symbols, rx_size_bytes, &_rmt_rx_config) != DSHOT_OK)
{
return {false, RECEIVER_FAILED};
}
}
// Local for performance
rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME];
dshot_result_t result = _encodeDShotFrame(packet, tx_symbols);
// Encode DShot packet into RMT symbols
_encodeDShotFrame(packet, tx_symbols);
if (!result.success)
{
return result;
}
// Calculate transmission data size
size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
// TODO: Find out, why this is needed
if (_is_bidirectional)
{
// Disable RMT RX for sending
if (rmt_disable(_rmt_rx_channel) != DSHOT_OK)
{
return {false, RECEIVER_FAILED};
}
}
// Perform RMT transmission
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK)
{
return {false, TRANSMISSION_FAILED};
}
// Re-enable RMT RX
if (_is_bidirectional)
{
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{
return {false, RECEIVER_FAILED};
}
}
// Update timestamp and calculate execution time
_timer_reset();
_timer_reset(); // Reset the timer for the next frame
return {true, TRANSMISSION_SUCCESS};
}
// Encode DShot packet into RMT symbol format (placed in IRAM for performance)
// This function needs to be fast, as it generates the RMT symbols just before sending
dshot_result_t IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols)
{
_parsed_packet = _parseDShotPacket(packet);
// Decode MSB
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
// Use precalculated bit positions - Performance optimized
int bit_position = _bitPositions[i];
bool bit = (_parsed_packet >> bit_position) & 1;
bool bit = (_parsed_packet >> bit_position) & 0b0000000000000001;
symbols[i].level0 = _level0;
// A '1' bit has a longer high-time, a '0' bit has a shorter high-time
symbols[i].level0 = _level0; // Go HIGH
symbols[i].duration0 = bit ? _rmt_ticks.t1h_ticks : _rmt_ticks.t0h_ticks;
symbols[i].level1 = _level1;
symbols[i].level1 = _level1; // Go LOW
symbols[i].duration1 = bit ? _rmt_ticks.t1l_ticks : _rmt_ticks.t0l_ticks;
}
return {true, ENCODING_SUCCESS};
}
// Decode DShot telemetry frame from received RMT symbols
uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
// 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)
{
uint32_t gcr_value = 0;
// Decode GCR symbols into a 21-bit value.
// '1' has a longer low pulse (duration0 > duration1).
// '0' has a longer high pulse (duration1 > duration0).
// Step 1: 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)
{
bool bit_is_one = symbols[i].duration0 > symbols[i].duration1;
gcr_value = (gcr_value << 1) | bit_is_one;
}
// Perform GCR decoding: data = gcr ^ (gcr >> 1).
// Step 2: Perform GCR decoding (GCR = Value ^ (Value >> 1))
uint32_t decoded_frame = gcr_value ^ (gcr_value >> 1);
// Extract 16 data bits and 4 CRC bits from 20-bit frame.
// The first bit of the GCR frame is a start bit and is discarded.
// Step 3: Extract the 16-bit DShot frame from the decoded data
uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET);
// Extract data (first 12 bits) and CRC (last 4 bits)
// Step 4: Extract data and CRC from the 16-bit frame
uint16_t received_data = data_and_crc >> 4;
uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK;
// Telemetry request bit has to be 1
if (!(received_data & (1 << 11)))
// Step 5: A valid response must have the telemetry request bit set to 1. This is a sanity check.
if (!((received_data >> 11) & 1))
{
return DSHOT_NULL_PACKET;
}
// Calculate expected CRC
uint16_t data_for_crc = received_data;
uint16_t calculated_crc = _calculateCRC(data_for_crc);
// Validate CRC
// Step 6: Calculate and validate CRC
uint16_t calculated_crc = _calculateCRC(received_data);
if (received_crc != calculated_crc)
{
return DSHOT_NULL_PACKET;
}
// Return the eRPM value (first 11 bits of received data).
// Return the eRPM value (first 11 bits).
return received_data & DSHOT_THROTTLE_MAX;
}
// Private Timing Control Functions
// Check if enough time has passed for next transmission
// Timing Control Functions
bool IRAM_ATTR DShotRMT::_timer_signal()
{
// Check if the minimum interval between frames has passed
uint64_t current_time = esp_timer_get_time();
// Handle potential overflow
uint64_t elapsed = current_time - _last_transmission_time_us;
return elapsed >= _frame_timer_us;
}
// Reset transmission timer to current time
bool DShotRMT::_timer_reset()
{
// Record the time of the current transmission
_last_transmission_time_us = esp_timer_get_time();
return DSHOT_OK;
return true;
}
// Static Callback Functions
// Callback for RMT RX
// This function is called by the RMT driver's ISR when a frame is received
bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
DShotRMT *instance = static_cast<DShotRMT *>(user_data);
// ISR check for valid data
if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && edata->num_symbols <= GCR_BITS_PER_FRAME)
if (edata && edata->num_symbols == GCR_BITS_PER_FRAME)
{
// Direct decoding
uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols);
if (erpm != DSHOT_NULL_PACKET)
{
// Atomic writes - thread-safe
instance->_last_erpm_atomic = erpm;
instance->_telemetry_ready_flag_atomic = true;
// Atomically store the new eRPM value and set the flag
instance->_last_erpm_atomic.store(erpm);
instance->_telemetry_ready_flag_atomic.store(true);
}
}

View File

@ -1,8 +1,8 @@
/**
* @file DShotRMT.h
* @brief DShot signal generation using ESP32 RMT with bidirectional support
* @brief Optimized DShot signal generation using ESP32 RMT with bidirectional support
* @author Wastl Kraus
* @date 2025-06-11
* @date 2025-09-18
* @license MIT
*/
@ -22,6 +22,10 @@ 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 auto DSHOT_OK = 0;
static constexpr auto DSHOT_ERROR = 1;
// DShot Modes
typedef enum dshot_mode
{
@ -78,25 +82,24 @@ class DShotRMT
{
public:
// Constructors & Destructor
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false);
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional);
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
~DShotRMT();
// Public Core Functions
// Initialize the RMT module and DShot config
dshot_result_t begin();
// Send throttle value (48 - 2047)
dshot_result_t sendThrottle(uint16_t throttle);
// Send DShot command (0 - 47)
dshot_result_t sendThrottlePercent(float percent);
dshot_result_t sendCommand(uint16_t command);
dshot_result_t sendCommand(dshot_commands_t dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US);
dshot_result_t getTelemetry(uint16_t magnet_count = 0);
dshot_result_t getESCInfo();
dshot_result_t setMotorSpinDirection(bool reversed);
dshot_result_t saveESCSettings();
// Get telemetry data (bidirectional mode only)
dshot_result_t getTelemetry(uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Public Info & Debug Functions
// Public Utility & Info Functions
void setMotorMagnetCount(uint16_t magnet_count);
void printDShotInfo(Stream &output = Serial) const;
void printCpuInfo(Stream &output = Serial) const;
@ -123,27 +126,23 @@ public:
}
private:
// Configuration Constants
static constexpr bool DSHOT_OK = 0;
static constexpr bool DSHOT_ERROR = 1;
// Configuration Constants
static constexpr auto const DSHOT_NULL_PACKET = 0b0000000000000000;
static constexpr auto const DSHOT_FULL_PACKET = 0b1111111111111111;
static constexpr auto const DSHOT_CRC_MASK = 0b0000000000001111;
static constexpr auto const DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
static constexpr auto const DSHOT_RMT_RESOLUTION = 8 * 1000 * 1000; // 8 MHz resolution
static constexpr auto const RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / (1 * 1000 * 1000); // RMT Ticks per microsecond
static constexpr auto const RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
static constexpr auto const DSHOT_RX_TIMEOUT_MS = 2;
static constexpr auto const DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility
static constexpr auto const RMT_BUFFER_SYMBOLS = 64;
static constexpr auto const DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility
static constexpr auto const RMT_BUFFER_SYMBOLS = 128;
static constexpr auto const RMT_QUEUE_DEPTH = 1;
static constexpr auto const GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame
static constexpr auto const POLE_PAIRS_MIN = 1;
static constexpr auto const MAGNETS_PER_POLE_PAIR = 2;
static constexpr auto const NO_DSHOT_TELEMETRY = 0;
static constexpr auto const DSHOT_PULSE_MIN = 3000; // 3us minimum pulse
static constexpr auto const DSHOT_PULSE_MAX = 60000; // 60us maximum pulse
static constexpr auto const DSHOT_PULSE_MIN = 800; // 0.8us minimum pulse
static constexpr auto const DSHOT_PULSE_MAX = 8000; // 8.0us maximum pulse
static constexpr auto const DSHOT_TELEMETRY_INVALID = DSHOT_THROTTLE_MAX;
// Error Messages
@ -162,17 +161,26 @@ private:
static constexpr char const *TRANSMISSION_FAILED = "Transmission failed!";
static constexpr char const *RECEIVER_FAILED = "RMT receiver failed!";
static constexpr char const *THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)";
static constexpr char const *PERCENT_NOT_IN_RANGE = "Percent not in range! (0.0 - 100.0)";
static constexpr char const *COMMAND_NOT_VALID = "Command not valid! (0 - 47)";
static constexpr char const *BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!";
static constexpr char const *TELEMETRY_SUCCESS = "Valid Telemetric Frame received!";
static constexpr char const *TELEMETRY_FAILED = "No valid Telemetric Frame received!";
static constexpr char const *INVALID_MAGNET_COUNT = "Invalid motor magnet count!";
static constexpr char const *TIMING_CORRECTION = "Timing correction!";
static constexpr char const *CALLBACK_REGISTERING_FAILED = "RMT RX Callback registering failed!";
static constexpr char const *INVALID_COMMAND = "Invalid command!";
static constexpr char const *COMMAND_SUCCESS = "DShot command sent successfully";
// --- UTILITY METHODS ---
bool _isValidCommand(dshot_commands_t command);
dshot_result_t _executeCommand(dshot_commands_t command);
// 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;
@ -180,11 +188,12 @@ private:
rmt_ticks_t _rmt_ticks;
uint16_t _last_throttle;
uint64_t _last_transmission_time_us;
uint64_t _last_command_timestamp;
uint16_t _parsed_packet;
dshot_packet_t _packet;
uint8_t _bitPositions[DSHOT_BITS_PER_FRAME];
uint16_t _level0;
uint16_t _level1;
uint16_t _level0; // Signal level for the first part of a pulse (always HIGH for DShot)
uint16_t _level1; // Signal level for the second part of a pulse (always LOW for DShot)
// RMT Hardware Handles
rmt_channel_handle_t _rmt_tx_channel;
@ -225,4 +234,10 @@ private:
// 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);
// Command Constants
static constexpr auto DEFAULT_CMD_DELAY_US = 10;
static constexpr auto DEFAULT_CMD_REPEAT_COUNT = 1;
static constexpr auto SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats
static constexpr auto SETTINGS_COMMAND_DELAY_US = 5;
};

249
src/ota_update.h Normal file
View File

@ -0,0 +1,249 @@
/**
* @file ota_update.h
* @brief DShot signal generation using ESP32 RMT with bidirectional support
* @author Wastl Kraus
* @date 2025-09-13
* @license MIT
*/
#pragma once
// OTA Update HTML
const char *ota_html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<title>OTA Update - DShotRMT</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial, Helvetica, sans-serif;
background-color: #2c3e50;
color: #ecf0f1;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: #34495e;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
h1 {
text-align: center;
color: #3498db;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="file"] {
width: 100%;
padding: 8px;
border: 1px solid #7f8c8d;
border-radius: 4px;
background-color: #2c3e50;
color: #ecf0f1;
}
.btn {
background-color: #3498db;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 16px;
}
.btn:hover {
background-color: #2980b9;
}
.btn:disabled {
background-color: #7f8c8d;
cursor: not-allowed;
}
.progress {
width: 100%;
background-color: #2c3e50;
border-radius: 4px;
margin-top: 10px;
display: none;
}
.progress-bar {
width: 0%;
height: 30px;
background-color: #27ae60;
border-radius: 4px;
text-align: center;
line-height: 30px;
color: white;
}
.message {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
display: none;
}
.success {
background-color: #27ae60;
}
.error {
background-color: #e74c3c;
}
.warning {
background-color: #f39c12;
color: #2c3e50;
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
font-weight: bold;
}
.back-link {
text-align: center;
margin-top: 20px;
}
.back-link a {
color: #3498db;
text-decoration: none;
}
.back-link a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<h1>OTA Firmware Update</h1>
<div class="warning">
WARNING: Stop motors before starting update!
</div>
<form id="upload_form" enctype="multipart/form-data">
<div class="form-group">
<label for="update">Firmware File (.bin):</label>
<input type="file" id="update" name="update" accept=".bin" required>
</div>
<button type="submit" class="btn" id="uploadBtn">Update!</button>
</form>
<div class="progress" id="progressDiv">
<div class="progress-bar" id="progressBar">0%</div>
</div>
<div class="message" id="message"></div>
</div>
<script>
document.getElementById('upload_form').addEventListener('submit', function (e) {
e.preventDefault();
const fileInput = document.getElementById('update');
const file = fileInput.files[0];
if (!file) {
showMessage('Choose a valid update file.', 'error');
return;
}
if (!file.name.endsWith('.bin')) {
showMessage('Choose a valid update file.', 'error');
return;
}
uploadFile(file);
});
function uploadFile(file) {
const formData = new FormData();
formData.append('update', file);
const xhr = new XMLHttpRequest();
// Progress tracking
xhr.upload.addEventListener('progress', function (e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
updateProgress(percentComplete);
}
});
// Upload complete
xhr.addEventListener('load', function () {
if (xhr.status === 200) {
showMessage('Update successfull! Restarting...', 'success');
setTimeout(() => {
window.location.href = '/';
}, 5000);
} else {
showMessage('Update failed: ' + xhr.responseText, 'error');
resetUpload();
}
});
// Upload error
xhr.addEventListener('error', function () {
showMessage('Connection error during Update.', 'error');
resetUpload();
});
// Start upload
document.getElementById('uploadBtn').disabled = true;
document.getElementById('uploadBtn').textContent = 'buffering...';
document.getElementById('progressDiv').style.display = 'block';
xhr.open('POST', '/update');
xhr.send(formData);
}
function updateProgress(percent) {
const progressBar = document.getElementById('progressBar');
progressBar.style.width = percent + '%';
progressBar.textContent = Math.round(percent) + '%';
}
function showMessage(text, type) {
const messageDiv = document.getElementById('message');
messageDiv.textContent = text;
messageDiv.className = 'message ' + type;
messageDiv.style.display = 'block';
}
function resetUpload() {
document.getElementById('uploadBtn').disabled = false;
document.getElementById('uploadBtn').textContent = 'Update!';
document.getElementById('progressDiv').style.display = 'none';
document.getElementById('progressBar').style.width = '0%';
document.getElementById('progressBar').textContent = '0%';
}
</script>
</body>
</html>
)rawliteral";

View File

@ -217,6 +217,7 @@ static constexpr char index_html[] = R"rawliteral(
<!-- Throttle Section -->
<div class="throttle-section" id="throttleSection">
<div id="throttleValue">0</div>
<div id="throttlePercent" style="font-size: 1.2rem; color: #bdc3c7; margin-top: -15px; margin-bottom: 15px;">0%</div>
<input type="range" min="48" max="2047" value="0" id="throttleSlider" disabled>
</div>
@ -253,6 +254,19 @@ static constexpr char index_html[] = R"rawliteral(
setTimeout(initWebSocket, 2000);
}
function updateThrottleDisplays(rawValue) {
const DSHOT_MIN = 48;
const DSHOT_MAX = 2047;
const clampedValue = Math.max(0, Math.min(DSHOT_MAX, rawValue));
document.getElementById('throttleValue').innerText = clampedValue;
let percent = 0;
if (clampedValue > 0) {
percent = (clampedValue - DSHOT_MIN) / (DSHOT_MAX - DSHOT_MIN) * 100;
}
document.getElementById('throttlePercent').innerText = Math.round(percent) + '%';
document.getElementById('throttleSlider').value = clampedValue;
}
// Getting data from sketch
function onMessage(event) {
try {
@ -265,8 +279,7 @@ static constexpr char index_html[] = R"rawliteral(
// Sync web and serial throttle inputs
if (data.throttle !== undefined) {
if (isArmed) {
document.getElementById('throttleSlider').value = data.throttle;
document.getElementById('throttleValue').innerText = data.throttle;
updateThrottleDisplays(data.throttle);
}
}
@ -304,8 +317,7 @@ static constexpr char index_html[] = R"rawliteral(
// If disarmed, set throttle to 0
if (!isArmed) {
slider.value = 0;
sliderValue.innerText = 0;
updateThrottleDisplays(0);
}
});
@ -325,8 +337,7 @@ static constexpr char index_html[] = R"rawliteral(
armingStatus.className = 'status-disarmed';
throttleSection.classList.remove('armed');
slider.disabled = true;
slider.value = 0;
sliderValue.innerText = 0;
updateThrottleDisplays(0);
}
}
@ -334,13 +345,12 @@ static constexpr char index_html[] = R"rawliteral(
slider.addEventListener('input', () => {
if (!isArmed) {
slider.disabled = true;
slider.value = 0;
sliderValue.innerText = 0;
updateThrottleDisplays(0);
return;
}
const throttle = slider.value;
sliderValue.innerText = throttle;
updateThrottleDisplays(throttle);
const message = JSON.stringify({
"throttle": parseInt(throttle),