diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e95371..5dc4cc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2b2b912..157e94d 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.vscode/arduino.json b/.vscode/arduino.json deleted file mode 100644 index b6596e1..0000000 --- a/.vscode/arduino.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 8add28d..0000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -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" - ] - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b961c02..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/README.md b/README.md index 44bb558..4ac852e 100644 --- a/README.md +++ b/README.md @@ -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 +#include + +// 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 - -// 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 - Send DShot command (0 - 47) - throttle - Set throttle (48 - 2047) - repeat cmd 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. \ No newline at end of file diff --git a/examples/command_manager/command_manager.ino b/examples/command_manager/command_manager.ino deleted file mode 100644 index 4bc7bdd..0000000 --- a/examples/command_manager/command_manager.ino +++ /dev/null @@ -1,345 +0,0 @@ -/* - * command_manager.ino - * Example sketch for DShotCommandManager - * Author: Wastl Kraus - * Date: 2025-09-04 - * License: MIT - */ - -#include -#include -#include - -// 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(cmd_num))) - { - USB_SERIAL.printf("Sending command %d (%s)... ", cmd_num, - DShotCommandManager::getCommandName(static_cast(cmd_num))); - cmd_result = commandManager.sendCommand(static_cast(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 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 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(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(cmd_num)), - repeat_count); - cmd_result = commandManager.sendCommand(static_cast(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 - Send Command (0 - 47)"); - USB_SERIAL.println(" throttle - 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("**********************************************"); -} diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index 70f4c77..d13533c 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -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) -static constexpr auto IS_BIDIRECTIONAL = 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 ")) diff --git a/examples/throttle_percent/throttle_percent.ino b/examples/throttle_percent/throttle_percent.ino new file mode 100644 index 0000000..3cb4cf5 --- /dev/null +++ b/examples/throttle_percent/throttle_percent.ino @@ -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 +#include + +// 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(" - Set throttle (0 - 100)"); + USB_SERIAL.println(" 0 - Stop motor"); + USB_SERIAL.println("*******************************************"); + USB_SERIAL.println(" cmd - 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"); + } + } +} diff --git a/examples/web_client/web_client.ino b/examples/web_client/web_client.ino index 3bf66f7..41491f0 100644 --- a/examples/web_client/web_client.ino +++ b/examples/web_client/web_client.ino @@ -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 +#include #include #include +#include #include #include @@ -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,15 +92,18 @@ void setup() if (wifi_connected) { + // Setup OTA first + setupOTA(); + // Init WebSockets and Webserver USB_SERIAL.println("\nStarting Webserver..."); - + ws.onEvent(onWsEvent); server.addHandler(&ws); server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send_P(200, "text/html", index_html); }); - + server.begin(); USB_SERIAL.println("HTTP server started."); @@ -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; } diff --git a/examples/web_control/web_control.ino b/examples/web_control/web_control.ino index 5db9332..38b0915 100644 --- a/examples/web_control/web_control.ino +++ b/examples/web_control/web_control.ino @@ -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 #include @@ -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; diff --git a/library.properties b/library.properties index 753180c..7ee1a53 100644 --- a/library.properties +++ b/library.properties @@ -1,12 +1,11 @@ name=DShotRMT -version=0.8.0 +version=0.8.3 author=Wastl Kraus maintainer=Wastl Kraus 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 \ No newline at end of file +provides_includes=DShotRMT.h, dshot_commands.h, web_content.h, ota_update.h \ No newline at end of file diff --git a/src/DShotCommandManager.cpp b/src/DShotCommandManager.cpp deleted file mode 100644 index ac5cb17..0000000 --- a/src/DShotCommandManager.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/* - * DShotCommandManager.cpp - * Advanced DShot command management for DShotRMT library - * Author: Wastl Kraus - * Date: 2025-09-04 - * License: MIT - */ - -#include - -// 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_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(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); - } -} diff --git a/src/DShotCommandManager.h b/src/DShotCommandManager.h deleted file mode 100644 index 9b05fc3..0000000 --- a/src/DShotCommandManager.h +++ /dev/null @@ -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 -#include - -// 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; -}; diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp index e46a521..7c64131 100644 --- a/src/DShotRMT.cpp +++ b/src/DShotRMT.cpp @@ -6,17 +6,16 @@ * @license MIT */ -#include "DShotRMT.h" +#include -// 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(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(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(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(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(_dshot_timing.bit_length_us * RMT_TICKS_PER_US); _rmt_ticks.t1h_ticks = static_cast(_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(_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(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); } } diff --git a/src/DShotRMT.h b/src/DShotRMT.h index 5c82f64..0966141 100644 --- a/src/DShotRMT.h +++ b/src/DShotRMT.h @@ -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 { @@ -62,8 +66,8 @@ typedef struct dshot_result { bool success; const char *msg; - uint16_t erpm; - uint16_t motor_rpm; + uint16_t erpm; + uint16_t motor_rpm; } dshot_result_t; // Command Type Alias @@ -78,28 +82,27 @@ 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); - - // Get telemetry data (bidirectional mode only) - dshot_result_t getTelemetry(uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT); - - // Public Info & Debug Functions + 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(); + + // Public Utility & Info Functions + void setMotorMagnetCount(uint16_t magnet_count); void printDShotInfo(Stream &output = Serial) const; void printCpuInfo(Stream &output = Serial) const; - + // Deprecated Methods [[deprecated("Use sendThrottle() instead")]] bool setThrottle(uint16_t throttle) @@ -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,12 +188,13 @@ 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; rmt_channel_handle_t _rmt_rx_channel; @@ -218,11 +227,17 @@ private: dshot_result_t _sendDShotFrame(const dshot_packet_t &packet); dshot_result_t _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols); uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols); - + // Private Timing Control Functions bool _timer_signal(); bool _timer_reset(); - + // 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; }; diff --git a/src/ota_update.h b/src/ota_update.h new file mode 100644 index 0000000..695426e --- /dev/null +++ b/src/ota_update.h @@ -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( + + + + + OTA Update - DShotRMT + + + + + +
+

OTA Firmware Update

+ +
+ WARNING: Stop motors before starting update! +
+ +
+
+ + +
+ +
+ +
+
0%
+
+ +
+ +
+ + + + + +)rawliteral"; diff --git a/src/web_content.h b/src/web_content.h index 2f7459d..104adcb 100644 --- a/src/web_content.h +++ b/src/web_content.h @@ -217,6 +217,7 @@ static constexpr char index_html[] = R"rawliteral(
0
+
0%
@@ -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),