diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cd3296..5dc4cc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,48 +15,78 @@ jobs: # ============================================================================ # Code Quality & Linting # ============================================================================ - quality-check: - strategy: - matrix: - os-hosts: - - ubuntu-latest - - windows-latest - name: Arduino Lint Check - runs-on: ${{ matrix.os-hosts }} + 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: Restore Arduino Core Cache - id: cache-core + 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 - if: steps.cache-core.outputs.cache-hit != 'true' + 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.outputs.cache-hit != 'true' + if: always() && steps.cache-core-linux.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: | ~/.arduino15/packages ~/.arduino15/cache - key: arduino-core-${{ runner.os }}-esp32-v1 + 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 }} @@ -67,44 +97,33 @@ jobs: # ============================================================================ # Compilation Test # ============================================================================ - compile-test: - strategy: - matrix: - example: - - examples/dshot300/dshot300.ino - os-hosts: - - ubuntu-latest - - windows-latest - name: Compile Sketches - runs-on: ${{ matrix.os-hosts }} + compile-test-linux: + name: Compile Sketches (Linux) + runs-on: ubuntu-latest timeout-minutes: 15 - steps: - uses: actions/checkout@v5 - - uses: arduino/setup-arduino-cli@v2 - - name: Restore Arduino Core & Libraries Cache - id: cache-all + 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 (Linux) - if: steps.cache-all.outputs.cache-hit != 'true' && runner.os == 'Linux' + 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 - + if ! arduino-cli core list | grep -q 'esp32'; then + arduino-cli core install esp32:esp32 + fi mkdir -p ~/Arduino/libraries - if [ ! -d ~/Arduino/libraries/ArduinoJson ]; then git clone --depth=1 https://github.com/bblanchon/ArduinoJson.git ~/Arduino/libraries/ArduinoJson fi @@ -114,14 +133,44 @@ jobs: 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 }} examples/dshot300/dshot300.ino - - name: Install ESP32 Core and Dependencies (Windows) - if: steps.cache-all.outputs.cache-hit != 'true' && runner.os == 'Windows' + 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 - arduino-cli core install esp32:esp32 - - # PowerShell If-Bedingungen, um nur fehlende Bibliotheken zu klonen + 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" } @@ -131,21 +180,18 @@ jobs: if (-not (Test-Path -Path "$env:USERPROFILE/Arduino/libraries/AsyncTCP")) { git clone --depth=1 https://github.com/ESP32Async/AsyncTCP "$env:USERPROFILE/Arduino/libraries/AsyncTCP" } - shell: pwsh - - name: Save Arduino Core & Libraries Cache - if: always() && steps.cache-all.outputs.cache-hit != 'true' + 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-${{ runner.os }}-esp32-v1 - + key: arduino-full-windows-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 # ============================================================================ # Build Status Report @@ -154,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: | @@ -163,17 +212,17 @@ 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 \ diff --git a/.gitignore b/.gitignore index e122f2c..c9a9ea1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ examples/dshot300/debug.cfg examples/dshot300/esp32.svd examples/dshot300/debug_custom.json examples/dshot300/debug.svd -.vscode +.vscode/ +.vscode/c_cpp_properties.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 8add28d..7fe2417 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -32,7 +32,7 @@ "~/.arduino15/packages/esp32/hardware/esp32/3.3.0/cores/esp32/Arduino.h" ], "cStandard": "c17", - "cppStandard": "c++20", + "cppStandard": "c++17", "defines": [ "F_CPU=240000000L", "ESP32", @@ -450,450 +450,6 @@ "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/library.properties b/library.properties index 6ea3a91..4687915 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=DShotRMT -version=0.8.0 +version=0.8.3 author=Wastl Kraus maintainer=Wastl Kraus license=MIT diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp index 06bb403..29a591a 100644 --- a/src/DShotRMT.cpp +++ b/src/DShotRMT.cpp @@ -8,15 +8,14 @@ #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) @@ -46,8 +45,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _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,10 +58,8 @@ 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(); - - // Bit positions precalculation _preCalculateBitPositions(); } @@ -81,8 +78,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; } } @@ -91,8 +88,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; } } @@ -104,18 +101,15 @@ DShotRMT::~DShotRMT() } } - // Public Core Functions // Initialize DShotRMT dshot_result_t DShotRMT::begin() { - // Init TX channel if (!_initTXChannel().success) { return {false, TX_INIT_FAILED}; } - // Init RX channel first (for bidirectional mode) if (_is_bidirectional) { if (!_initRXChannel().success) @@ -124,7 +118,6 @@ dshot_result_t DShotRMT::begin() } } - // Init DShot encoder if (!_initDShotEncoder().success) { return {false, ENCODER_INIT_FAILED}; @@ -136,61 +129,51 @@ 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 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); } // 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 + // 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) { + // Calculate motor RPM from eRPM and magnet count uint8_t pole_pairs = max(POLE_PAIRS_MIN, (magnet_count / MAGNETS_PER_POLE_PAIR)); uint32_t motor_rpm = (erpm / pole_pairs); @@ -205,46 +188,29 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) } // Public Info & Debug Functions -// Print timing diagnostic information to specified stream void DShotRMT::printDShotInfo(Stream &output) const { - output.println(" "); - output.println(" === DShot Signal Info === "); - - // Current DShot mode - output.printf("Current Mode: DSHOT%d\n", - _mode == DSHOT150 ? 150 : - _mode == DSHOT300 ? 300 : - _mode == DSHOT600 ? 600 : - _mode == DSHOT1200 ? 1200 : 0); + 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"); - - // 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.print((_parsed_packet >> i) & 1); } - output.printf("\n"); - output.printf("Current Value: %u\n", _packet.throttle_value); + output.printf("\nCurrent Value: %u\n", _packet.throttle_value); } -// Print CPU information +// 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()); @@ -253,27 +219,24 @@ void DShotRMT::printCpuInfo(Stream &output) const } // 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 + // Set the final signal level after transmission + // For bidirectional, line must be high (pulled up) to allow ESC to respond + // For unidirectional, line returns to low (idle) + _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,48 +245,43 @@ dshot_result_t DShotRMT::_initTXChannel() return {true, TX_INIT_SUCCESS}; } -// Initialize RMT RX channel dshot_result_t DShotRMT::_initRXChannel() { - // Check if the bidirectional mode is enabled to be sure + // 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}; } - // Register RX event callback + // 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}; } - - // Enable RX channel + if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) { return {false, RX_INIT_FAILED}; } - // Calculate transmission data size + // 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}; @@ -332,13 +290,9 @@ dshot_result_t DShotRMT::_initRXChannel() 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 + rmt_copy_encoder_config_t encoder_config = {}; if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) { return {false, ENCODER_INIT_FAILED}; @@ -348,77 +302,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; @@ -426,140 +366,123 @@ 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}; } - // Local for performance rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME]; - - // Encode DShot packet into RMT symbols dshot_result_t result = _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); - // 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}; } - // 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; } // Timing Control Functions -// Check if enough time has passed for next transmission 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; } -// Callback for RMT RX +// Static Callback Functions +// 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) { - // Casts the user data back to class instance DShotRMT *instance = static_cast(user_data); - // Process received symbols only if the frame size is correct if (edata && edata->num_symbols == GCR_BITS_PER_FRAME) { - // Parse the GCR frame and store the result uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols); + if (erpm != DSHOT_NULL_PACKET) { - // Atomic writes - thread-safe + // 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 75baeb2..0403808 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 @@ -80,26 +84,19 @@ 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); - + ~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 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 void printDShotInfo(Stream &output = Serial) const; void printCpuInfo(Stream &output = Serial) const; - + // Deprecated Methods [[deprecated("Use sendThrottle() instead")]] bool setThrottle(uint16_t throttle) @@ -123,10 +120,7 @@ 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; @@ -134,15 +128,15 @@ private: 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 DSHOT_RX_TIMEOUT_MS = 2; - static constexpr auto const DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility + static constexpr auto const DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility static constexpr auto const RMT_BUFFER_SYMBOLS = 192; static constexpr auto const RMT_QUEUE_DEPTH = 4; 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 = 1000; // 1.0us minimum pulse - static constexpr auto const DSHOT_PULSE_MAX = 8000; // 10.0us maximum pulse + static constexpr auto const DSHOT_PULSE_MIN = 1000; // 1.0us minimum pulse + static constexpr auto const DSHOT_PULSE_MAX = 8000; // 10.0us maximum pulse static constexpr auto const DSHOT_TELEMETRY_INVALID = DSHOT_THROTTLE_MAX; // Error Messages @@ -183,9 +177,9 @@ private: 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 +212,11 @@ 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); };