prepare 0.8.3

prepare 0.8.3
This commit is contained in:
Wastl Kraus 2025-09-18 11:23:39 +02:00
parent 1f05e2b8ca
commit 39207abe6e
6 changed files with 211 additions and 688 deletions

View File

@ -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 \

3
.gitignore vendored
View File

@ -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

View File

@ -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"
]
}
]
}

View File

@ -1,5 +1,5 @@
name=DShotRMT
version=0.8.0
version=0.8.3
author=Wastl Kraus <wir-sind-die-matrix.de>
maintainer=Wastl Kraus <wir-sind-die-matrix.de>
license=MIT

View File

@ -8,15 +8,14 @@
#include <DShotRMT.h>
// Static Data & Helper Functions
// Timing parameters for each DShot mode
// Format: {bit_length_us, t1h_length_us}
static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = {
{0.00, 0.00},
{6.67, 5.00},
{3.33, 2.50},
{1.67, 1.25},
{0.83, 0.67}};
{0.00, 0.00}, // DSHOT_OFF
{6.67, 5.00}, // DSHOT150
{3.33, 2.50}, // DSHOT300
{1.67, 1.25}, // DSHOT600
{0.83, 0.67}}; // DSHOT1200
// Helper function to print DShot results and telemetry
void printDShotResult(dshot_result_t &result, Stream &output)
@ -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<uint16_t>(_dshot_timing.bit_length_us * RMT_TICKS_PER_US);
_rmt_ticks.t1h_ticks = static_cast<uint16_t>(_dshot_timing.t1h_lenght_us * RMT_TICKS_PER_US);
_rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double that of a 0
_rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double of 0
_rmt_ticks.t1l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t1h_ticks;
_rmt_ticks.t0l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t0h_ticks;
// Calculate the minimum time required between frames
// Pause between frames is frame time in us, some padding and about 30 us is added by hardware
_frame_timer_us = (static_cast<uint64_t>(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US;
// Double frame time for bidirectional mode (includes response time)
// For bidirectional, double up
if (_is_bidirectional)
{
_frame_timer_us = (_frame_timer_us << 1);
}
}
// Precalculate bit positions for performance optimization
void DShotRMT::_preCalculateBitPositions()
{
// Pre-calculate bit positions to avoid redundant calculations in the encoding loop
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
_bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i;
@ -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<DShotRMT *>(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);
}

View File

@ -1,8 +1,8 @@
/**
* @file DShotRMT.h
* @brief DShot signal generation using ESP32 RMT with bidirectional support
* @brief Optimized DShot signal generation using ESP32 RMT with bidirectional support
* @author Wastl Kraus
* @date 2025-06-11
* @date 2025-09-18
* @license MIT
*/
@ -22,6 +22,10 @@ static constexpr auto DSHOT_THROTTLE_MAX = 2047;
static constexpr auto DSHOT_BITS_PER_FRAME = 16;
static constexpr auto DEFAULT_MOTOR_MAGNET_COUNT = 14;
// Custom status codes
static constexpr auto DSHOT_OK = 0;
static constexpr auto DSHOT_ERROR = 1;
// DShot Modes
typedef enum dshot_mode
{
@ -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);
};