diff --git a/.vscode/arduino.json b/.vscode/arduino.json index a0ddeba..7dd99e1 100644 --- a/.vscode/arduino.json +++ b/.vscode/arduino.json @@ -1,8 +1,9 @@ { - "board": "arduino:avr:micro", + "board": "arduino:esp32:nano_nora", "programmer": "arduino:avrispmkii", "port": "/dev/ttyACM0", - "sketch": "truckLightAndFunction.ino", + "sketch": "truck-multi-function-sbus.ino", "output": "./build", - "intelliSenseGen": "global" + "intelliSenseGen": "global", + "configuration": "PartitionScheme=default,PinNumbers=default,USBMode=default" } \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index cda8204..d3ba902 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -2,8 +2,8 @@ "version": 4, "configurations": [ { - "name": "Arduino", - "compilerPath": "/home/magraina/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin/avr-g++", + "name": "Arduino Nano ESP32", + "compilerPath": "/home/magraina/.arduino15/packages/esp32/tools/s3-gcc/2021r2-p5/bin/xtensa-esp32s3-elf-g++", "compilerArgs": [ "-w", "-std=gnu++11", @@ -17,526 +17,35 @@ "intelliSenseMode": "gcc-x64", "includePath": [ "${workspaceFolder}/**", - "/home/magraina/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino", - "/home/magraina/.arduino15/packages/arduino/hardware/avr/1.8.6/variants/micro", - "/home/magraina/Arduino/libraries/**", - "/home/magraina/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/lib/gcc/avr/7.3.0/include", - "/home/magraina/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/lib/gcc/avr/7.3.0/include-fixed", - "/home/magraina/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/avr/include" + // 1. Core & Variant (Basis Arduino ESP32) + "/home/magraina/.arduino15/packages/arduino/hardware/esp32/2.0.18-arduino.5/cores/esp32", + "/home/magraina/.arduino15/packages/arduino/hardware/esp32/2.0.18-arduino.5/variants/arduino_nano_nora", + + // 2. Die magische sdkconfig.h für das Nano ESP32 Board + "/home/magraina/.arduino15/packages/arduino/hardware/esp32/2.0.18-arduino.5/tools/sdk/esp32s3/qio_opi/include", + + // 3. ESP-IDF Framework Includes (Hier liegt die hal.h und alle esp_... Dateien) + "/home/magraina/.arduino15/packages/arduino/hardware/esp32/2.0.18-arduino.5/tools/sdk/esp32s3/include/**", + + // 4. Eigene & System-Bibliotheken (ESP32Servo, ArduinoJson, Preferences etc.) + "/home/magraina/.arduino15/packages/arduino/hardware/esp32/2.0.18-arduino.5/libraries/**", + "/home/magraina/Arduino/libraries/**" ], "forcedInclude": [ - "/home/magraina/.arduino15/packages/arduino/hardware/avr/1.8.6/cores/arduino/Arduino.h" + "/home/magraina/.arduino15/packages/arduino/hardware/esp32/2.0.18-arduino.5/cores/esp32/Arduino.h" ], "cStandard": "c11", "cppStandard": "c++11", "defines": [ - "F_CPU=16000000L", - "ARDUINO=10607", - "ARDUINO_AVR_MICRO", - "ARDUINO_ARCH_AVR", - "USB_VID=0x2341", - "USB_PID=0x8037", - "USB_MANUFACTURER=\"Unknown\"", - "USB_PRODUCT=\"Arduino Micro\"", - "__DBL_MIN_EXP__=(-125)", - "__HQ_FBIT__=15", - "__cpp_attributes=200809", - "__UINT_LEAST16_MAX__=0xffffU", - "__ATOMIC_ACQUIRE=2", - "__SFRACT_IBIT__=0", - "__FLT_MIN__=1.17549435e-38F", - "__GCC_IEC_559_COMPLEX=0", - "__BUILTIN_AVR_SLEEP=1", - "__BUILTIN_AVR_COUNTLSULLK=1", - "__cpp_aggregate_nsdmi=201304", - "__BUILTIN_AVR_COUNTLSULLR=1", - "__UFRACT_MAX__=0XFFFFP-16UR", - "__UINT_LEAST8_TYPE__=unsigned char", - "__DQ_FBIT__=63", - "__INTMAX_C(c)=c ## LL", - "__ULFRACT_FBIT__=32", - "__SACCUM_EPSILON__=0x1P-7HK", - "__CHAR_BIT__=8", - "__USQ_IBIT__=0", - "__UINT8_MAX__=0xff", - "__ACCUM_FBIT__=15", - "__WINT_MAX__=0x7fff", - "__FLT32_MIN_EXP__=(-125)", - "__cpp_static_assert=200410", - "__USFRACT_FBIT__=8", - "__ORDER_LITTLE_ENDIAN__=1234", - "__SIZE_MAX__=0xffffU", - "__WCHAR_MAX__=0x7fff", - "__LACCUM_IBIT__=32", - "__DBL_DENORM_MIN__=double(1.40129846e-45L)", - "__GCC_ATOMIC_CHAR_LOCK_FREE=1", - "__GCC_IEC_559=0", - "__FLT_EVAL_METHOD__=0", - "__BUILTIN_AVR_LLKBITS=1", - "__cpp_binary_literals=201304", - "__LLACCUM_MAX__=0X7FFFFFFFFFFFFFFFP-47LLK", - "__GCC_ATOMIC_CHAR32_T_LOCK_FREE=1", - "__BUILTIN_AVR_HKBITS=1", - "__BUILTIN_AVR_BITSLLK=1", - "__FRACT_FBIT__=15", - "__BUILTIN_AVR_BITSLLR=1", - "__cpp_variadic_templates=200704", - "__UINT_FAST64_MAX__=0xffffffffffffffffULL", - "__SIG_ATOMIC_TYPE__=char", - "__BUILTIN_AVR_UHKBITS=1", - "__UACCUM_FBIT__=16", - "__DBL_MIN_10_EXP__=(-37)", - "__FINITE_MATH_ONLY__=0", - "__cpp_variable_templates=201304", - "__LFRACT_IBIT__=0", - "__GNUC_PATCHLEVEL__=0", - "__FLT32_HAS_DENORM__=1", - "__LFRACT_MAX__=0X7FFFFFFFP-31LR", - "__UINT_FAST8_MAX__=0xff", - "__has_include(STR)=__has_include__(STR)", - "__DEC64_MAX_EXP__=385", - "__INT8_C(c)=c", - "__INT_LEAST8_WIDTH__=8", - "__UINT_LEAST64_MAX__=0xffffffffffffffffULL", - "__SA_FBIT__=15", - "__SHRT_MAX__=0x7fff", - "__LDBL_MAX__=3.40282347e+38L", - "__FRACT_MAX__=0X7FFFP-15R", - "__UFRACT_FBIT__=16", - "__UFRACT_MIN__=0.0UR", - "__UINT_LEAST8_MAX__=0xff", - "__GCC_ATOMIC_BOOL_LOCK_FREE=1", - "__UINTMAX_TYPE__=long long unsigned int", - "__LLFRACT_EPSILON__=0x1P-63LLR", - "__BUILTIN_AVR_DELAY_CYCLES=1", - "__DEC32_EPSILON__=1E-6DF", - "__FLT_EVAL_METHOD_TS_18661_3__=0", - "__UINT32_MAX__=0xffffffffUL", - "__GXX_EXPERIMENTAL_CXX0X__=1", - "__ULFRACT_MAX__=0XFFFFFFFFP-32ULR", - "__TA_IBIT__=16", - "__LDBL_MAX_EXP__=128", - "__WINT_MIN__=(-__WINT_MAX__ - 1)", - "__INT_LEAST16_WIDTH__=16", - "__ULLFRACT_MIN__=0.0ULLR", - "__SCHAR_MAX__=0x7f", - "__WCHAR_MIN__=(-__WCHAR_MAX__ - 1)", - "__INT64_C(c)=c ## LL", - "__DBL_DIG__=6", - "__GCC_ATOMIC_POINTER_LOCK_FREE=1", - "__AVR_HAVE_SPH__=1", - "__LLACCUM_MIN__=(-0X1P15LLK-0X1P15LLK)", - "__BUILTIN_AVR_KBITS=1", - "__BUILTIN_AVR_ABSK=1", - "__BUILTIN_AVR_ABSR=1", - "__SIZEOF_INT__=2", - "__SIZEOF_POINTER__=2", - "__GCC_ATOMIC_CHAR16_T_LOCK_FREE=1", - "__USACCUM_IBIT__=8", - "__USER_LABEL_PREFIX__", - "__STDC_HOSTED__=1", - "__LDBL_HAS_INFINITY__=1", - "__LFRACT_MIN__=(-0.5LR-0.5LR)", - "__HA_IBIT__=8", - "__FLT32_DIG__=6", - "__TQ_IBIT__=0", - "__FLT_EPSILON__=1.19209290e-7F", - "__GXX_WEAK__=1", - "__SHRT_WIDTH__=16", - "__USFRACT_IBIT__=0", - "__LDBL_MIN__=1.17549435e-38L", - "__FRACT_MIN__=(-0.5R-0.5R)", - "__AVR_SFR_OFFSET__=0x20", - "__DEC32_MAX__=9.999999E96DF", - "__cpp_threadsafe_static_init=200806", - "__DA_IBIT__=32", - "__INT32_MAX__=0x7fffffffL", - "__UQQ_FBIT__=8", - "__INT_WIDTH__=16", - "__SIZEOF_LONG__=4", - "__UACCUM_MAX__=0XFFFFFFFFP-16UK", - "__UINT16_C(c)=c ## U", - "__PTRDIFF_WIDTH__=16", - "__DECIMAL_DIG__=9", - "__LFRACT_EPSILON__=0x1P-31LR", - "__AVR_2_BYTE_PC__=1", - "__ULFRACT_MIN__=0.0ULR", - "__INTMAX_WIDTH__=64", - "__has_include_next(STR)=__has_include_next__(STR)", - "__BUILTIN_AVR_ULLRBITS=1", - "__LDBL_HAS_QUIET_NAN__=1", - "__ULACCUM_IBIT__=32", - "__UACCUM_EPSILON__=0x1P-16UK", - "__BUILTIN_AVR_SEI=1", - "__GNUC__=7", - "__ULLACCUM_MAX__=0XFFFFFFFFFFFFFFFFP-48ULLK", - "__cpp_delegating_constructors=200604", - "__HQ_IBIT__=0", - "__BUILTIN_AVR_SWAP=1", - "__FLT_HAS_DENORM__=1", - "__SIZEOF_LONG_DOUBLE__=4", - "__BIGGEST_ALIGNMENT__=1", - "__STDC_UTF_16__=1", - "__UINT24_MAX__=16777215UL", - "__BUILTIN_AVR_NOP=1", - "__GNUC_STDC_INLINE__=1", - "__DQ_IBIT__=0", - "__FLT32_HAS_INFINITY__=1", - "__DBL_MAX__=double(3.40282347e+38L)", - "__ULFRACT_IBIT__=0", - "__cpp_raw_strings=200710", - "__INT_FAST32_MAX__=0x7fffffffL", - "__DBL_HAS_INFINITY__=1", - "__INT64_MAX__=0x7fffffffffffffffLL", - "__ACCUM_IBIT__=16", - "__DEC32_MIN_EXP__=(-94)", - "__BUILTIN_AVR_UKBITS=1", - "__INTPTR_WIDTH__=16", - "__BUILTIN_AVR_FMULSU=1", - "__LACCUM_MAX__=0X7FFFFFFFFFFFFFFFP-31LK", - "__INT_FAST16_TYPE__=int", - "__LDBL_HAS_DENORM__=1", - "__BUILTIN_AVR_BITSK=1", - "__BUILTIN_AVR_BITSR=1", - "__cplusplus=201402L", - "__cpp_ref_qualifiers=200710", - "__DEC128_MAX__=9.999999999999999999999999999999999E6144DL", - "__INT_LEAST32_MAX__=0x7fffffffL", - "__USING_SJLJ_EXCEPTIONS__=1", - "__DEC32_MIN__=1E-95DF", - "__ACCUM_MAX__=0X7FFFFFFFP-15K", - "__DEPRECATED=1", - "__cpp_rvalue_references=200610", - "__DBL_MAX_EXP__=128", - "__USACCUM_EPSILON__=0x1P-8UHK", - "__WCHAR_WIDTH__=16", - "__FLT32_MAX__=3.40282347e+38F32", - "__DEC128_EPSILON__=1E-33DL", - "__SFRACT_MAX__=0X7FP-7HR", - "__FRACT_IBIT__=0", - "__PTRDIFF_MAX__=0x7fff", - "__UACCUM_MIN__=0.0UK", - "__UACCUM_IBIT__=16", - "__BUILTIN_AVR_NOPS=1", - "__BUILTIN_AVR_WDR=1", - "__FLT32_HAS_QUIET_NAN__=1", - "__GNUG__=7", - "__LONG_LONG_MAX__=0x7fffffffffffffffLL", - "__SIZEOF_SIZE_T__=2", - "__ULACCUM_MAX__=0XFFFFFFFFFFFFFFFFP-32ULK", - "__cpp_rvalue_reference=200610", - "__cpp_nsdmi=200809", - "__SIZEOF_WINT_T__=2", - "__LONG_LONG_WIDTH__=64", - "__cpp_initializer_lists=200806", - "__FLT32_MAX_EXP__=128", - "__SA_IBIT__=16", - "__ULLACCUM_MIN__=0.0ULLK", - "__BUILTIN_AVR_ROUNDUHK=1", - "__BUILTIN_AVR_ROUNDUHR=1", - "__cpp_hex_float=201603", - "__GXX_ABI_VERSION=1011", - "__INT24_MAX__=8388607L", - "__UTA_FBIT__=48", - "__FLT_MIN_EXP__=(-125)", - "__USFRACT_MAX__=0XFFP-8UHR", - "__UFRACT_IBIT__=0", - "__BUILTIN_AVR_ROUNDFX=1", - "__BUILTIN_AVR_ROUNDULK=1", - "__BUILTIN_AVR_ROUNDULR=1", - "__cpp_lambdas=200907", - "__BUILTIN_AVR_COUNTLSLLK=1", - "__BUILTIN_AVR_COUNTLSLLR=1", - "__BUILTIN_AVR_ROUNDHK=1", - "__INT_FAST64_TYPE__=long long int", - "__BUILTIN_AVR_ROUNDHR=1", - "__DBL_MIN__=double(1.17549435e-38L)", - "__BUILTIN_AVR_COUNTLSK=1", - "__BUILTIN_AVR_ROUNDLK=1", - "__BUILTIN_AVR_COUNTLSR=1", - "__BUILTIN_AVR_ROUNDLR=1", - "__LACCUM_MIN__=(-0X1P31LK-0X1P31LK)", - "__ULLACCUM_FBIT__=48", - "__BUILTIN_AVR_LKBITS=1", - "__ULLFRACT_EPSILON__=0x1P-64ULLR", - "__DEC128_MIN__=1E-6143DL", - "__REGISTER_PREFIX__", - "__UINT16_MAX__=0xffffU", - "__DBL_HAS_DENORM__=1", - "__BUILTIN_AVR_ULKBITS=1", - "__ACCUM_MIN__=(-0X1P15K-0X1P15K)", - "__AVR_ARCH__=2", - "__SQ_IBIT__=0", - "__FLT32_MIN__=1.17549435e-38F32", - "__UINT8_TYPE__=unsigned char", - "__BUILTIN_AVR_ROUNDUK=1", - "__BUILTIN_AVR_ROUNDUR=1", - "__UHA_FBIT__=8", - "__NO_INLINE__=1", - "__SFRACT_MIN__=(-0.5HR-0.5HR)", - "__UTQ_FBIT__=128", - "__FLT_MANT_DIG__=24", - "__LDBL_DECIMAL_DIG__=9", - "__VERSION__=\"7.3.0\"", - "__UINT64_C(c)=c ## ULL", - "__ULLFRACT_FBIT__=64", - "__cpp_unicode_characters=200704", - "__FRACT_EPSILON__=0x1P-15R", - "__ULACCUM_MIN__=0.0ULK", - "__UDA_FBIT__=32", - "__cpp_decltype_auto=201304", - "__LLACCUM_EPSILON__=0x1P-47LLK", - "__GCC_ATOMIC_INT_LOCK_FREE=1", - "__FLT32_MANT_DIG__=24", - "__BUILTIN_AVR_BITSUHK=1", - "__BUILTIN_AVR_BITSUHR=1", - "__FLOAT_WORD_ORDER__=__ORDER_LITTLE_ENDIAN__", - "__USFRACT_MIN__=0.0UHR", - "__BUILTIN_AVR_BITSULK=1", - "__ULLACCUM_IBIT__=16", - "__BUILTIN_AVR_BITSULR=1", - "__UQQ_IBIT__=0", - "__BUILTIN_AVR_LLRBITS=1", - "__SCHAR_WIDTH__=8", - "__BUILTIN_AVR_BITSULLK=1", - "__BUILTIN_AVR_BITSULLR=1", - "__INT32_C(c)=c ## L", - "__DEC64_EPSILON__=1E-15DD", - "__ORDER_PDP_ENDIAN__=3412", - "__DEC128_MIN_EXP__=(-6142)", - "__UHQ_FBIT__=16", - "__LLACCUM_FBIT__=47", - "__FLT32_MAX_10_EXP__=38", - "__BUILTIN_AVR_ROUNDULLK=1", - "__BUILTIN_AVR_ROUNDULLR=1", - "__INT_FAST32_TYPE__=long int", - "__BUILTIN_AVR_HRBITS=1", - "__UINT_LEAST16_TYPE__=unsigned int", - "__BUILTIN_AVR_UHRBITS=1", - "__INT16_MAX__=0x7fff", - "__SIZE_TYPE__=unsigned int", - "__UINT64_MAX__=0xffffffffffffffffULL", - "__UDQ_FBIT__=64", - "__INT8_TYPE__=signed char", - "__cpp_digit_separators=201309", - "__ELF__=1", - "__ULFRACT_EPSILON__=0x1P-32ULR", - "__LLFRACT_FBIT__=63", - "__FLT_RADIX__=2", - "__INT_LEAST16_TYPE__=int", - "__BUILTIN_AVR_ABSFX=1", - "__LDBL_EPSILON__=1.19209290e-7L", - "__UINTMAX_C(c)=c ## ULL", - "__INT24_MIN__=(-__INT24_MAX__-1)", - "__SACCUM_MAX__=0X7FFFP-7HK", - "__BUILTIN_AVR_ABSHR=1", - "__SIG_ATOMIC_MAX__=0x7f", - "__GCC_ATOMIC_WCHAR_T_LOCK_FREE=1", - "__cpp_sized_deallocation=201309", - "__SIZEOF_PTRDIFF_T__=2", - "__AVR=1", - "__BUILTIN_AVR_ABSLK=1", - "__BUILTIN_AVR_ABSLR=1", - "__LACCUM_EPSILON__=0x1P-31LK", - "__DEC32_SUBNORMAL_MIN__=0.000001E-95DF", - "__INT_FAST16_MAX__=0x7fff", - "__UINT_FAST32_MAX__=0xffffffffUL", - "__UINT_LEAST64_TYPE__=long long unsigned int", - "__USACCUM_MAX__=0XFFFFP-8UHK", - "__SFRACT_EPSILON__=0x1P-7HR", - "__FLT_HAS_QUIET_NAN__=1", - "__FLT_MAX_10_EXP__=38", - "__LONG_MAX__=0x7fffffffL", - "__DEC128_SUBNORMAL_MIN__=0.000000000000000000000000000000001E-6143DL", - "__FLT_HAS_INFINITY__=1", - "__cpp_unicode_literals=200710", - "__USA_FBIT__=16", - "__UINT_FAST16_TYPE__=unsigned int", - "__DEC64_MAX__=9.999999999999999E384DD", - "__INT_FAST32_WIDTH__=32", - "__BUILTIN_AVR_RBITS=1", - "__CHAR16_TYPE__=unsigned int", - "__PRAGMA_REDEFINE_EXTNAME=1", - "__SIZE_WIDTH__=16", - "__INT_LEAST16_MAX__=0x7fff", - "__DEC64_MANT_DIG__=16", - "__UINT_LEAST32_MAX__=0xffffffffUL", - "__SACCUM_FBIT__=7", - "__FLT32_DENORM_MIN__=1.40129846e-45F32", - "__GCC_ATOMIC_LONG_LOCK_FREE=1", - "__SIG_ATOMIC_WIDTH__=8", - "__INT_LEAST64_TYPE__=long long int", - "__INT16_TYPE__=int", - "__INT_LEAST8_TYPE__=signed char", - "__SQ_FBIT__=31", - "__DEC32_MAX_EXP__=97", - "__INT_FAST8_MAX__=0x7f", - "__INTPTR_MAX__=0x7fff", - "__QQ_FBIT__=7", - "__cpp_range_based_for=200907", - "__UTA_IBIT__=16", - "__AVR_ERRATA_SKIP__=1", - "__FLT32_MIN_10_EXP__=(-37)", - "__LDBL_MANT_DIG__=24", - "__SFRACT_FBIT__=7", - "__SACCUM_MIN__=(-0X1P7HK-0X1P7HK)", - "__DBL_HAS_QUIET_NAN__=1", - "__SIG_ATOMIC_MIN__=(-__SIG_ATOMIC_MAX__ - 1)", - "AVR=1", - "__BUILTIN_AVR_FMULS=1", - "__cpp_return_type_deduction=201304", - "__INTPTR_TYPE__=int", - "__UINT16_TYPE__=unsigned int", - "__WCHAR_TYPE__=int", - "__SIZEOF_FLOAT__=4", - "__AVR__=1", - "__BUILTIN_AVR_INSERT_BITS=1", - "__USQ_FBIT__=32", - "__UINTPTR_MAX__=0xffffU", - "__INT_FAST64_WIDTH__=64", - "__DEC64_MIN_EXP__=(-382)", - "__cpp_decltype=200707", - "__FLT32_DECIMAL_DIG__=9", - "__INT_FAST64_MAX__=0x7fffffffffffffffLL", - "__GCC_ATOMIC_TEST_AND_SET_TRUEVAL=1", - "__FLT_DIG__=6", - "__UINT_FAST64_TYPE__=long long unsigned int", - "__BUILTIN_AVR_BITSHK=1", - "__BUILTIN_AVR_BITSHR=1", - "__INT_MAX__=0x7fff", - "__LACCUM_FBIT__=31", - "__USACCUM_MIN__=0.0UHK", - "__UHA_IBIT__=8", - "__INT64_TYPE__=long long int", - "__BUILTIN_AVR_BITSLK=1", - "__BUILTIN_AVR_BITSLR=1", - "__FLT_MAX_EXP__=128", - "__UTQ_IBIT__=0", - "__DBL_MANT_DIG__=24", - "__cpp_inheriting_constructors=201511", - "__BUILTIN_AVR_ULLKBITS=1", - "__INT_LEAST64_MAX__=0x7fffffffffffffffLL", - "__DEC64_MIN__=1E-383DD", - "__WINT_TYPE__=int", - "__UINT_LEAST32_TYPE__=long unsigned int", - "__SIZEOF_SHORT__=2", - "__ULLFRACT_IBIT__=0", - "__LDBL_MIN_EXP__=(-125)", - "__UDA_IBIT__=32", - "__WINT_WIDTH__=16", - "__INT_LEAST8_MAX__=0x7f", - "__LFRACT_FBIT__=31", - "__LDBL_MAX_10_EXP__=38", - "__ATOMIC_RELAXED=0", - "__DBL_EPSILON__=double(1.19209290e-7L)", - "__BUILTIN_AVR_BITSUK=1", - "__BUILTIN_AVR_BITSUR=1", - "__UINT8_C(c)=c", - "__INT_LEAST32_TYPE__=long int", - "__BUILTIN_AVR_URBITS=1", - "__SIZEOF_WCHAR_T__=2", - "__LLFRACT_MAX__=0X7FFFFFFFFFFFFFFFP-63LLR", - "__TQ_FBIT__=127", - "__INT_FAST8_TYPE__=signed char", - "__ULLACCUM_EPSILON__=0x1P-48ULLK", - "__BUILTIN_AVR_ROUNDK=1", - "__BUILTIN_AVR_ROUNDR=1", - "__UHQ_IBIT__=0", - "__LLACCUM_IBIT__=16", - "__FLT32_EPSILON__=1.19209290e-7F32", - "__DBL_DECIMAL_DIG__=9", - "__STDC_UTF_32__=1", - "__INT_FAST8_WIDTH__=8", - "__DEC_EVAL_METHOD__=2", - "__TA_FBIT__=47", - "__UDQ_IBIT__=0", - "__ORDER_BIG_ENDIAN__=4321", - "__cpp_runtime_arrays=198712", - "__WITH_AVRLIBC__=1", - "__UINT64_TYPE__=long long unsigned int", - "__ACCUM_EPSILON__=0x1P-15K", - "__UINT32_C(c)=c ## UL", - "__BUILTIN_AVR_COUNTLSUHK=1", - "__INTMAX_MAX__=0x7fffffffffffffffLL", - "__cpp_alias_templates=200704", - "__BUILTIN_AVR_COUNTLSUHR=1", - "__BYTE_ORDER__=__ORDER_LITTLE_ENDIAN__", - "__FLT_DENORM_MIN__=1.40129846e-45F", - "__LLFRACT_IBIT__=0", - "__INT8_MAX__=0x7f", - "__LONG_WIDTH__=32", - "__UINT_FAST32_TYPE__=long unsigned int", - "__CHAR32_TYPE__=long unsigned int", - "__BUILTIN_AVR_COUNTLSULK=1", - "__BUILTIN_AVR_COUNTLSULR=1", - "__FLT_MAX__=3.40282347e+38F", - "__cpp_constexpr=201304", - "__USACCUM_FBIT__=8", - "__BUILTIN_AVR_COUNTLSFX=1", - "__INT32_TYPE__=long int", - "__SIZEOF_DOUBLE__=4", - "__FLT_MIN_10_EXP__=(-37)", - "__UFRACT_EPSILON__=0x1P-16UR", - "__INT_LEAST32_WIDTH__=32", - "__BUILTIN_AVR_COUNTLSHK=1", - "__BUILTIN_AVR_COUNTLSHR=1", - "__INTMAX_TYPE__=long long int", - "__BUILTIN_AVR_ABSLLK=1", - "__BUILTIN_AVR_ABSLLR=1", - "__DEC128_MAX_EXP__=6145", - "__AVR_HAVE_16BIT_SP__=1", - "__ATOMIC_CONSUME=1", - "__GNUC_MINOR__=3", - "__INT_FAST16_WIDTH__=16", - "__UINTMAX_MAX__=0xffffffffffffffffULL", - "__DEC32_MANT_DIG__=7", - "__HA_FBIT__=7", - "__BUILTIN_AVR_COUNTLSLK=1", - "__BUILTIN_AVR_COUNTLSLR=1", - "__BUILTIN_AVR_CLI=1", - "__DBL_MAX_10_EXP__=38", - "__LDBL_DENORM_MIN__=1.40129846e-45L", - "__INT16_C(c)=c", - "__cpp_generic_lambdas=201304", - "__STDC__=1", - "__PTRDIFF_TYPE__=int", - "__LLFRACT_MIN__=(-0.5LLR-0.5LLR)", - "__BUILTIN_AVR_LRBITS=1", - "__ATOMIC_SEQ_CST=5", - "__DA_FBIT__=31", - "__UINT32_TYPE__=long unsigned int", - "__BUILTIN_AVR_ROUNDLLK=1", - "__UINTPTR_TYPE__=unsigned int", - "__BUILTIN_AVR_ROUNDLLR=1", - "__USA_IBIT__=16", - "__BUILTIN_AVR_ULRBITS=1", - "__DEC64_SUBNORMAL_MIN__=0.000000000000001E-383DD", - "__DEC128_MANT_DIG__=34", - "__LDBL_MIN_10_EXP__=(-37)", - "__BUILTIN_AVR_COUNTLSUK=1", - "__BUILTIN_AVR_COUNTLSUR=1", - "__SIZEOF_LONG_LONG__=8", - "__ULACCUM_EPSILON__=0x1P-32ULK", - "__cpp_user_defined_literals=200809", - "__SACCUM_IBIT__=8", - "__GCC_ATOMIC_LLONG_LOCK_FREE=1", - "__LDBL_DIG__=6", - "__FLT_DECIMAL_DIG__=9", - "__UINT_FAST16_MAX__=0xffffU", - "__GCC_ATOMIC_SHORT_LOCK_FREE=1", - "__BUILTIN_AVR_ABSHK=1", - "__BUILTIN_AVR_FLASH_SEGMENT=1", - "__INT_LEAST64_WIDTH__=64", - "__ULLFRACT_MAX__=0XFFFFFFFFFFFFFFFFP-64ULLR", - "__UINT_FAST8_TYPE__=unsigned char", - "__USFRACT_EPSILON__=0x1P-8UHR", - "__ULACCUM_FBIT__=32", - "__QQ_IBIT__=0", - "__cpp_init_captures=201304", - "__ATOMIC_ACQ_REL=4", - "__ATOMIC_RELEASE=3", - "__BUILTIN_AVR_FMUL=1", - "USBCON" + "IRAM_ATTR=", + "ESP32", + "ARDUINO=10819", + "ARDUINO_NANO_ESP32", + "ARDUINO_ARCH_ESP32", + "ARDUINO_USB_MODE=1", + "ARDUINO_USB_CDC_ON_BOOT=1", + "USBCON", + "F_CPU=240000000L" ] } ] diff --git a/config.h b/config.h deleted file mode 100644 index 7bf3519..0000000 --- a/config.h +++ /dev/null @@ -1,174 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#ifndef _CONFIG_H_ -#define _CONFIG_H_ - -#include "vehicleConfig.h" - -/************************************ - * Configuration Programm - ************************************/ - -VehicleConfig vehicleConfig = { - .generalConfig = { - .countryOption = CountryOption::US, - .debugLevel = DebugLevel::STATUS_ONLY, - .statusLightPin = 13 - }, - .ppmConfig = { - .pinChannel1 = 2, - .pinChannel2 = 3, - .pinSoundChannel = 7 - }, - .generalLightConfig = { - .fadeOnTime = 200, - .fadeOffTime = 200, - .starterDimmingFactor = 5, - .starterDimmingMultiplier = 2 - }, - .serialConfig = { - .isEnabled = true, - .outTxEnablePin = 4, - .baudRate = 19200, - .byteFormat = SERIAL_8N1, - .timeout = 1000, - .pollingInterval = 20, - .protocolVersion = ProtocolVersion::V2 - }, - .lightInputChannel = { - .reverseSignal = A4, - .brakeSignal = A5 - }, - .rearLeftTurnLight = { - .outputPin = 5, - .primaryOnBrightness = 255, - // Parking when combined - .secondaryOnBrightness = 15, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200, - }, - .rearRightTurnLight = { - .outputPin = 6, - .primaryOnBrightness = 255, - // Parking when combined - .secondaryOnBrightness = 15, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .frontLeftTurnLight = { - .outputPin = 8, - .primaryOnBrightness = 255, - .secondaryOnBrightness = 0, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .frontRightTurnLight = { - .outputPin = 9, - .primaryOnBrightness = 255, - .secondaryOnBrightness = 0, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .lowBeamLight = { - .outputPin = 10, - // Low Beam or High beam when combined - .primaryOnBrightness = 255, - // Low Beam when combined with High Beam - .secondaryOnBrightness = 100, - // Parking when combined with Low Beam - .tertiaryOnBrightness = 5, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .highBeamLight = { - .outputPin = 11, - .primaryOnBrightness = 255, - .secondaryOnBrightness = 0, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .parkingLight = { - .outputPin = A3, - .primaryOnBrightness = 255, - .secondaryOnBrightness = 0, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .fogLight = { - .outputPin = A2, - .primaryOnBrightness = 255, - .secondaryOnBrightness = 0, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .reverseLight = { - .outputPin = A1, - .primaryOnBrightness = 255, - .secondaryOnBrightness = 0, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .brakeLight = { - .outputPin = 12, - .primaryOnBrightness = 255, - // Parking when combined - .secondaryOnBrightness = 15, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .auxLight = { - .outputPin = A0, - .primaryOnBrightness = 255, - .secondaryOnBrightness = 0, - .tertiaryOnBrightness = 0, - .offBrightness = 0, - .fadeOnTime = 200, - .fadeOffTime = 200 - }, - .lowBeamConfig = { - .isParkingLight = true, - .isHighBeam = true - }, - .highBeamConfig = { - .flashFrequency = 800 - }, - .turnSignalConfig = { - .flashFrequency = 1000 - } -}; - -#define DEBUGLEVEL vehicleConfig.generalConfig.debugLevel - -#endif \ No newline at end of file diff --git a/debugging.cpp b/debugging.cpp deleted file mode 100644 index 3d846f1..0000000 --- a/debugging.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/************************************s - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#include "debugging.h" - -bool pulseStatus = false; -uint32_t StatusPreviousMillis = 0; -uint32_t blinkOnTime = 0; -bool serialIsSent[3] = { false }; - - -void debuggingInit(uint8_t debugLevel, uint8_t outputLED) { - if(debugLevel >=1) pinMode(outputLED, OUTPUT); - if(debugLevel >=2) SerialUSB.begin(9600); // start Serial for Monitoring -} - -void controllerStatus(bool errorFlag, uint8_t outputLED) { - if(errorFlag) { - digitalWrite(outputLED, true); - } else { - uint32_t currentMillis = millis(); - if (currentMillis - StatusPreviousMillis >= 1000) { //Zeitverzoegerte Abfrage - StatusPreviousMillis = currentMillis; - pulseStatus = !pulseStatus; - } else if (currentMillis < StatusPreviousMillis) { //Reset - StatusPreviousMillis = currentMillis; - } - digitalWrite(outputLED, pulseStatus); //Flash if everything is OK - } -} - -void debugChannelEvaluation( - uint16_t channelNumber, - uint16_t channel1, - uint16_t channel2, - uint16_t channel3, - uint16_t channel4, - uint16_t channel5, - uint16_t channel6, - uint16_t channel7, - uint16_t channel8 -) { - if((millis()%1000 >= 500) && (serialIsSent[0] == false)) { - SerialUSB.print(F("--Multiswitch ")); - SerialUSB.print(channelNumber); - SerialUSB.println(F(" --")); - SerialUSB.print(F("channel 1: ")); - SerialUSB.println(channel1); - SerialUSB.print(F("channel 2: ")); - SerialUSB.println(channel2); - SerialUSB.print(F("channel 3: ")); - SerialUSB.println(channel3); - SerialUSB.print(F("channel 4: ")); - SerialUSB.println(channel4); - SerialUSB.print(F("channel 5: ")); - SerialUSB.println(channel5); - SerialUSB.print(F("channel 6: ")); - SerialUSB.println(channel6); - SerialUSB.print(F("channel 7: ")); - SerialUSB.println(channel7); - SerialUSB.print(F("channel 8: ")); - SerialUSB.println(channel8); - SerialUSB.println(F("-------End-------")); - serialIsSent[0] = true; - } else if((millis()%1000 < 500) && (serialIsSent[0] == true)) { - serialIsSent[0] = false; - } -} - -void debugFunctionState(bool parkLight, - bool lowBeamLight, - bool highBeamLight, - bool highBeamLightFlash, - bool fogLight, - bool beaconLight, - bool auxLight, - bool hazardLight, - bool leftIndicatorLight, - bool rightIndicatorLight, - bool reverseLight, - bool brakeLight) { - if((millis()%1000 >= 500) && (serialIsSent[1] == false)) { - SerialUSB.println(F("-- Light State --")); - SerialUSB.print(F("parkLight: ")); - SerialUSB.println(parkLight); - SerialUSB.print(F("lowBeamLight: ")); - SerialUSB.println(lowBeamLight); - SerialUSB.print(F("highBeamLight: ")); - SerialUSB.println(highBeamLight); - SerialUSB.print(F("highBeamLightFlash: ")); - SerialUSB.println(highBeamLightFlash); - SerialUSB.print(F("fogLight: ")); - SerialUSB.println(fogLight); - SerialUSB.print(F("beaconLight: ")); - SerialUSB.println(beaconLight); - SerialUSB.print(F("auxLight: ")); - SerialUSB.println(auxLight); - SerialUSB.print(F("hazardLight: ")); - SerialUSB.println(hazardLight); - SerialUSB.print(F("leftIndicatorLight: ")); - SerialUSB.println(leftIndicatorLight); - SerialUSB.print(F("rightIndicatorLight: ")); - SerialUSB.println(rightIndicatorLight); - SerialUSB.print(F("reverseLight: ")); - SerialUSB.println(reverseLight); - SerialUSB.print(F("brakeLight: ")); - SerialUSB.println(brakeLight); - SerialUSB.println(F("-------End-------")); - serialIsSent[1] = true; - } else if((millis()%1000 < 500) && (serialIsSent[1] == true)) { - serialIsSent[1] = false; - } -} - -void debugFunctionOut(bool parkLight, - bool lowBeamLight, - bool highBeamLight, - bool highBeamLightFlash, - bool fogLight, - bool beaconLight, - bool auxLight, - bool hazardLight, - bool leftIndicatorLight, - bool rightIndicatorLight, - bool reverseLight, - bool brakeLight) { - if((millis()%1000 >= 500) && (serialIsSent[2] == false)) { - SerialUSB.println(F("-- Light Out --")); - SerialUSB.print(F("parkLight: ")); - SerialUSB.println(parkLight); - SerialUSB.print(F("lowBeamLight: ")); - SerialUSB.println(lowBeamLight); - SerialUSB.print(F("highBeamLight: ")); - SerialUSB.println(highBeamLight); - SerialUSB.print(F("highBeamLightFlash: ")); - SerialUSB.println(highBeamLightFlash); - SerialUSB.print(F("fogLight: ")); - SerialUSB.println(fogLight); - SerialUSB.print(F("beaconLight: ")); - SerialUSB.println(beaconLight); - SerialUSB.print(F("auxLight: ")); - SerialUSB.println(auxLight); - SerialUSB.print(F("hazardLight: ")); - SerialUSB.println(hazardLight); - SerialUSB.print(F("leftIndicatorLight: ")); - SerialUSB.println(leftIndicatorLight); - SerialUSB.print(F("rightIndicatorLight: ")); - SerialUSB.println(rightIndicatorLight); - SerialUSB.print(F("reverseLight: ")); - SerialUSB.println(reverseLight); - SerialUSB.print(F("brakeLight: ")); - SerialUSB.println(brakeLight); - SerialUSB.println(F("-------End-------")); - serialIsSent[2] = true; - } else if((millis()%1000 < 500) && (serialIsSent[2] == true)) { - serialIsSent[2] = false; - } -} \ No newline at end of file diff --git a/debugging.h b/debugging.h deleted file mode 100644 index 43da26d..0000000 --- a/debugging.h +++ /dev/null @@ -1,65 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#ifndef _DEBBUGING_H_ -#define _DEBBUGING_H_ - -#include "Arduino.h" - - -#ifndef SerialUSB //if not allready defined -#define SerialUSB SERIAL_PORT_MONITOR //then define monitor port -#endif - -void debuggingInit(uint8_t debugLevel, uint8_t outputLED); -void controllerStatus(bool errorFlag, uint8_t outputLED); //function to signal errorstate -void debugChannelEvaluation( - uint16_t channelNumber, - uint16_t channel1, - uint16_t channel2, - uint16_t channel3, - uint16_t channel4, - uint16_t channel5, - uint16_t channel6, - uint16_t channel7, - uint16_t channel8 -); - -void debugFunctionState(bool parkLight, - bool lowBeamLight, - bool highBeamLight, - bool highBeamLightFlash, - bool fogLight, - bool beaconLight, - bool auxLight, - bool hazardLight, - bool leftIndicatorLight, - bool rightIndicatorLight, - bool reverseLight, - bool brakeLight); -void debugFunctionOut(bool parkLight, - bool lowBeamLight, - bool highBeamLight, - bool highBeamLightFlash, - bool fogLight, - bool beaconLight, - bool auxLight, - bool hazardLight, - bool leftIndicatorLight, - bool rightIndicatorLight, - bool reverseLight, - bool brakeLight); - -#endif \ No newline at end of file diff --git a/lightFunctions.cpp b/lightFunctions.cpp deleted file mode 100644 index add64c9..0000000 --- a/lightFunctions.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#include "lightFunctions.h" -#include "tools.h" -#include // https://github.com/bhagman/SoftPWM - - -Blink flasher[3]; - -bool directlyToOutput(bool lightState) { - return lightState; -} - -bool highBeamFlash(bool lightState, bool lightFlashState, uint16_t flashFrequency) { - if(lightState) return true; - if(lightFlashState) return flasher[0].blink(flashFrequency); - // else then reset - flasher[0].resetBlink(); - return false; -} - -void setTurnIndicators(bool leftFlasherState, bool rightFlasherState, bool hazardState, bool* outLeftLight, bool* outRightLight, uint16_t flashFrequency) { - if(hazardState) { - bool blinkerState = flasher[1].blink(flashFrequency); - *outLeftLight = blinkerState; - *outRightLight = blinkerState; - } else if(leftFlasherState) { - *outLeftLight = flasher[1].blink(flashFrequency); - *outRightLight = false; - } else if(rightFlasherState) { - *outLeftLight = false; - *outRightLight = flasher[1].blink(flashFrequency); - } else if((*outLeftLight || *outRightLight) && !flasher[1].blink(flashFrequency)) { - *outLeftLight = false; - *outRightLight = false; - } else if (!*outLeftLight && !*outRightLight) { - flasher[1].resetBlink(); - } - -} - -void initLightOutput() { - SoftPWMBegin(); //Init Soft PWM Lib -} - -void setupLightOutput(uint8_t pin, uint16_t fadeOnTime, uint16_t fadeOffTime) { - SoftPWMSet(pin, SOFT_PWM_LOW); //Create and set pin to 0 - SoftPWMSetFadeTime(pin, fadeOnTime, fadeOffTime); //Set Fade on/off time for output -} - -void setBooleanLight(uint8_t pin, bool state, uint8_t highValue = SOFT_PWM_HIGH) { - if(state) SoftPWMSet(pin, highValue); - if(!state) SoftPWMSet(pin, SOFT_PWM_LOW); -} - -void setCombinedHeadlightAll(uint8_t pin, - uint8_t parkingState, - uint8_t lowBeamState, - uint8_t highBeamState, - uint8_t parkingOutValue, - uint8_t lowBeamOutValue, - uint8_t highBeamOutValue) { - if(highBeamState) { - SoftPWMSet(pin, highBeamOutValue); - } else if(lowBeamState) { - SoftPWMSet(pin, lowBeamOutValue); - } else if(parkingState) { - SoftPWMSet(pin, parkingOutValue); - } else { - SoftPWMSet(pin, SOFT_PWM_LOW); - } -} -void setCombinedHeadlightHighOnly(uint8_t pin, - uint8_t lowBeamState, - uint8_t highBeamState, - uint8_t lowBeamOutValue, - uint8_t highBeamOutValue) { - if(highBeamState) { - SoftPWMSet(pin, highBeamOutValue); - } else if(lowBeamState) { - SoftPWMSet(pin, lowBeamOutValue); - } else { - SoftPWMSet(pin, SOFT_PWM_LOW); - } -} - -void setCombinedHeadlightParkOnly(uint8_t pin, - uint8_t parkingState, - uint8_t lowBeamState, - uint8_t parkingOutValue, - uint8_t lowBeamOutValue) { - if(lowBeamState) { - SoftPWMSet(pin, lowBeamOutValue); - } else if(parkingState) { - SoftPWMSet(pin, parkingOutValue); - } else { - SoftPWMSet(pin, SOFT_PWM_LOW); - } -} - -void setBrakingWithPark(uint8_t pin, uint8_t parkState, uint8_t brakeState, uint8_t parkDimming, uint8_t highValue) { - if(brakeState) { - SoftPWMSet(pin, highValue); - } else if(parkState) { - SoftPWMSet(pin, parkDimming); - } else { - SoftPWMSet(pin, SOFT_PWM_LOW); - } -} diff --git a/lightFunctions.h b/lightFunctions.h deleted file mode 100644 index fed9ce1..0000000 --- a/lightFunctions.h +++ /dev/null @@ -1,52 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#ifndef _LIGHT_FUNCTIONS_H_ -#define _LIGHT_FUNCTIONS_H_ - -#include "Arduino.h" - -#define SOFT_PWM_HIGH 255 -#define SOFT_PWM_LOW 0 - -bool directlyToOutput(bool lightState); -bool highBeamFlash(bool lightState, bool lightFlashState, uint16_t flashFrequency); -void setTurnIndicators(bool leftFlasherState, bool rightFlasherState, bool hazardState, bool* outLeftLight, bool* outRightLight, uint16_t flashFrequency); -void initLightOutput(); -void setupLightOutput(uint8_t pin, uint16_t fadeOnTime, uint16_t fadeOffTime); -void setBooleanLight(uint8_t pin, bool state, uint8_t highValue = SOFT_PWM_HIGH); - -void setCombinedHeadlightAll(uint8_t pin, - uint8_t parkingState, - uint8_t lowBeamState, - uint8_t highBeamState, - uint8_t parkingOutValue, - uint8_t lowBeamOutValue, - uint8_t highBeamOutValue); -void setCombinedHeadlightHighOnly(uint8_t pin, - uint8_t lowBeamState, - uint8_t highBeamState, - uint8_t lowBeamOutValue, - uint8_t highBeamOutValue); - -void setCombinedHeadlightParkOnly(uint8_t pin, - uint8_t parkingState, - uint8_t lowBeamState, - uint8_t parkingOutValue, - uint8_t lowBeamOutValue); - -void setBrakingWithPark(uint8_t pin, uint8_t parkState, uint8_t brakeState, uint8_t parkDimming, uint8_t highValue = SOFT_PWM_HIGH); - -#endif \ No newline at end of file diff --git a/ppmToSwitches.cpp b/ppmToSwitches.cpp deleted file mode 100644 index 7dd5602..0000000 --- a/ppmToSwitches.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ -#include "ppmToSwitches.h" - -/*************************************************** - * PPM Signal have a range from 1000ms to 2000ms - * So 3 stages should be 1000/1500/2000 => UP/MID/DOWN - * invertDirection is optional to turn the direction of UP and DOWN - ***************************************************/ -uint8_t ppmToSwitchStages(uint16_t signal, bool invertDirection) { - uint8_t dynDirectionUp; - uint8_t dynDirectionDown; - if(invertDirection) { //if switch output is inverted - dynDirectionUp = DIRECTION_DOWN; //then assign opposite direction - dynDirectionDown = DIRECTION_UP; - } else { - dynDirectionUp = DIRECTION_UP; //else assign same direction - dynDirectionDown = DIRECTION_DOWN; - } - if((signal >= 750) && (signal <= 1250)) { //if signal is at 1000ms ±250ms - return dynDirectionUp; //return 1 if signal is at lower end - } else if(signal <= 1750) { //else if signal is at 1500ms ±250ms - return DIRECTION_MID; //return 2 if signal is at middle - } else if(signal <= 2250) { //else if signal is at 2000ms ±250ms - return dynDirectionDown; //return 3 if signal is at upper end - } else { //else signal is <750 or >2250 - return 0; //return 0 cause signal is out of bound | error - } -} - -/*************************************************** - * PPM Signal have a range from 1000ms to 2000ms - * 3 Stages on 2 signals - * If first signal have ~1000 the switch is in up position - * If both signals have ~1700 the switch is in middle position - * If second signal have ~1000 the switch is in down position - ***************************************************/ -uint8_t ppm2ToSwitch3Stages(uint16_t signal1, uint16_t signal2) { - if((signal1 >= 750) && (signal1 <= 1250)) { //if signal is at 1000ms ±250ms - return DIRECTION_UP; //return 1 if signal is at lower end - } else if((signal2 >= 750) && (signal2 <= 1250)) { //else if signal is at 2000ms ±250ms - return DIRECTION_DOWN; //return 3 if signal is at upper end - } else if((signal1 >= 1500) && (signal2 >= 1500) && (signal1 <= 2000) && (signal2 <= 2000)) { //else if signal is at 1500ms ±250ms - return DIRECTION_MID; //return 2 if signal is at middle - } else { //else signal is <750 or >2250 - return 0; //return 0 cause signal is out of bound | error - } -} - -/*************************************************** - * PPM Signal have a range from 1000ms to 2000ms - * input Range of signal is defined by inMin (~1000) inMax (~2000) - * returns a value from outMin to outMax - * if calculation fails a zero returns - ***************************************************/ -uint32_t ppmServoToRange(uint32_t signal, uint32_t inMin, uint32_t inMax, uint32_t outMin, uint32_t outMax) { - uint32_t dynResult; - if(inMin != inMax) { //if Min and Max are equal abbort calculation cause of divide by zero - dynResult = (signal - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; - if(signal > inMax) dynResult = outMax; - if(signal < inMin) dynResult = outMin; - return dynResult; - } else { - return 0; - } -} diff --git a/readPPMdata.cpp b/readPPMdata.cpp deleted file mode 100644 index dccfb04..0000000 --- a/readPPMdata.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#include "readPPMdata.h" // Include own header file -#include "ppmToSwitches.h" // Library to evaluate switch position from ppm value -#include "tools.h" // Get filter class from there - -#define INTERRUPT_BUFFERSIZE 8 // Max Buffersize of ISR; Max is 255 -#define PPM_START_MIN 850 // Minimun for start impuls -#define PPM_START_MAX 980 // Maximum for start impuls -#define PPM_HIGH_MIN 700 // Minimun for High impuls -#define PPM_HIGH_MAX 2200 // Maximun for High impuls -#define MULTI1 0 // Array number of Multiswitch -#define MULTI2 1 // Array number of Multiswitch -#define MAX_TIME_SIGNAL 5000 // 2000ms Maximum time for signal to change - -// Variables -struct multiswitch { - volatile uint16_t buffer[INTERRUPT_BUFFERSIZE]; // Stores time difference of every channel - volatile uint8_t position = 8; // Current index for interrupt | Start with 8 to make sure to start with the first start impulse - volatile uint32_t lastMicros = 0; // Time since last interrupt - volatile uint32_t lastValidMillis = 0; // Time since last valid save in interrupt -}; - -multiswitch interrupt[2]; // Structure for ISR - -struct { - volatile uint16_t servoValue = 0; // Saves time difference of this channel - volatile uint32_t lastMicros = 0; // Time since last interrupt - volatile uint32_t lastValidMillis = 0; // Time since last valid save in interrupt -} interrupt3; - -// Classes -Filter filter[2]; // Filter Function for Potis - -/*************************************************** - * PPM Signal have a range from 1000ms to 2000ms - * So 3 stages should be 1000/1500/2000 => UP/MID/DOWN - * invertDirection is optional to turn the direction of UP and DOWN - ***************************************************/ - -void initInterrupts(uint8_t pinPPMChannel1, uint8_t pinPPMChannel2, uint8_t pinServoChannel) { - attachInterrupt(digitalPinToInterrupt(pinPPMChannel1), ppmMultiInterrupt1, CHANGE); //Setup Interrupt - attachInterrupt(digitalPinToInterrupt(pinPPMChannel2), ppmMultiInterrupt2, CHANGE); //Setup Interrupt - attachInterrupt(digitalPinToInterrupt(pinServoChannel), ppmServoInterrupt, CHANGE); //Setup Interrupt - filter[0].init(1520); - filter[1].init(1520); -} - -void ppmMultiInterrupt1(){ - volatile uint32_t nMicros = micros(); //Store current time - volatile uint32_t nDifference = (nMicros - interrupt[MULTI1].lastMicros); //Calc time since last Change - if((nDifference > (uint16_t)PPM_HIGH_MIN) && (nDifference < (uint16_t)PPM_HIGH_MAX)) { //Filter HIGH Impulse | HIGH if time is between 700 and 2200 - if((nDifference > (uint16_t)PPM_START_MIN) && (nDifference < (uint16_t)PPM_START_MAX)) { //if time is ~915 then this is the start impulse - interrupt[MULTI1].position = 0; //then set index to 0 - } else if(interrupt[MULTI1].position < INTERRUPT_BUFFERSIZE) { //if index is out of bound, then wait for next start impulse - interrupt[MULTI1].buffer[interrupt[MULTI1].position] = nDifference; //save current time difference to value - interrupt[MULTI1].position++; //increment index by one - interrupt[MULTI1].lastValidMillis = millis(); //save time of the last valid signal - } - } - interrupt[MULTI1].lastMicros = nMicros; //save time for next interrupt -} - -void ppmMultiInterrupt2(){ - volatile uint32_t nMicros = micros(); //Store current time - volatile uint32_t nDifference = (nMicros - interrupt[MULTI2].lastMicros); //Calc time since last Change - if((nDifference > (uint16_t)PPM_HIGH_MIN) && (nDifference < (uint16_t)PPM_HIGH_MAX)) { //Filter HIGH Impulse | HIGH if time is between 700 and 2200 else return - if((nDifference > (uint16_t)PPM_START_MIN) && (nDifference < (uint16_t)PPM_START_MAX)) { //if time is ~915 then this is the start impulse - interrupt[MULTI2].position = 0; //then set index to 0 - return; // And wait for the next impulse - } else if(interrupt[MULTI2].position < INTERRUPT_BUFFERSIZE) { //if index is out of bound, then wait for next start impulse - interrupt[MULTI2].buffer[interrupt[MULTI2].position] = nDifference; //save current time difference to value - interrupt[MULTI2].position++; //increment index by one - interrupt[MULTI2].lastValidMillis = millis(); //save time of the last valid signal - } - } - interrupt[MULTI2].lastMicros = nMicros; //save time for next interrupt -} - - -void ppmServoInterrupt(){ - volatile uint32_t nMicros = micros(); // Store current time - volatile uint32_t nDifference = (nMicros - interrupt3.lastMicros); //Calc time since last Change - if((nDifference > PPM_HIGH_MIN) && (nDifference < PPM_HIGH_MAX)) { // Filter HIGH Impulse | HIGH if time is between 700 and 2200 - interrupt3.servoValue = nDifference; // Store current time difference to value - interrupt3.lastValidMillis = millis(); //save time of the last valid signal - } - interrupt3.lastMicros = nMicros; //save time for next interrupt - return; -} - -bool checkChannelStatus(uint8_t multiSwitch) { - if((millis()-interrupt[multiSwitch].lastValidMillis) >= (uint16_t)MAX_TIME_SIGNAL) return false; - return true; -} - -uint8_t getChannel1Switch(uint8_t channel, uint8_t fallbackValue) { - if(!checkChannelStatus(MULTI1)) return fallbackValue; // return fallback if channel does not respond - switch (channel) { - case 0: - return ppmToSwitchStages(interrupt[MULTI1].buffer[0]); - break; - case 1: - return ppmToSwitchStages(interrupt[MULTI1].buffer[1]); - break; - case 2: - return ppmToSwitchStages(interrupt[MULTI1].buffer[2]); - break; - case 3: - return ppmToSwitchStages(interrupt[MULTI1].buffer[3]); - break; - case 4: - return ppmToSwitchStages(interrupt[MULTI1].buffer[4]); - break; - case 5: - return ppmToSwitchStages(interrupt[MULTI1].buffer[5]); - break; - case 6: - return ppmToSwitchStages(interrupt[MULTI1].buffer[6]); - break; - case 7: - return ppmToSwitchStages(interrupt[MULTI1].buffer[7]); - break; - } - // If something wrong return fallback - return fallbackValue; -} - -uint16_t getChannel2Poti(uint8_t channel, uint16_t fallbackValue) { - uint16_t actualValue = fallbackValue; - switch (channel) { - case 0: - if (checkChannelStatus(MULTI2)) actualValue = interrupt[MULTI2].buffer[0]; - - return filter[0].filterValue(actualValue, 10, 50); - break; - case 1: - if (checkChannelStatus(MULTI2)) actualValue = interrupt[MULTI2].buffer[1]; - - return filter[1].filterValue(actualValue, 10, 50); - break; - } - // If something wrong return fallback - return fallbackValue; -} - -uint8_t getChannel2Switch(uint8_t channel, uint8_t fallbackValue) { - if(!checkChannelStatus(MULTI2)) return fallbackValue; // return fallback if channel does not respond - switch (channel) { - case 0: - return ppmToSwitchStages(interrupt[MULTI2].buffer[0], PPM_INVERT); - break; - case 1: - return ppmToSwitchStages(interrupt[MULTI2].buffer[1], PPM_INVERT); - break; - case 2: - return ppmToSwitchStages(interrupt[MULTI2].buffer[2], PPM_INVERT); - break; - case 3: - return ppmToSwitchStages(interrupt[MULTI2].buffer[3], PPM_INVERT); - break; - case 4: - return ppmToSwitchStages(interrupt[MULTI2].buffer[4], PPM_INVERT); - break; - case 5: - return ppmToSwitchStages(interrupt[MULTI2].buffer[5], PPM_INVERT); - break; - case 6: - return ppmToSwitchStages(interrupt[MULTI2].buffer[6], PPM_INVERT); - break; - case 7: - return ppmToSwitchStages(interrupt[MULTI2].buffer[7], PPM_INVERT); - break; - } - // If something wrong return fallback - return fallbackValue; -} - -bool mapSwitchToFunction(uint8_t channel, uint8_t downValue, uint8_t midValue, uint8_t upValue) { - switch (channel) { - case DIRECTION_DOWN: - return downValue; - break; - case DIRECTION_MID: - return midValue; - break; - case DIRECTION_UP: - return upValue; - break; - default: - return false; - break; - } -} - -uint16_t getChannel3Signal() { - if((millis()-interrupt3.lastValidMillis) >= (uint16_t)MAX_TIME_SIGNAL) return 0; - return ppmToSwitchStages(interrupt3.servoValue); -} diff --git a/readPPMdata.h b/readPPMdata.h deleted file mode 100644 index 73b9141..0000000 --- a/readPPMdata.h +++ /dev/null @@ -1,36 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#ifndef _READ_PPM_DATA_H_ -#define _READ_PPM_DATA_H_ - -#include "Arduino.h" - -#define DIRECTION_UP 1 -#define DIRECTION_MID 2 -#define DIRECTION_DOWN 3 - -void initInterrupts(uint8_t pinPPMChannel1, uint8_t pinPPMChannel2, uint8_t pinServoChannel); // Function to attach all interrupt service routines (ISR) -void ppmMultiInterrupt1(); // ISR for interrupt of first PPM signal -void ppmMultiInterrupt2(); // ISR for interrupt of second PPM signal -void ppmServoInterrupt(); // ISR for interrupt of servo PPM signal -bool checkChannelStatus(uint8_t multiSwitch); // Function to check the Status of the Channel (If signal is lost) -uint8_t getChannel1Switch(uint8_t channel, uint8_t fallbackValue); // Function to get the value of the Switches from Channel 1 -uint16_t getChannel2Poti(uint8_t channel, uint16_t fallbackValue); // Function to get the value of the Potis from Channel 1 -uint8_t getChannel2Switch(uint8_t channel, uint8_t fallbackValue); // Function to get the value of the Switches from Channel 2 -bool mapSwitchToFunction(uint8_t channel, uint8_t downValue, uint8_t midValue, uint8_t upValue); // Function to map a Key to a boolean function value -uint16_t getChannel3Signal(); - -#endif \ No newline at end of file diff --git a/serialCommMaster.cpp b/src/communication/serialCommMaster.cpp similarity index 56% rename from serialCommMaster.cpp rename to src/communication/serialCommMaster.cpp index c89d6ac..f829cb4 100644 --- a/serialCommMaster.cpp +++ b/src/communication/serialCommMaster.cpp @@ -20,17 +20,15 @@ void SerialCommMaster::begin( HardwareSerial* serialPort, uint32_t baud, - uint8_t byteFormat, + SerialConfig byteFormat, long timeout, long polling, - uint8_t txEnablePin, - uint8_t protocolVersion + uint8_t txEnablePin ) { _serialPort = serialPort; _timeout = timeout; _polling = polling; _txEnablePin = txEnablePin; - _protocolVersion = protocolVersion; (*_serialPort).begin(baud, byteFormat); pinMode(_txEnablePin, OUTPUT); @@ -38,18 +36,13 @@ void SerialCommMaster::begin( _errorCount = 0; _state = WAITING_FOR_TURNAROUND; - _frameDelay = 10; _delayStart = 0; - _lightDataFromSerial = 0; - _additionalDataFromSerial = 0; - _servoMicrosFromSerial[0] = 0; - _servoMicrosFromSerial[1] = 0; } -uint16_t SerialCommMaster::update() { +uint16_t SerialCommMaster::update(uint32_t inputState, uint32_t effectState, uint16_t* servoStateArray) { switch (_state) { case IDLE: - idle(); + constructPacket(inputState, effectState, servoStateArray); break; case WAITING_FOR_TURNAROUND: waitingForTurnaround(); @@ -58,45 +51,30 @@ uint16_t SerialCommMaster::update() { return _errorCount; } -void SerialCommMaster::idle() { - switch (_protocolVersion) { - case FUNC_LIGHT_DATA: - constructPacket(FUNC_LIGHT_DATA, _lightDataFromSerial); - break; - case FUNC_LIGHT_SERVO: - constructPacket(FUNC_LIGHT_SERVO, _lightDataFromSerial, _additionalDataFromSerial, _servoMicrosFromSerial[0], - _servoMicrosFromSerial[1]); - break; - } -} - void SerialCommMaster::constructPacket( - uint8_t function, - uint16_t lightData, - uint16_t additionalData, - uint16_t servoData1, - uint16_t servoData2 + uint32_t inputState, uint32_t effectState, uint16_t* servoStateArray ) { - _frame[0] = function; - uint8_t frameSize; - switch (function) { - case FUNC_LIGHT_DATA: - frameSize = 4; // 1 byte function + 1 byte lightData + 2 bytes CRC - _frame[1] = lightData & 0x00FF; - break; - case FUNC_LIGHT_SERVO: - frameSize = 9; // 1 byte function + 1 byte lightData + 1 byte additionalData + 2 bytes servo1 + 2 bytes - // servo2 + 2 bytes CRC - _frame[1] = lightData & 0x00FF; - _frame[2] = additionalData & 0x00FF; - _frame[3] = servoData1 >> 8; - _frame[4] = servoData1 & 0xFF; - _frame[5] = servoData2 >> 8; - _frame[6] = servoData2 & 0xFF; - break; - default: - return; // Invalid function, do not send anything - } + // 1 byte function + 4 byte lightState + 2 byte effectState + + // 9 byte ServoData (12bit) + 2 bytes CRC => 18 bytes total + uint8_t frameSize = 18; + _frame[0] = FUNC_LIGHT_DATA; + + _frame[1] = inputState & 0x00FF; + _frame[2] = (inputState >> 8) & 0x00FF; + _frame[3] = (inputState >> 16) & 0x00FF; + _frame[4] = (inputState >> 24) & 0x00FF; + + _frame[5] = effectState & 0x00FF; + _frame[6] = (effectState >> 8) & 0x00FF; + _frame[7] = (servoStateArray[SRV_REMOTE_1] >> 4) & 0xFF; // First 8 bits of servo 1 + _frame[8] = ((servoStateArray[SRV_REMOTE_1] & 0x0F) << 4) | ((servoStateArray[SRV_REMOTE_2] >> 8) & 0x0F); // Last 4 bits of servo 1 and first 4 bits of servo 2 + _frame[9] = servoStateArray[SRV_REMOTE_2] & 0xFF; // Last 8 bits of servo 2 + _frame[10] = servoStateArray[SRV_REMOTE_3] >> 4; // First 8 bits of servo 3 + _frame[11] = ((servoStateArray[SRV_REMOTE_3] & 0x0F) << 4) | ((servoStateArray[SRV_REMOTE_4] >> 8) & 0x0F); // Last 4 bits of servo 3 and first 4 bits of servo 4 + _frame[12] = servoStateArray[SRV_REMOTE_4] & 0xFF; // Last 8 bits of servo 4 + _frame[13] = servoStateArray[SRV_REMOTE_5] >> 4; // First 8 bits of servo 5 + _frame[14] = ((servoStateArray[SRV_REMOTE_5] & 0x0F) << 4) | ((servoStateArray[SRV_REMOTE_6] >> 8) & 0x0F); // Last 4 bits of servo 5 and first 4 bits of servo 6 + _frame[15] = servoStateArray[SRV_REMOTE_6] & 0xFF; // Last 8 bits of servo 6 uint16_t crc16 = calculateCRC(frameSize - 2); _frame[frameSize - 2] = crc16 >> 8; // Split crc into two bytes @@ -135,33 +113,7 @@ void SerialCommMaster::sendPacket(uint8_t bufferSize) { } (*_serialPort).flush(); - delayMicroseconds(_frameDelay); - digitalWrite(_txEnablePin, LOW); _delayStart = millis(); // start the timeout delay -} - -void SerialCommMaster::setLightData(LightIdentifier lightOption, bool lightState) { - if (lightState) { - uint8_t bitmask = 0x1 << lightOption; - _lightDataFromSerial |= bitmask; - } else { - uint8_t bitmask = ~(0x1 << lightOption); - _lightDataFromSerial &= bitmask; - } -} - -void SerialCommMaster::setAdditionalData(AdditionalDataIdentifier additionalOption, bool additionalState) { - if (additionalState) { - uint8_t bitmask = 0x1 << additionalOption; - _additionalDataFromSerial |= bitmask; - } else { - uint8_t bitmask = ~(0x1 << additionalOption); - _additionalDataFromSerial &= bitmask; - } -} - -void SerialCommMaster::setServoData(ServoDataIdentifier servoOption, uint16_t servoValue) { - _servoMicrosFromSerial[servoOption] = servoValue; } \ No newline at end of file diff --git a/serialCommMaster.h b/src/communication/serialCommMaster.h similarity index 59% rename from serialCommMaster.h rename to src/communication/serialCommMaster.h index cf3cffc..accc0a0 100644 --- a/serialCommMaster.h +++ b/src/communication/serialCommMaster.h @@ -16,45 +16,19 @@ * If not, see . ************************************/ -#ifndef _SERIAL_COMM_MASTER_H_ -#define _SERIAL_COMM_MASTER_H_ - -#include "Arduino.h" -#include "HardwareSerial.h" - -enum LightIdentifier { - PARK_LIGHT = 0, - BRAKE_LIGHT = 1, - REVERSE_LIGHT = 2, - RIGHT_BLINK = 3, - LEFT_BLINK = 4, - AUX_LIGHT = 5, - BEACON_LIGHT = 6, - DIMM_LIGHTS = 7 -}; - -enum AdditionalDataIdentifier { - LEFT_TURN_INDICATOR = 0, - RIGHT_TURN_INDICATOR = 1, - HAZARD_STATE = 2, - SERVO_POSITION_DOWN = 3, - SERVO_POSITION_UP = 4 -}; - -enum ServoDataIdentifier { - SERVO_CHANNEL_1 = 0, - SERVO_CHANNEL_2 = 1 -}; +#pragma once +#include +#include +#include "../config/main-config.h" +#include "../config/light-mode.h" +#include "../config/global-effects-config.h" class SerialCommMaster { public: - void begin(HardwareSerial* serialPort, uint32_t baud, uint8_t byteFormat, long timeout, long polling, - uint8_t txEnablePin, uint8_t protocolVersion = 1); + void begin(HardwareSerial* serialPort, uint32_t baud, SerialConfig byteFormat, long timeout, long polling, uint8_t txEnablePin); - uint16_t update(); - void setLightData(LightIdentifier lightOption, bool lightState); - void setAdditionalData(AdditionalDataIdentifier additionalOption, bool additionalState); - void setServoData(ServoDataIdentifier servoOption, uint16_t servoValue); + uint16_t update(uint32_t inputState, uint32_t effectState, uint16_t* servoStateArray); + private: // Constants @@ -74,7 +48,6 @@ class SerialCommMaster { HardwareSerial* _serialPort; uint8_t _txEnablePin; uint16_t _errorCount; - uint8_t _protocolVersion; uint32_t _timeout; uint32_t _polling; uint16_t _frameDelay; @@ -83,21 +56,10 @@ class SerialCommMaster { uint8_t _frame[BUFFER_SIZE]; State _state; - uint8_t _lightDataFromSerial; - uint8_t _additionalDataFromSerial; - uint16_t _servoMicrosFromSerial[2]; - // Private methods void idle(); void waitingForTurnaround(); - void constructPacket( - uint8_t function, - uint16_t lightData, - uint16_t additionalData = 0, - uint16_t servoData1 = 0, - uint16_t servoData2 = 0); + void constructPacket(uint32_t inputState, uint32_t effectState, uint16_t* servoStateArray); uint16_t calculateCRC(uint8_t bufferSize); void sendPacket(uint8_t bufferSize); }; - -#endif \ No newline at end of file diff --git a/src/config-interface/json-interface.cpp b/src/config-interface/json-interface.cpp new file mode 100644 index 0000000..da97625 --- /dev/null +++ b/src/config-interface/json-interface.cpp @@ -0,0 +1,127 @@ +#include "json-interface.h" + +void JsonInterface::update(LocalOutputController& outputController, MemoryManager& memoryManager) { + if (Serial.available() == 0) return; + + // 1024 bytes is enough for a chunky update block + JsonDocument doc; + DeserializationError error = deserializeJson(doc, Serial); + + if (error) { + Serial.print("JSON Parse Failed: "); + Serial.println(error.c_str()); + while(Serial.available()) Serial.read(); // Clear buffer + return; + } + + // --- Main Config --- + if(doc["main"].is()) { + JsonObject genConfig = doc["main"]; + activeMainConfig.failsafeMask = genConfig["fsMask"].as() | activeMainConfig.failsafeMask; + activeMainConfig.failsafeTimeoutMs = genConfig["fsTimeout"].as() | activeMainConfig.failsafeTimeoutMs; + activeMainConfig.ppmMode = static_cast(genConfig["ppmMode"].as() | static_cast(activeMainConfig.ppmMode)); + Serial.println("{ \"status\": \"Main config updated\" }"); + } + + // --- 1. OUTPUTS --- + if (doc["outputs"].is()) { + JsonArray outputs = doc["outputs"].as(); + size_t index = 0; + for (JsonObject outConfig : outputs) { + if(index < NUM_LOCAL_OUTPUTS && index < outputs.size()) { + activeMainConfig.localOutputs[index].pin = outConfig["pin"].as(); + activeMainConfig.localOutputs[index].mode = static_cast(outConfig["mode"].as()); + activeMainConfig.localOutputs[index].triggerMask = outConfig["mask"].as(); + activeMainConfig.localOutputs[index].param1 = outConfig["p1"].as(); + activeMainConfig.localOutputs[index].param2 = outConfig["p2"].as(); + activeMainConfig.localOutputs[index].param3 = outConfig["p3"].as(); + activeMainConfig.localOutputs[index].fadeTime = outConfig["fade"].as(); + } + index++; + } + Serial.println("{ \"status\": \"Outputs updated\" }"); + outputController.begin(); // Re-init hardware pins + } + + // --- 2. SBUS INPUTS --- + if (doc["sbus"].is()) { + JsonArray sbusInputs = doc["sbus"].as(); + for (JsonObject inConfig : sbusInputs) { + uint8_t ch = inConfig["ch"]; + uint8_t channel = ch - 1; + if (channel < 16) { + activeMainConfig.sbusInputs[channel].type = static_cast(inConfig["type"].as()); + activeMainConfig.sbusInputs[channel].targetMaskLow = inConfig["maskL"].as(); + activeMainConfig.sbusInputs[channel].targetMaskMid = inConfig["maskM"].as(); + activeMainConfig.sbusInputs[channel].targetMaskHigh = inConfig["maskH"].as(); + activeMainConfig.sbusInputs[channel].thresholdLow = inConfig["thL"].as(); + activeMainConfig.sbusInputs[channel].thresholdHigh = inConfig["thH"].as(); + activeMainConfig.sbusInputs[channel].targetServoIndex = inConfig["srv"].as(); + } + } + Serial.println("{ \"status\": \"SBUS Inputs updated\" }"); + } + + // --- 2.1. PPM INPUTS --- + if (doc["ppmIn"].is()) { + JsonArray ppmInputs = doc["ppmIn"].as(); + for (JsonObject inConfig : ppmInputs) { + uint8_t ch = inConfig["ch"]; + uint8_t channel = ch - 1; + if (channel < 8) { + activeMainConfig.ppmInputs[channel].type = static_cast(inConfig["type"].as()); + activeMainConfig.ppmInputs[channel].targetMaskLow = inConfig["maskL"].as(); + activeMainConfig.ppmInputs[channel].targetMaskMid = inConfig["maskM"].as(); + activeMainConfig.ppmInputs[channel].targetMaskHigh = inConfig["maskH"].as(); + activeMainConfig.ppmInputs[channel].thresholdLow = inConfig["thL"].as(); + activeMainConfig.ppmInputs[channel].thresholdHigh = inConfig["thH"].as(); + activeMainConfig.ppmInputs[channel].targetServoIndex = inConfig["srv"].as(); + } + } + Serial.println("{ \"status\": \"PPM Inputs updated\" }"); + } + + // --- 3. GLOBAL EFFECTS --- + if (doc["effects"].is()) { + JsonObject effConfig = doc["effects"]; + + // Using | default_value to only update keys that are present in the JSON + activeEffectsConfig.turnSignalFreq = effConfig["turnFreq"] | activeEffectsConfig.turnSignalFreq; + activeEffectsConfig.starterDimFactor = effConfig["stDim"] | activeEffectsConfig.starterDimFactor; + activeEffectsConfig.strobeFlashDuration = effConfig["strFlash"] | activeEffectsConfig.strobeFlashDuration; + activeEffectsConfig.strobeShortPause = effConfig["strShort"] | activeEffectsConfig.strobeShortPause; + activeEffectsConfig.strobeLongPause = effConfig["strLong"] | activeEffectsConfig.strobeLongPause; + activeEffectsConfig.beacon1Speed = effConfig["bcn1Spd"] | activeEffectsConfig.beacon1Speed; + activeEffectsConfig.beacon2Speed = effConfig["bcn2Spd"] | activeEffectsConfig.beacon2Speed; + activeEffectsConfig.beacon1MaxLeds = effConfig["bcn1Max"] | activeEffectsConfig.beacon1MaxLeds; + activeEffectsConfig.beacon2MaxLeds = effConfig["bcn2Max"] | activeEffectsConfig.beacon2MaxLeds; + activeEffectsConfig.flashToPassFreq = effConfig["ftpFreq"] | activeEffectsConfig.flashToPassFreq; + activeEffectsConfig.corneringLightOffDelay = effConfig["cornerOff"] | activeEffectsConfig.corneringLightOffDelay; + activeEffectsConfig.xenonFlashDuration = effConfig["xenFlash"] | activeEffectsConfig.xenonFlashDuration; + activeEffectsConfig.xenonFadeDuration = effConfig["xenFade"] | activeEffectsConfig.xenonFadeDuration; + activeEffectsConfig.xenonLowBeamStartPwm = effConfig["xenLowPwm"] | activeEffectsConfig.xenonLowBeamStartPwm; + + Serial.println("{ \"status\": \"effects updated\" }"); + } + + // Optional: Trigger a save to NVS here if configChanged is true + if (doc["save"].is()) { + JsonString save = doc["save"]; + if(save == "true") { + memoryManager.saveConfig(); + Serial.println("{ \"status\": \"config saved\" }"); + } + } + + if (doc["factory"].is()) { + JsonString factory = doc["factory"]; + if(factory == "true") { + memoryManager.factoryReset(); + Serial.println("{ \"status\": \"factory reset\" }"); + } + } + + if (doc["status"].is()) { + Serial.println("{ \"status\": \"ok\", \"version\": \"2.0.0\", \"model\": \"MainESP32\" }"); + } +} \ No newline at end of file diff --git a/src/config-interface/json-interface.h b/src/config-interface/json-interface.h new file mode 100644 index 0000000..bce6017 --- /dev/null +++ b/src/config-interface/json-interface.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#include "../config/main-config.h" +#include "../config/global-effects-config.h" +#include "../output/local-outputs.h" +#include "../memory/memory-manager.h" + +class JsonInterface { +public: + // Pass the output controller so we can re-initialize pins if the config changes + void update(LocalOutputController& outputController, MemoryManager& memoryManager); +}; \ No newline at end of file diff --git a/src/config/global-effects-config.h b/src/config/global-effects-config.h new file mode 100644 index 0000000..eeaaa4e --- /dev/null +++ b/src/config/global-effects-config.h @@ -0,0 +1,46 @@ +#pragma once +#include + +enum GlobalOutputEffects: uint16_t { + BIT_GLOBAL_TURN_L = (1U << 0), + BIT_GLOBAL_TURN_R = (1U << 1), + BIT_GLOBAL_STROBE = (1U << 2), + BIT_GLOBAL_FLASH_TO_PASS = (1U << 3), + BIT_GLOBAL_CORNERING_L = (1U << 4), + BIT_GLOBAL_CORNERING_R = (1U << 5), +}; + +struct GlobalEffectsConfig { + // --- Timings in milliseconds --- + + // Turn signals & Hazards (usually identical speed, but distinct logic) + uint16_t turnSignalFreq; // e.g., 500ms ON / 500ms OFF + + // Strobes (Double Flash Sequence / Doppelblitz) + // Pattern: ON -> shortPause -> ON -> longPause -> (repeat) + uint16_t strobeFlashDuration; // e.g., 40 (Time the LED stays ON) + uint16_t strobeShortPause; // e.g., 60 (Pause between the two rapid flashes) + uint16_t strobeLongPause; // e.g., 400 (Pause before the next double flash starts) + + // Flash to pass (Lichthupe) + uint16_t flashToPassFreq; // e.g., 100ms ON / 100ms OFF + + // Rotating beacon (Rundumleuchte) + uint16_t beacon1Speed; // Time between LED transition steps + uint16_t beacon2Speed; // Time between LED transition steps + uint8_t beacon1MaxLeds; // Amount of virtual LEDs in the beacon circle + uint8_t beacon2MaxLeds; // Amount of virtual LEDs in the beacon circle + + // --- Specific settings --- + uint8_t starterDimFactor; // Dimming percentage (e.g., 50%) during motor start + + // --- Cornering Lights --- + uint16_t corneringLightOffDelay; // Time to turn cornering light off + + // -- Bi-Xenon Headlight Effect --- + uint16_t xenonFlashDuration; // Duration of the initial flash when turning on the headlights + uint16_t xenonFadeDuration; // Duration of the fade from low beam to high beam after the initial flash + uint32_t xenonLowBeamStartPwm; // Starting PWM value for low beam when fading up to high beam +}; + +extern GlobalEffectsConfig activeEffectsConfig; \ No newline at end of file diff --git a/src/config/input-config.h b/src/config/input-config.h new file mode 100644 index 0000000..63ea08b --- /dev/null +++ b/src/config/input-config.h @@ -0,0 +1,50 @@ +#pragma once +#include + +// --- INPUT DEFINITIONS --- + +enum class InputType : uint8_t { + NONE = 0, // Channel is disabled / ignored + SWITCH_3POS = 1, // Evaluates Low, Mid, and High positions +}; + +enum InputServoMapping : uint8_t { + NONE = 0, + // Local Master Board Servos + SRV_MASTER_1 = 1, + SRV_MASTER_2 = 2, + SRV_MASTER_3 = 3, + SRV_MASTER_4 = 4, + SRV_MASTER_5 = 5, + SRV_MASTER_6 = 6, + + // Remote RS485 Bus Servos + SRV_REMOTE_1 = 7, + SRV_REMOTE_2 = 8, + SRV_REMOTE_3 = 9, + SRV_REMOTE_4 = 10, + SRV_REMOTE_5 = 11, + SRV_REMOTE_6 = 12 +}; + +struct InputConfig { + InputType type; + + // --- Used for SWITCH_3POS --- + // The targets (bits) to trigger based on the switch position + uint32_t targetMaskLow; // Triggered if value < thresholdLow + uint32_t targetMaskMid; // Triggered if value >= thresholdLow AND <= thresholdHigh + uint32_t targetMaskHigh; // Triggered if value > thresholdHigh + + // The limits defining the 3 switch zones + // Units depend on input type: + // For PWM: Raw value ranges 700-2300 (values ideally at 1300-1700) + // For PPM: Raw value ranges 1000-2000 (values ideally at 1300-1700) + // For SBus: Raw SBus units ranges from 0-2047 (values ideally at 900-1100) + uint16_t thresholdLow; + uint16_t thresholdHigh; + + // --- Used for PROPORTIONAL --- + // Maps this input channel to an index in the global servo array (0 to 12); while 0 means off + InputServoMapping targetServoIndex; +}; \ No newline at end of file diff --git a/src/config/light-mode.h b/src/config/light-mode.h new file mode 100644 index 0000000..1181a59 --- /dev/null +++ b/src/config/light-mode.h @@ -0,0 +1,64 @@ +/************************************ + * Copyright (C) 2020-2026 Marina Egner + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + ************************************/ + +#pragma once +#include + +// The 16-bit master mask (The vocabulary of the bus) +enum LightBits : uint32_t { + BIT_STATIC_OFF = 0, + // --- Standard Driving Lights --- + BIT_PARKING_LIGHT = (1UL << 0), // Value: 1 + BIT_LOW_BEAM = (1UL << 1), // Value: 2 + BIT_HIGH_BEAM = (1UL << 2), // Value: 4 + + // --- Signal Lights --- + BIT_TURN_SIGNAL_L = (1UL << 3), // Value: 8 + BIT_TURN_SIGNAL_R = (1UL << 4), // Value: 16 + BIT_HAZARD_LIGHT = (1UL << 5), // Value: 32 + BIT_BRAKE_LIGHT = (1UL << 6), // Value: 64 + BIT_REVERSE_LIGHT = (1UL << 7), // Value: 128 + + // --- Special Functions --- + BIT_BEACON_LIGHT = (1UL << 8), // Value: 256 (Rotating beacon) + BIT_FOG_L = (1UL << 9), // Value: 512 (e.g., Fog light left) + BIT_FOG_R = (1UL << 10), // Value: 1024 (e.g., Fog light right) + BIT_FOG = (1UL << 11), // Value: 2048 (e.g., Fog light both sides) + BIT_STARTER_DIM = (1UL << 12), // Value: 4096 (Starter dims lights) + + // --- System & Control Bits --- + BIT_SHOWMODE = (1UL << 13), // Value: 8192 (Triggers show/sequence mode on slaves) + BIT_STATIC_ON = (1UL << 14), // Value: 16384 (Virtual bit for always-on/marker lights) + BIT_STROBE_LIGHT = (1UL << 15), // Value: 32768 (Warning strobes) + BIT_FLASH_TO_PASS = (1UL << 16), // Value: 65536 (Lichthupe / Optical horn sequence) + BIT_STEERING_LEFT = (1UL << 17), // Value: 131072 (Steering Left Indicator) + BIT_STEERING_RIGHT = (1UL << 18), // Value: 262144 (Steering Right Indicator) + BIT_BI_XENON = (1UL << 19), // Value: 524288 (For special bi-xenon headlights with motorized shutter) + BIT_AUX1 = (1UL << 20), // Value: 1048576 (e.g., Working light) + BIT_AUX2 = (1UL << 21), // Value: 2097152 (Spare bit for future use) + BIT_DRL = (1UL << 22), // Value: 4194304 (Daytime Running Light - Virtual bit for daytime mode) + // Available: Bits 23 to 31 (Over 2 billion possible combinations!) +}; + +enum CombinedLightStates: uint32_t { + COMB_PARK_AND_FULL_BEAM = (BIT_PARKING_LIGHT | BIT_LOW_BEAM | BIT_HIGH_BEAM), + COMB_PARK_AND_LOW_BEAM = (BIT_PARKING_LIGHT | BIT_LOW_BEAM), + COMB_LOW_AND_HIGH_BEAM = (BIT_LOW_BEAM | BIT_HIGH_BEAM), + COMB_PARK_AND_BRAKE = (BIT_PARKING_LIGHT | BIT_BRAKE_LIGHT), + COMB_US_TAIL_L = (BIT_PARKING_LIGHT | BIT_BRAKE_LIGHT | BIT_HAZARD_LIGHT | BIT_TURN_SIGNAL_L), + COMB_US_TAIL_R = (BIT_PARKING_LIGHT | BIT_BRAKE_LIGHT | BIT_HAZARD_LIGHT | BIT_TURN_SIGNAL_R), + COMB_PARK_TURN_L = (BIT_PARKING_LIGHT | BIT_HAZARD_LIGHT | BIT_TURN_SIGNAL_L), + COMB_PARK_TURN_R = (BIT_PARKING_LIGHT | BIT_HAZARD_LIGHT | BIT_TURN_SIGNAL_R), +}; diff --git a/src/config/local-output-config.h b/src/config/local-output-config.h new file mode 100644 index 0000000..23cf13b --- /dev/null +++ b/src/config/local-output-config.h @@ -0,0 +1,47 @@ +#pragma once +#include + +// --- LOCAL OUTPUT DEFINITIONS --- + +enum class OutputMode : uint8_t { + NONE = 0, // Pin is disabled + DIGITAL = 1, // Simple HIGH/LOW (e.g., Relays, basic LEDs) + PWM = 2, // Dimmable output (ESP32 ledc hardware PWM) + SERVO = 3 // 50Hz Servo or ESC control +}; + +struct LocalOutputConfig { + uint8_t pin; // The physical GPIO pin on the ESP32 + OutputMode mode; // How should this pin be driven? + + uint32_t triggerMask; // Which LightBits trigger this output? (e.g., BIT_LOW_BEAM) + + // Generic parameters depending on the selected OutputMode: + // + // Mode DIGITAL: + // - param1, param2, param3 are ignored. + // + // Mode PWM: + // Multi-purpose parameters. + // Example for combined lights (Parking | Low | High): + // param1 = Brightness for Parking (lowest priority) + // param2 = Brightness for Low Beam + // param3 = Brightness for High Beam (highest priority) + // + // Example for Beacon: + // param1 = LED Index (e.g., 2nd LED in the circle) + // param2 = Beacon Group ID (0 or 1, for independent beacons) + // param3 = Brightness + // + // Mode SERVO: + // - param1 = Servo position (0-11) + // - param2 = Minimum pulse width in microseconds (e.g., 1000) + // - param3 = Maximum pulse width in microseconds (e.g., 2000) + uint16_t param1; + uint16_t param2; + uint16_t param3; + // Global fade transition time for this specific pin. + // 0 = Instant snap (good for strobes/blinkers) + // 1-255 = Milliseconds to fade to the new target value (soft on/off) + uint16_t fadeTime; +}; \ No newline at end of file diff --git a/src/config/main-config.h b/src/config/main-config.h new file mode 100644 index 0000000..f77eb7d --- /dev/null +++ b/src/config/main-config.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include "input-config.h" +#include "local-output-config.h" + +#define NUM_SBUS_CHANNELS 16 +#define NUM_PPM_CHANNELS 8 +#define NUM_LOCAL_OUTPUTS 14 + +// Defines how the physical PPM pin is evaluated +enum class PpmInputMode : uint8_t { + MULTIPLEXED_8CH = 0, // Expects a standard 8-channel PPM stream + SINGLE_PWM = 1 // Expects a single traditional RC PWM pulse +}; + +struct MainConfig { + // --- Network / Identification --- + uint8_t nodeId; // Address of this module (e.g., 0x00 for Master, 0x10 for Trailer 1) + + // --- Routing: Inputs --- + InputConfig sbusInputs[NUM_SBUS_CHANNELS]; + + PpmInputMode ppmMode; // Toggle between 8-channel PPM or single-channel PWM + InputConfig ppmInputs[NUM_PPM_CHANNELS]; // If SINGLE_PWM, only ppmInputs[0] is evaluated + + // --- Routing: Outputs (ESP32 Pins) --- + LocalOutputConfig localOutputs[NUM_LOCAL_OUTPUTS]; + + // --- System Safety: Failsafe --- + // Timeout in milliseconds without a valid PPM/PWM pulse before failsafe triggers + uint16_t failsafeTimeoutMs; + + // Which bits to force ON during a failsafe (e.g., BIT_HAZARD_LIGHT | BIT_BRAKE_LIGHT) + uint32_t failsafeMask; +}; + +// Global instance +extern MainConfig activeMainConfig; \ No newline at end of file diff --git a/src/input/esc-parser.cpp b/src/input/esc-parser.cpp new file mode 100644 index 0000000..716d537 --- /dev/null +++ b/src/input/esc-parser.cpp @@ -0,0 +1,34 @@ +#include "esc-parser.h" + +EscParser::EscParser(uint8_t brakePin, uint8_t reversePin) + : brakingActive(false), reverseActive(false), + _brakePin(brakePin), _reversePin(reversePin), activeEscMask(0) {} + +void EscParser::begin() { + pinMode(_brakePin, INPUT_PULLUP); + pinMode(_reversePin, INPUT_PULLUP); +} + +void EscParser::update() { + // Da das Signal reversed reinkommt (Active Low): + // LOW (0V) -> Signal ist an (true) + // HIGH (3.3V) -> Signal ist aus (false) + brakingActive = (digitalRead(_brakePin) == LOW); + reverseActive = (digitalRead(_reversePin) == LOW); + + uint32_t newMask = 0; + + if (brakingActive) { + newMask |= BIT_BRAKE_LIGHT; + } + + if (reverseActive) { + newMask |= BIT_REVERSE_LIGHT; + } + + activeEscMask = newMask; +} + +uint32_t EscParser::getActiveMask() { + return activeEscMask; +} \ No newline at end of file diff --git a/src/input/esc-parser.h b/src/input/esc-parser.h new file mode 100644 index 0000000..4d02424 --- /dev/null +++ b/src/input/esc-parser.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include "../config/main-config.h" +#include "../config/light-mode.h" + +class EscParser { +private: + bool brakingActive; + bool reverseActive; + uint8_t _brakePin; + uint8_t _reversePin; + + uint32_t activeEscMask; + +public: + EscParser(uint8_t brakePin, uint8_t reversePin); + + void begin(); + + // Liest die Hardware-Pins und berechnet die Maske basierend auf der Config + void update(); + + uint32_t getActiveMask(); + + bool isBraking() { return brakingActive; } + bool isReverse() { return reverseActive; } +}; \ No newline at end of file diff --git a/src/input/ppm-parser.cpp b/src/input/ppm-parser.cpp new file mode 100644 index 0000000..e849c23 --- /dev/null +++ b/src/input/ppm-parser.cpp @@ -0,0 +1,152 @@ +#include "ppm-parser.h" + +const uint16_t SYNC_PULSE_MIN = 850; +const uint16_t SYNC_PULSE_MAX = 980; + +// Standardmäßig starten wir mit einem sicheren Modus +PpmParser::PpmParser(uint8_t pin) + : inputPin(pin), mode(PpmInputMode::MULTIPLEXED_8CH), lastRiseTime(0), currentChannelCount(0), activePpmMask(0) { + // Initialize smoothenValues array with 1500 for all channels + for (uint8_t i = 0; i < NUM_PPM_CHANNELS; i++) { + smoothenValues[i] = 1500; + // Initialize historyChannels array with 1500 for all history entries + for (uint8_t j = 0; j < PPM_HISTORY_SIZE; j++) { + historyChannels[i][j] = 1500; + } + } + historyIndex = 0; +} + +void PpmParser::begin() { + pinMode(inputPin, INPUT_PULLDOWN); + attachInterruptArg(inputPin, PpmParser::handleInterrupt, this, CHANGE); +} + +void PpmParser::setMode(PpmInputMode newMode) { + mode = newMode; + // Optional: Reset der Kanaldaten bei Moduswechsel + currentChannelCount = 0; +} + +void IRAM_ATTR PpmParser::handleInterrupt(void* arg) { + PpmParser* instance = (PpmParser*)arg; + uint32_t now = micros(); + uint32_t nowMillis = millis(); + + uint32_t duration = now - instance->lastRiseTime; + + // Die ISR nutzt immer den aktuell gesetzten Modus der Instanz + if (instance->mode == PpmInputMode::SINGLE_PWM) { + if (duration >= 800 && duration <= 2200) { + instance->rawValues[0] = (uint16_t)duration; + instance->lastValidPulseTime = nowMillis; + instance->lastValidPulseStart = nowMillis; + } + } + else { + if (duration >= SYNC_PULSE_MIN && duration <= SYNC_PULSE_MAX) { + instance->currentChannelCount = 0; + instance->lastValidPulseTime = nowMillis; + instance->lastValidPulseStart = nowMillis; + } + else if (duration >= 700 && duration <= 2200) { + if (instance->currentChannelCount < NUM_PPM_CHANNELS) { + uint8_t mirroredIndex = (NUM_PPM_CHANNELS - 1) - instance->currentChannelCount; + instance->rawValues[mirroredIndex] = (uint16_t)duration; + instance->currentChannelCount++; + instance->lastValidPulseTime = nowMillis; + } + } + } + instance->lastRiseTime = now; +} + +void PpmParser::update(bool isLinkActive, uint16_t* servoStateArray) { + if (!isLinkActive) { + activePpmMask = 0; + return; + } + + uint32_t newMask = 0; + uint8_t channelsToProcess = (mode == PpmInputMode::SINGLE_PWM) ? 1 : NUM_PPM_CHANNELS; + updateSmoothValue(); + + for (uint8_t i = 0; i < channelsToProcess; i++) { + InputConfig& cfg = activeMainConfig.ppmInputs[i]; + uint16_t val = getNormalizedSmoothValue(i); + + if (cfg.targetServoIndex != InputServoMapping::NONE && cfg.targetServoIndex <= InputServoMapping::SRV_REMOTE_6) { + servoStateArray[cfg.targetServoIndex - 1] = val; + } + + if (cfg.type == InputType::NONE) { + continue; + } + + if (cfg.type == InputType::SWITCH_3POS) { + if (val < cfg.thresholdLow) { + newMask |= cfg.targetMaskLow; + } else if (val >= cfg.thresholdLow && val <= cfg.thresholdHigh) { + newMask |= cfg.targetMaskMid; + } else { + newMask |= cfg.targetMaskHigh; + } + } + } + activePpmMask = newMask; +} + +bool PpmParser::isPulsePresent() { + return (millis() - lastValidPulseTime < activeMainConfig.failsafeTimeoutMs); +} + +uint32_t PpmParser::getActiveMask() { return activePpmMask; } +uint16_t PpmParser::getRawValue(uint8_t ch) { return rawValues[ch]; } + +uint16_t PpmParser::getNormalizedValue(uint8_t index) { + uint16_t raw = rawValues[index]; // Die us Werte (z.B. 930, 1500, 1977) + + // Mapping von Mikrosekunden auf unsere interne 0-2000 Skala + // 1000us -> 0 | 1500us -> 1000 | 2000us -> 2000 + int32_t normalized = map(raw, 1000, 2000, 0, 2000); + return (uint16_t)constrain(normalized, 0, 2000); +} + +uint16_t PpmParser::getNormalizedSmoothValue(uint8_t index) { + uint16_t smoothValue = smoothenValues[index]; // Die us Werte (z.B. 930, 1500, 1977) + + // Mapping von Mikrosekunden auf unsere interne 0-2000 Skala + // 1000us -> 0 | 1500us -> 1000 | 2000us -> 2000 + int32_t normalized = map(smoothValue, 1000, 2000, 0, 2000); + return (uint16_t)constrain(normalized, 0, 2000); +} + +void PpmParser::updateSmoothValue() { + if(lastValidPulseStart != lastValidPacketTime) { + lastValidPacketTime = lastValidPulseStart; + // New packet received, reset history + for(uint8_t i = 0; i < NUM_PPM_CHANNELS; i++) { + historyChannels[i][historyIndex] = rawValues[i]; + + smoothenValues[i] = calculateMedian(i); + } + + historyIndex = (historyIndex + 1) % PPM_HISTORY_SIZE; + } +} + +uint16_t PpmParser::calculateMedian(uint8_t index) { + uint16_t sorted[PPM_HISTORY_SIZE]; + memcpy(sorted, historyChannels[index], sizeof(uint16_t) * PPM_HISTORY_SIZE); + // Simple insertion sort + for (uint8_t i = 1; i < PPM_HISTORY_SIZE; i++) { + uint16_t key = sorted[i]; + int8_t j = i - 1; + while (j >= 0 && sorted[j] > key) { + sorted[j + 1] = sorted[j]; + j--; + } + sorted[j + 1] = key; + } + return sorted[PPM_HISTORY_SIZE / 2]; // Return median +} \ No newline at end of file diff --git a/src/input/ppm-parser.h b/src/input/ppm-parser.h new file mode 100644 index 0000000..3f85dda --- /dev/null +++ b/src/input/ppm-parser.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include "../config/main-config.h" + +#define PPM_HISTORY_SIZE 3 +class PpmParser { +private: + uint8_t inputPin; + PpmInputMode mode; // Wird jetzt dynamisch gesetzt + + volatile uint32_t lastRiseTime; + volatile uint16_t rawValues[NUM_PPM_CHANNELS]; + volatile uint8_t currentChannelCount; + volatile uint32_t lastValidPulseTime; + volatile uint32_t lastValidPulseStart; + uint16_t smoothenValues[NUM_PPM_CHANNELS]; + uint16_t historyChannels[NUM_PPM_CHANNELS][PPM_HISTORY_SIZE]; + uint8_t historyIndex; + uint32_t lastValidPacketTime; + + uint32_t activePpmMask; + + static void IRAM_ATTR handleInterrupt(void* arg); + +public: + // Konstruktor nur noch mit dem Pin + PpmParser(uint8_t pin); + + void begin(); + + // Neue Funktion zum Setzen oder Ändern des Modus zur Laufzeit + void setMode(PpmInputMode newMode); + + void update(bool isLinkActive, uint16_t* servoStateArray); + + uint32_t getActiveMask(); + bool isPulsePresent(); + uint16_t getRawValue(uint8_t channel); + uint16_t getNormalizedValue(uint8_t index); + uint16_t getNormalizedSmoothValue(uint8_t index); + void updateSmoothValue(); + uint16_t calculateMedian(uint8_t index); +}; \ No newline at end of file diff --git a/src/input/sbus-parser.cpp b/src/input/sbus-parser.cpp new file mode 100644 index 0000000..a578a38 --- /dev/null +++ b/src/input/sbus-parser.cpp @@ -0,0 +1,134 @@ +#include "sbus-parser.h" + +SbusParser::SbusParser(HardwareSerial* serialPort, int8_t rxPin, int8_t txPin, bool invert) + : receiver(serialPort, rxPin, txPin, invert), activeSbusMask(0) { + // Initialize smoothenValues array with 1024 for all channels + for (uint8_t i = 0; i < NUM_SBUS_CHANNELS; i++) { + smoothenValues[i] = 1024; + // Initialize historyChannels array with 1024 for all history entries + for (uint8_t j = 0; j < SBUS_HISTORY_SIZE; j++) { + historyChannels[i][j] = 1024; + } + } + historyIndex = 0; +} + +void SbusParser::begin() { + receiver.begin(); +} + +void SbusParser::update(bool isLinkActive, uint16_t* servoStateArray) { + receiver.processIncoming(); + + // The main loop has determined the link is dead. Force failsafe mask. + if (!isLinkActive) { + activeSbusMask = activeMainConfig.failsafeMask; + return; + } + + // Link is active, process normally + receiver.getChannel(&channelData); + updateSmoothValue(); + uint32_t newMask = 0; + + for (uint8_t i = 0; i < NUM_SBUS_CHANNELS; i++) { + InputConfig& cfg = activeMainConfig.sbusInputs[i]; + uint16_t val = getSmoothValue(i); + + if (cfg.targetServoIndex != InputServoMapping::NONE && cfg.targetServoIndex <= InputServoMapping::SRV_REMOTE_6) { + servoStateArray[cfg.targetServoIndex - 1] = val; + } + + if (cfg.type == InputType::NONE) { + continue; + } + + if (cfg.type == InputType::SWITCH_3POS) { + if (val < cfg.thresholdLow) { + newMask |= cfg.targetMaskLow; + } + else if (val >= cfg.thresholdLow && val <= cfg.thresholdHigh) { + newMask |= cfg.targetMaskMid; + } + else { + newMask |= cfg.targetMaskHigh; + } + } + } + + activeSbusMask = newMask; +} + +uint32_t SbusParser::getActiveMask() { + return activeSbusMask; +} + +bool SbusParser::isSerialConnected() { + return receiver.getSerialConnectionStatus(); +} + +bool SbusParser::isFailsafeActive() { + return receiver.getFailsafe(); +} + +bool SbusParser::isFrameLost() { + return receiver.getFramelost(); +} + +uint16_t SbusParser::getChannelValue(uint8_t index) { + switch (index) { + case 0: return channelData.channel1; + case 1: return channelData.channel2; + case 2: return channelData.channel3; + case 3: return channelData.channel4; + case 4: return channelData.channel5; + case 5: return channelData.channel6; + case 6: return channelData.channel7; + case 7: return channelData.channel8; + case 8: return channelData.channel9; + case 9: return channelData.channel10; + case 10: return channelData.channel11; + case 11: return channelData.channel12; + case 12: return channelData.channel13; + case 13: return channelData.channel14; + case 14: return channelData.channel15; + case 15: return channelData.channel16; + default: return 1024; + } +} + +uint16_t SbusParser::getSmoothValue(uint8_t index) { + + return smoothenValues[index]; +} + +void SbusParser::updateSmoothValue() { + uint32_t currentLastPacketTime = receiver.getLastValidPacketTime(); + if(currentLastPacketTime != lastValidPacketTime) { + lastValidPacketTime = currentLastPacketTime; + // New packet received, reset history + for(uint8_t i = 0; i < NUM_SBUS_CHANNELS; i++) { + historyChannels[i][historyIndex] = getChannelValue(i); + + smoothenValues[i] = calculateMedian(i); + } + + historyIndex = (historyIndex + 1) % SBUS_HISTORY_SIZE; + } +} + +uint16_t SbusParser::calculateMedian(uint8_t index) { + uint16_t sorted[SBUS_HISTORY_SIZE]; + memcpy(sorted, historyChannels[index], sizeof(uint16_t) * SBUS_HISTORY_SIZE); + // Simple insertion sort + for (uint8_t i = 1; i < SBUS_HISTORY_SIZE; i++) { + uint16_t key = sorted[i]; + int8_t j = i - 1; + while (j >= 0 && sorted[j] > key) { + sorted[j + 1] = sorted[j]; + j--; + } + sorted[j + 1] = key; + } + return sorted[SBUS_HISTORY_SIZE / 2]; // Return median +} \ No newline at end of file diff --git a/src/input/sbus-parser.h b/src/input/sbus-parser.h new file mode 100644 index 0000000..00078b6 --- /dev/null +++ b/src/input/sbus-parser.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include "./serialIO/SerialIO.h" +#include "../config/main-config.h" + +#define SBUS_HISTORY_SIZE 5 +class SbusParser { +private: + sbus receiver; + rc_channels_t channelData; + uint32_t activeSbusMask; + uint16_t smoothenValues[NUM_SBUS_CHANNELS]; + uint16_t historyChannels[NUM_SBUS_CHANNELS][SBUS_HISTORY_SIZE]; + uint8_t historyIndex; + uint32_t lastValidPacketTime; + + uint16_t getChannelValue(uint8_t index); + +public: + SbusParser(HardwareSerial* serialPort, int8_t rxPin, int8_t txPin, bool invert); + + void begin(); + + // The main loop passes the combined connection status into the update function + void update(bool isLinkActive, uint16_t* servoStateArray); + + uint32_t getActiveMask(); + + // Expose hardware status for the main loop to evaluate + bool isSerialConnected(); + bool isFailsafeActive(); + bool isFrameLost(); + uint16_t getSmoothValue(uint8_t index); + void updateSmoothValue(); + uint16_t calculateMedian(uint8_t index); +}; \ No newline at end of file diff --git a/src/input/serialIO/SerialIO.cpp b/src/input/serialIO/SerialIO.cpp new file mode 100644 index 0000000..7722494 --- /dev/null +++ b/src/input/serialIO/SerialIO.cpp @@ -0,0 +1,31 @@ +#include "SerialIO.h" + +SerialIO::SerialIO(Stream *rxPort, int rxPin, int txPin, bool inverted) + : _rxPort(rxPort), _rxPin(rxPin), _txPin(txPin), _inverted(inverted) { + // Constructor implementation +} + +SerialIO::~SerialIO() { +// End serial communication +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_AVR) + HardwareSerial *serialPort = (HardwareSerial *)_rxPort; + serialPort->end(); +#elif defined(ARDUINO_ARCH_RP2040) + SerialUART *serialPort = (SerialUART *)_rxPort; + serialPort->end(); +#else +#warning "Unsupported hardware platform." +#endif +} + +void SerialIO::leftShift(uint8_t arr[], size_t size) { + memmove(arr, arr + 1, (size - 1)); + arr[size - 1] = 0xFF; +} + +void SerialIO::rightShift(uint8_t arr[], size_t size) { + memmove(arr + 1, arr, size - 1); + arr[0] = 0xFF; +} + +void SerialIO::getChannel(ibus_channels_t *channelData) {} \ No newline at end of file diff --git a/src/input/serialIO/SerialIO.h b/src/input/serialIO/SerialIO.h new file mode 100644 index 0000000..0652710 --- /dev/null +++ b/src/input/serialIO/SerialIO.h @@ -0,0 +1,117 @@ +/** + * @file SerialIO.h + * @brief Header file for SerialIO class, providing serial input/output (IO) functionality. + * + * @author WittyWizard + */ + +#pragma once +#ifndef SerialIO_H +#define SerialIO_H + +#include +#include "ibus/ibus_protocol.h" + +#define PACKED __attribute__((packed)) + +/** + * @brief Structure representing 16 RC channels using 11-bit values. + */ +typedef struct rc_channels_s +{ + unsigned channel1 : 11; + unsigned channel2 : 11; + unsigned channel3 : 11; + unsigned channel4 : 11; + unsigned channel5 : 11; + unsigned channel6 : 11; + unsigned channel7 : 11; + unsigned channel8 : 11; + unsigned channel9 : 11; + unsigned channel10 : 11; + unsigned channel11 : 11; + unsigned channel12 : 11; + unsigned channel13 : 11; + unsigned channel14 : 11; + unsigned channel15 : 11; + unsigned channel16 : 11; +} PACKED rc_channels_t; + +/** + * @brief Class providing methods for initializing and decoding RC protocols. + */ +class SerialIO +{ +public: + /** + * @brief Constructor to initialize the SerialIO class. + * + * @param rxPort Pointer to the hardware serial port to use. + * @param rxPin RX pin number. + * @param txPin TX pin number. + * @param inverted Set to true if the serial signal is inverted. + */ + SerialIO(Stream *rxPort, int rxPin, int txPin, bool inverted); + + /** + * @brief Virtual destructor. + */ + virtual ~SerialIO(); + + /** + * @brief Initialize pins and set up the serial port. + */ + virtual void begin() = 0; + + /** + * @brief Process incoming serial data and decode it. + */ + virtual void processIncoming() = 0; + + /** + * @brief Retrieve the current channel data. + * + * @param[out] channelData Pointer to an rc_channels_t structure to store the retrieved channel data. + * + * @note The structure contains 16 channels, each represented as an 11-bit unsigned integer with a maximum value of 2047. + */ + virtual void getChannel(rc_channels_t *channelData) = 0; + + /** + * @brief Retrieve the current channel data using ibus_channels_t. + * + * @param[out] channelData Pointer to an ibus_channels_t structure to store the channel data. + */ + virtual void getChannel(ibus_channels_t *channelData); + +protected: + Stream *_rxPort; ///< Pointer to the hardware serial port used for communication. + bool _inverted; ///< Indicates whether the serial signal is inverted. + int _rxPin; ///< RX pin number. + int _txPin; ///< TX pin number. + uint32_t _lastValidPacketTime = 0; ///< Timestamp of the last received packet. + bool _connectionTimeout = false; ///< Indicates whether the connection has timed out. + + /** + * @brief Perform a left shift operation on the given byte array. + * + * @param arr Pointer to the byte array. + * @param size Size of the array. + */ + void leftShift(uint8_t arr[], size_t size); + + /** + * @brief Perform a right shift operation on the given byte array. + * + * @param arr Pointer to the byte array. + * @param size Size of the array. + */ + void rightShift(uint8_t arr[], size_t size); +}; + +#include "crsf/crsf.h" +#include "fport/fport.h" +#include "ibus/ibus.h" +#include "sbus/sbus.h" + +#endif // SerialIO_H diff --git a/src/input/serialIO/crsf/crsf.cpp b/src/input/serialIO/crsf/crsf.cpp new file mode 100644 index 0000000..9a5ed2f --- /dev/null +++ b/src/input/serialIO/crsf/crsf.cpp @@ -0,0 +1,67 @@ +#include "crsf.h" + +crsf::crsf(Stream *rxPort, int rxPin, int txPin, bool inverted) + : SerialIO(rxPort, rxPin, txPin, inverted){}; + +void crsf::begin() { + +// Initialize the serial port +#if defined(ARDUINO_ARCH_ESP32) + HardwareSerial *serialPort = (HardwareSerial *)_rxPort; + serialPort->begin(CRSF_BAUDRATE, SERIAL_8N1, _rxPin, _txPin, _inverted); +#elif defined(ARDUINO_ARCH_RP2040) + SerialUART *serialPort = (SerialUART *)_rxPort; + serialPort->setPinout(_txPin, _rxPin); + serialPort->setInvertRX(_inverted); + serialPort->setInvertTX(_inverted); + serialPort->begin(CRSF_BAUDRATE, SERIAL_8N1); +#else +#warning "Unsupported hardware platform." +#endif +} + +void crsf::processIncoming() { + uint8_t size = CRSF_MAX_PACKET_SIZE; + while (_rxPort->available()) { + _rxData[CRSF_MAX_PACKET_SIZE - 1] = _rxPort->read(); + if (crc8(&_rxData[CRSF_MAX_PACKET_SIZE - size], + _rxData[CRSF_MAX_PACKET_SIZE - size - 1]) == 0) { + if ((_rxData[CRSF_MAX_PACKET_SIZE - size - 2] == + CRSF_ADDRESS_FLIGHT_CONTROLLER) || + (_rxData[CRSF_MAX_PACKET_SIZE - size - 2] == + CRSF_ADDRESS_CRSF_TRANSMITTER)) { + if (_rxData[CRSF_MAX_PACKET_SIZE - size] == + CRSF_FRAMETYPE_RC_CHANNELS_PACKED) { + memcpy(&_channelData, &_rxData[CRSF_MAX_PACKET_SIZE - size + 1], + sizeof(_channelData)); + } + } + } + if (_rxData[CRSF_MAX_PACKET_SIZE - 2] == + CRSF_ADDRESS_CRSF_TRANSMITTER || + _rxData[CRSF_MAX_PACKET_SIZE - 2] == + CRSF_ADDRESS_FLIGHT_CONTROLLER) { + size = _rxData[CRSF_MAX_PACKET_SIZE - 1]; + } + leftShift(_rxData, sizeof(_rxData)); + } +} + +void crsf::getChannel(rc_channels_t *channelData) { + memcpy(channelData, &_channelData, sizeof(rc_channels_t)); +} + +uint8_t crsf::crc8(uint8_t *data, uint8_t len) { + uint8_t crc = 0; + for (uint8_t i = 0; i < len; i++) { + crc ^= data[i]; + for (uint8_t j = 0; j < 8; j++) { + if (crc & 0x80) { + crc = (crc << 1) ^ CRC8_POLY_D5; + } else { + crc <<= 1; + } + } + } + return crc; +} \ No newline at end of file diff --git a/src/input/serialIO/crsf/crsf.h b/src/input/serialIO/crsf/crsf.h new file mode 100644 index 0000000..6485468 --- /dev/null +++ b/src/input/serialIO/crsf/crsf.h @@ -0,0 +1,56 @@ +/*! + * @file crsf.h + * @brief Header file for the CRSF protocol implementation. + * @author Witty-Wizard + */ + +#pragma once +#ifndef CRSF_H +#define CRSF_H +#include "crsf_protocol.h" +#include "../SerialIO.h" // Include header file for the serial IO class +#include "crsf_protocol.h" + +#define CRC8_POLY_D5 0xD5 + +/** + * @brief A class for handling CRSF protocol communication. + */ +class crsf : public SerialIO { +private: + crsf_channels_t _channelData; + uint8_t _rxData[CRSF_MAX_PACKET_SIZE]={0}; + bool _headerDetected; // Flag indicating whether a header has been detected in + // the incoming data. + uint8_t _rxIndex; // Index for the receive_buffer. + uint8_t _buffer; + +public: + /** + * @brief Constructor for the CRSF class. + * @param rxPort Reference to the hardware serial port for RX communication. + * @param rxPin The RX pin number. + * @param txPin The TX pin number. + * @param inverted Whether the serial signal is inverted (true) or not + * (false). + */ + explicit crsf(Stream *rxPort, int rxPin = -1, int txPin = -1, + bool inverted = false); + + /** + * @brief Initializes the CRSF communication. + */ + void begin() override; + void processIncoming() override; + + /** + * @brief Retrieves the decoded RC channels from the received CRSF data. + * @param channelData Pointer to a crsf_channels_t struct where the decoded + * channel data will be stored. + */ + void getChannel(rc_channels_t *channelData) override; + + uint8_t crc8(uint8_t *data, uint8_t len); +}; + +#endif \ No newline at end of file diff --git a/src/input/serialIO/crsf/crsf_protocol.h b/src/input/serialIO/crsf/crsf_protocol.h new file mode 100644 index 0000000..06dcca3 --- /dev/null +++ b/src/input/serialIO/crsf/crsf_protocol.h @@ -0,0 +1,194 @@ +#pragma once + +#ifndef CRSF_PROTOCOL_H +#define CRSF_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define PACKED __attribute__((packed)) + +#define CRSF_BAUDRATE 420000 ///< CRSF default baud rate +#define CRSF_NUM_CHANNELS 16 ///< CRSF number of channels +#define CRSF_CHANNEL_VALUE_MIN \ + 172 ///< 987us - actual CRSF min is 0 with E.Limits on +#define CRSF_CHANNEL_VALUE_1000 191 +#define CRSF_CHANNEL_VALUE_MID 992 +#define CRSF_CHANNEL_VALUE_2000 1792 +#define CRSF_CHANNEL_VALUE_MAX \ + 1811 ///< 2012us - actual CRSF max is 1984 with E.Limits on +#define CRSF_CHANNEL_VALUE_SPAN \ + (CRSF_CHANNEL_VALUE_MAX - CRSF_CHANNEL_VALUE_MIN) +#define CRSF_MAX_PACKET_SIZE \ + 64 ///< max declared len is 62+DEST+LEN on top of that = 64 +#define CRSF_MAX_PAYLOAD_LEN \ + (CRSF_MAX_PACKET_SIZE - \ + 4) ///< Max size of payload in [dest] [len] [type] [payload] [crc8] + +/** Length of different CRSF frame */ +enum { + CRSF_FRAME_LENGTH_ADDRESS = 1, // length of ADDRESS field + CRSF_FRAME_LENGTH_FRAMELENGTH = 1, // length of FRAMELENGTH field + CRSF_FRAME_LENGTH_TYPE = 1, // length of TYPE field + CRSF_FRAME_LENGTH_CRC = 1, // length of CRC field + CRSF_FRAME_LENGTH_TYPE_CRC = 2, // length of TYPE and CRC fields combined + CRSF_FRAME_LENGTH_EXT_TYPE_CRC = + 4, // length of Extended Dest/Origin, TYPE and CRC fields combined + CRSF_FRAME_LENGTH_NON_PAYLOAD = + 4, // combined length of all fields except payload +}; + +/** Length of CRSF frames */ +enum { + CRSF_FRAME_GPS_PAYLOAD_SIZE = 15, + CRSF_FRAME_BATTERY_SENSOR_PAYLOAD_SIZE = 8, + CRSF_FRAME_LINK_STATISTICS_PAYLOAD_SIZE = 10, + CRSF_FRAME_RC_CHANNELS_PAYLOAD_SIZE = + 22, // 11 bits per channel * 16 channels = 22 bytes. + CRSF_FRAME_ATTITUDE_PAYLOAD_SIZE = 6, +}; + +/** CRSF Sensor frame type */ +typedef enum { + CRSF_FRAMETYPE_GPS = 0x02, + CRSF_FRAMETYPE_BATTERY_SENSOR = 0x08, + CRSF_FRAMETYPE_LINK_STATISTICS = 0x14, + CRSF_FRAMETYPE_OPENTX_SYNC = 0x10, + CRSF_FRAMETYPE_RADIO_ID = 0x3A, + CRSF_FRAMETYPE_RC_CHANNELS_PACKED = 0x16, + CRSF_FRAMETYPE_ATTITUDE = 0x1E, + CRSF_FRAMETYPE_FLIGHT_MODE = 0x21, + // Extended Header Frames, range: 0x28 to 0x96 + CRSF_FRAMETYPE_DEVICE_PING = 0x28, + CRSF_FRAMETYPE_DEVICE_INFO = 0x29, + CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY = 0x2B, + CRSF_FRAMETYPE_PARAMETER_READ = 0x2C, + CRSF_FRAMETYPE_PARAMETER_WRITE = 0x2D, + CRSF_FRAMETYPE_COMMAND = 0x32, + // MSP commands + CRSF_FRAMETYPE_MSP_REQ = + 0x7A, // response request using msp sequence as command + CRSF_FRAMETYPE_MSP_RESP = 0x7B, // reply with 58 byte chunked binary + CRSF_FRAMETYPE_MSP_WRITE = 0x7C, // write with 8 byte chunked binary (OpenTX + // outbound telemetry_buffer limit) +} crsf_frame_type_e; + +/** CRSF sensor address */ +typedef enum { + CRSF_ADDRESS_BROADCAST = 0x00, + CRSF_ADDRESS_USB = 0x10, + CRSF_ADDRESS_TBS_CORE_PNP_PRO = 0x80, + CRSF_ADDRESS_RESERVED1 = 0x8A, + CRSF_ADDRESS_CURRENT_SENSOR = 0xC0, + CRSF_ADDRESS_GPS = 0xC2, + CRSF_ADDRESS_TBS_BLACKBOX = 0xC4, + CRSF_ADDRESS_FLIGHT_CONTROLLER = 0xC8, + CRSF_ADDRESS_RESERVED2 = 0xCA, + CRSF_ADDRESS_RACE_TAG = 0xCC, + CRSF_ADDRESS_RADIO_TRANSMITTER = 0xEA, + CRSF_ADDRESS_CRSF_RECEIVER = 0xEC, + CRSF_ADDRESS_CRSF_TRANSMITTER = 0xEE, +} crsf_addr_e; + +/** Heder of CRSF Packet*/ +typedef struct crsf_header_s { + uint8_t device_addr; // from crsf_addr_e + uint8_t frame_size; // counts size after this byte, so it must be the payload + // size + 2 (type and crc) + uint8_t type; // from crsf_frame_type_e +} PACKED crsf_header_t; + +/** RC Packet Frame */ +typedef struct crsf_channels_s { + unsigned channel1 : 11; + unsigned channel2 : 11; + unsigned channel3 : 11; + unsigned channel4 : 11; + unsigned channel5 : 11; + unsigned channel6 : 11; + unsigned channel7 : 11; + unsigned channel8 : 11; + unsigned channel9 : 11; + unsigned channel10 : 11; + unsigned channel11 : 11; + unsigned channel12 : 11; + unsigned channel13 : 11; + unsigned channel14 : 11; + unsigned channel15 : 11; + unsigned channel16 : 11; +} PACKED crsf_channels_t; + +/** Payload Statics */ +typedef struct crsfPayloadLinkstatistics_s { + uint8_t uplink_RSSI_1; + uint8_t uplink_RSSI_2; + uint8_t uplink_Link_quality; + int8_t uplink_SNR; + uint8_t active_antenna; + uint8_t rf_Mode; + uint8_t uplink_TX_Power; + uint8_t downlink_RSSI; + uint8_t downlink_Link_quality; + int8_t downlink_SNR; +} crsfLinkStatistics_t; + +/** Battery Voltage parameters */ +typedef struct crsf_sensor_battery_s { + uint32_t voltage : 16; // V * 10 big endian + uint32_t current : 16; // A * 10 big endian + uint32_t capacity : 24; // mah big endian + uint32_t remaining : 8; // % +} PACKED crsf_sensor_battery_t; + +/** GPS parameters */ +typedef struct crsf_sensor_gps_s { + int32_t latitude; // degree / 10,000,000 big endian + int32_t longitude; // degree / 10,000,000 big endian + uint16_t groundspeed; // km/h / 10 big endian + uint16_t heading; // GPS heading, degree/100 big endian + uint16_t altitude; // meters, +1000m big endian + uint8_t satellites; // satellites +} PACKED crsf_sensor_gps_t; + +#if !defined(__linux__) +static inline uint16_t htobe16(uint16_t val) { +#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return val; +#else + return __builtin_bswap16(val); +#endif +} + +static inline uint16_t be16toh(uint16_t val) { +#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return val; +#else + return __builtin_bswap16(val); +#endif +} + +static inline uint32_t htobe32(uint32_t val) { +#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return val; +#else + return __builtin_bswap32(val); +#endif +} + +static inline uint32_t be32toh(uint32_t val) { +#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + return val; +#else + return __builtin_bswap32(val); +#endif +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif // CRSF_PROTOCOL_H \ No newline at end of file diff --git a/src/input/serialIO/fport/fport.cpp b/src/input/serialIO/fport/fport.cpp new file mode 100644 index 0000000..dcbe24b --- /dev/null +++ b/src/input/serialIO/fport/fport.cpp @@ -0,0 +1,52 @@ +/*! + * @file fport.cpp + * @brief Source file for the F.Port implementations + * @author Witty-Wizard + */ + +#include "fport.h" + +fport::fport(Stream *rxPort, int rxPin, int txPin, bool inverted) + : SerialIO(rxPort, rxPin, txPin, inverted) {} + +void fport::begin() { + +// Initialize the serial port +#if defined(ARDUINO_ARCH_ESP32) + HardwareSerial *serialPort = (HardwareSerial *)_rxPort; + serialPort->begin(FPORT_BAUDRATE, SERIAL_8N1, _rxPin, _txPin, _inverted); +#elif defined(ARDUINO_ARCH_RP2040) + SerialUART *serialPort = (SerialUART *)_rxPort; + serialPort->setPinout(_txPin, _rxPin); + serialPort->setInvertRX(_inverted); + serialPort->setInvertTX(_inverted); + serialPort->begin(FPORT_BAUDRATE, SERIAL_8N1); +#else +#warning "Unsupported hardware platform." +#endif +} + +void fport::processIncoming() { + uint8_t size = FPORT_MAX_PACKET_SIZE; + while (_rxPort->available()) { + _rxData[FPORT_MAX_PACKET_SIZE - 1] = _rxPort->read(); + if (_rxData[FPORT_MAX_PACKET_SIZE - size - 4] == FPORT_END_BYTES && + _rxData[FPORT_MAX_PACKET_SIZE - 1] == FPORT_END_BYTES) { + if (_rxData[FPORT_MAX_PACKET_SIZE - size - 2] == + FPORT_FRAMETYPE_RC_CHANNELS_PACKED) { + memcpy(&_channelData, &_rxData[FPORT_MAX_PACKET_SIZE - size - 1], + sizeof(_channelData)); + } + } + if (_rxData[FPORT_MAX_PACKET_SIZE - 2] == FPORT_END_BYTES) { + size = _rxData[FPORT_MAX_PACKET_SIZE - 1]; + } + leftShift(_rxData, sizeof(_rxData)); + } +} + +void fport::getChannel(rc_channels_t *channelData) { + memcpy(channelData, &_channelData, sizeof(rc_channels_t)); +} + +void fport::crc() {} \ No newline at end of file diff --git a/src/input/serialIO/fport/fport.h b/src/input/serialIO/fport/fport.h new file mode 100644 index 0000000..57f3f70 --- /dev/null +++ b/src/input/serialIO/fport/fport.h @@ -0,0 +1,27 @@ +/*! + * @file fport.h + * @brief Header file for the FPort Protocol definations + * @author Witty-Wizard + */ + +#pragma once +#ifndef FPORT_H +#define FPORT_H + +#include "../SerialIO.h" +#include "fport_protocol.h" + +class fport : public SerialIO { +private: + uint8_t _rxData[FPORT_MAX_PACKET_SIZE] = {0}; + fport_channels_t _channelData; + +public: + explicit fport(Stream *rxPort, int rxPin = -1, int txPin = -1, + bool inverted = true); + void begin() override; + void processIncoming() override; + void getChannel(rc_channels_t *channelData) override; + void crc(); +}; +#endif \ No newline at end of file diff --git a/src/input/serialIO/fport/fport_protocol.h b/src/input/serialIO/fport/fport_protocol.h new file mode 100644 index 0000000..55b0fa3 --- /dev/null +++ b/src/input/serialIO/fport/fport_protocol.h @@ -0,0 +1,55 @@ +/*! + * @file fport_protocol.h + * @brief Header file for the F.Port protocol implementation. + */ +#pragma once + +#ifndef FPORT_PROTOCOL_H +#define FPORT_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define PACKED __attribute__((packed)) +#define FPORT_BAUDRATE 115200 ///< F.Port baudrate +#define FPORT_MAX_PACKET_SIZE 29 ///< F.Port maximum packet length +#define FPORT_END_BYTES 0x7E + +typedef enum { + FPORT_FRAMETYPE_RC_CHANNELS_PACKED = 0x00, + FPORT_FRAMETYPE_DOWNLINK = 0x01, + FPORT_FRAMETYPE_UPLINK = 0x81, +} fport_frame_type_e; + +typedef struct fport_channels_s { + unsigned channel1 : 11; + unsigned channel2 : 11; + unsigned channel3 : 11; + unsigned channel4 : 11; + unsigned channel5 : 11; + unsigned channel6 : 11; + unsigned channel7 : 11; + unsigned channel8 : 11; + unsigned channel9 : 11; + unsigned channel10 : 11; + unsigned channel11 : 11; + unsigned channel12 : 11; + unsigned channel13 : 11; + unsigned channel14 : 11; + unsigned channel15 : 11; + unsigned channel16 : 11; + unsigned dummy : 4; + unsigned failsafe : 1; + unsigned framelost : 1; + unsigned channel17 : 1; + unsigned channel18 : 1; +} PACKED fport_channels_t; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/input/serialIO/ibus/ibus.cpp b/src/input/serialIO/ibus/ibus.cpp new file mode 100644 index 0000000..07a0dbf --- /dev/null +++ b/src/input/serialIO/ibus/ibus.cpp @@ -0,0 +1,80 @@ +#include "ibus.h" + +ibus::ibus(Stream *rxPort, int rxPin, int txPin, bool inverted) + : SerialIO(rxPort, rxPin, txPin, inverted) {} + +void ibus::begin() { + +// Initialize the serial port +#if defined(ARDUINO_ARCH_ESP32) + HardwareSerial *serialPort = (HardwareSerial *)_rxPort; + serialPort->begin(IBUS_BAUDRATE, SERIAL_8N1, _rxPin, _txPin, _inverted); +#elif defined(ARDUINO_ARCH_AVR) + HardwareSerial *serialPort = (HardwareSerial *)_rxPort; + serialPort->begin(IBUS_BAUDRATE); +#elif defined(ARDUINO_ARCH_RP2040) + SerialUART *serialPort = (SerialUART *)_rxPort; + serialPort->setPinout(_txPin, _rxPin); + serialPort->setInvertRX(_inverted); + serialPort->setInvertTX(_inverted); + serialPort->begin(IBUS_BAUDRATE, SERIAL_8N1); +#else +#warning "Unsupported hardware platform." +#endif +} + +void ibus::processIncoming() { + while (_rxPort->available()) { + _rxData[IBUS_MAX_PACKET_SIZE - 1] = _rxPort->read(); + if (_rxData[0] == IBUS_HEADER1 && _rxData[1] == IBUS_HEADER2) { + if (checkSum()) { + size_t len = sizeof(ibus_channels_s) / sizeof(uint16_t); + uint16_t *arr = (uint16_t *)&_channelData; + for (int i = 0; i < len; i++) { + arr[i] = (_rxData[i * sizeof(uint16_t) + 1] << 8) | + _rxData[i * sizeof(uint16_t)]; + } + } + } + leftShift(_rxData, sizeof(_rxData)); + } +} + +void ibus::getChannel(rc_channels_t *channelData) { + channelData->channel1 = _channelData.channel1; + channelData->channel2 = _channelData.channel2; + channelData->channel3 = _channelData.channel3; + channelData->channel4 = _channelData.channel4; + channelData->channel5 = _channelData.channel5; + channelData->channel6 = _channelData.channel6; + channelData->channel7 = _channelData.channel7; + channelData->channel8 = _channelData.channel8; + channelData->channel9 = _channelData.channel9; + channelData->channel10 = _channelData.channel10; + channelData->channel11 = _channelData.channel11; + channelData->channel12 = _channelData.channel12; + channelData->channel13 = _channelData.channel13; + channelData->channel14 = _channelData.channel14; +} + +void ibus::getChannel(ibus_channels_t *channelData) { + *channelData = _channelData; +} + +bool ibus::checkSum() { + // Sum all the elements of _rxData except the last two bytes + uint16_t sum = 0; + for (size_t i = 0; i < sizeof(_rxData) - 2; ++i) { + sum += _rxData[i]; + } + + // Transform the last two bytes into a little-endian uint16_t + uint16_t checkSum = + (_rxData[sizeof(_rxData) - 1] << 8) | _rxData[sizeof(_rxData) - 2]; + + // Add the last two bytes to the sum + sum += checkSum; + + // Check if the sum matches the expected CRC + return (sum == 0xFFFF); // Assuming IBUS CRC is 0xFFFF when correct +} \ No newline at end of file diff --git a/src/input/serialIO/ibus/ibus.h b/src/input/serialIO/ibus/ibus.h new file mode 100644 index 0000000..d2cb2d4 --- /dev/null +++ b/src/input/serialIO/ibus/ibus.h @@ -0,0 +1,59 @@ +/*! + * @file ibus.h + * @brief Header file for the Ibus protocol implementation. + */ + +#pragma once +#ifndef IBUS_H +#define IBUS_H + +#include "../SerialIO.h" // Include header file for the serial IO class +#include "ibus_protocol.h" + +/** + * @brief A class for handling IBUS protocol communication. + */ +class ibus : public SerialIO { +private: + ibus_channels_t _channelData; + uint8_t _rxData[IBUS_MAX_PACKET_SIZE]; ///< Buffer to store received IBUS data + bool checkSum(); + +public: + /** + * @brief Constructor for the IBUS class. + * @param rxPort Reference to the hardware serial port for RX communication. + * @param rxPin The RX pin number. + * @param txPin The TX pin number. + * @param inverted Whether the serial signal is inverted (true) or not + * (false). + */ + explicit ibus(Stream *rxPort, int rxPin = -1, int txPin = -1, + bool inverted = false); + + /** + * @brief Initializes the IBUS communication. + */ + void begin() override; + + /** + * @brief Processes incoming IBUS data. + */ + void processIncoming() override; + + /** + * @brief Gets the decoded RC channels from the IBUS data. + * @param channelData Pointer to a rc_channels_t struct where the decoded + * channel data will be stored. + */ + void getChannel(rc_channels_t *channelData) override; + + /** + * @brief Gets the decoded RC channels from the IBUS data. + * @param channelData Pointer to a ibus_channels_t struct where the decoded + * channel data will be stored. + */ + void getChannel(ibus_channels_t *channelData) override; +}; + +#endif // IBUS_H diff --git a/src/input/serialIO/ibus/ibus_protocol.h b/src/input/serialIO/ibus/ibus_protocol.h new file mode 100644 index 0000000..e88103e --- /dev/null +++ b/src/input/serialIO/ibus/ibus_protocol.h @@ -0,0 +1,45 @@ +/*! + * @file ibus_protocol.h + * @brief Header file for the iBus protocol implementation. + */ +#pragma once + +#ifndef IBUS_PROTOCOL_H +#define IBUS_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define PACKED __attribute__((packed)) +#define IBUS_MAX_PACKET_SIZE 32 ///< Maximum packet size for the IBUS protocol +#define IBUS_BAUDRATE 115200 ///< Baud rate for IBUS communication +#define IBUS_HEADER1 0x20 +#define IBUS_HEADER2 0x40 + +typedef struct ibus_channels_s { + unsigned header : 16; + unsigned channel1 : 16; + unsigned channel2 : 16; + unsigned channel3 : 16; + unsigned channel4 : 16; + unsigned channel5 : 16; + unsigned channel6 : 16; + unsigned channel7 : 16; + unsigned channel8 : 16; + unsigned channel9 : 16; + unsigned channel10 : 16; + unsigned channel11 : 16; + unsigned channel12 : 16; + unsigned channel13 : 16; + unsigned channel14 : 16; + unsigned checksum : 16; +} PACKED ibus_channels_t; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/input/serialIO/sbus/sbus.cpp b/src/input/serialIO/sbus/sbus.cpp new file mode 100644 index 0000000..9b25fae --- /dev/null +++ b/src/input/serialIO/sbus/sbus.cpp @@ -0,0 +1,90 @@ +/*! + * @file sbus.cpp + * @brief Source file for the SBus implementations + * @author Witty-Wizard + */ +#include "sbus.h" + +sbus::sbus(Stream *rxPort, int rxPin, int txPin, bool inverted) + : SerialIO(rxPort, rxPin, txPin, inverted) {} + +void sbus::begin() { + +// Initialize the serial port +#if defined(ARDUINO_ARCH_ESP32) + HardwareSerial *serialPort = (HardwareSerial *)_rxPort; + serialPort->begin(SBUS_BAUDRATE, SERIAL_8E2, _rxPin, _txPin, _inverted); +#elif defined(ARDUINO_ARCH_RP2040) + SerialUART *serialPort = (SerialUART *)_rxPort; + serialPort->setPinout(_txPin, _rxPin); + serialPort->setInvertRX(_inverted); + serialPort->setInvertTX(_inverted); + serialPort->begin(SBUS_BAUDRATE, SERIAL_8E2); +#else +#warning "Unsupported hardware platform." +#endif +} + +void sbus::processIncoming() { + while (_rxPort->available()) { + uint32_t now = micros(); + uint8_t incomingByte = _rxPort->read(); + + // Preventing false sync by checking the start of a new frame after a gap in the data stream + // If there was a gap, we MUST be at the start of a frame + if (now - lastByteMicros > SBUS_GAP_THRESHOLD) { + bufferIdx = 0; + } + lastByteMicros = now; + + if (bufferIdx < 25) { + _rxData[bufferIdx++] = incomingByte; + } + + if (bufferIdx == 25) { + if (_rxData[0] == HEADER_SBUS && + _rxData[SBUS_MAX_PACKET_SIZE - 1] == FOOTER_SBUS) { + + memcpy(&_channelData, _rxData, sizeof(_channelData)); + + _lastValidPacketTime = millis(); + _connectionTimeout = false; + } else { + // Potential "False Sync" - discard and wait for next gap + bufferIdx = 0; + } + } + } + + if (millis() - _lastValidPacketTime > SBUS_TIMEOUT) { + _connectionTimeout = true; + } +} + +void sbus::getChannel(rc_channels_t *channelData) { + memcpy(channelData, (uint8_t *)&_channelData + 1, sizeof(rc_channels_t)); +} + +bool sbus::getFailsafe() { + return _channelData.failsafe; +} + +bool sbus::getFramelost() { + return _channelData.framelost; +} + +bool sbus::getChannel17() { + return _channelData.channel17; +} + +bool sbus::getChannel18() { + return _channelData.channel18; +} + +bool sbus::getSerialConnectionStatus() { + return !_connectionTimeout; +} + +uint32_t sbus::getLastValidPacketTime() { + return _lastValidPacketTime; + } \ No newline at end of file diff --git a/src/input/serialIO/sbus/sbus.h b/src/input/serialIO/sbus/sbus.h new file mode 100644 index 0000000..61a481f --- /dev/null +++ b/src/input/serialIO/sbus/sbus.h @@ -0,0 +1,82 @@ +/*! + * @file sbus.h + * @brief Header file for the SBUS protocol implementation. + */ + +#pragma once +#ifndef SBUS_H +#define SBUS_H + +#include "../SerialIO.h" // Include header file for the serial IO class +#include "sbus_protocol.h" + +/** + * @brief A class for handling SBUS protocol communication. + */ +class sbus : public SerialIO { +private: + sbus_channels_t _channelData; + uint8_t _rxData[SBUS_MAX_PACKET_SIZE]; + const uint32_t SBUS_GAP_THRESHOLD = 2000; // 2ms in microseconds + uint32_t lastByteMicros = 0; + uint8_t bufferIdx = 0; + +public: + /** + * @brief Constructor for the SBUS class. + * @param rxPort Reference to the hardware serial port for RX communication. + * @param rxPin The RX pin number. + * @param txPin The TX pin number. + * @param inverted Whether the serial signal is inverted (true) or not + * (false). + */ + explicit sbus(Stream *rxPort, int rxPin = -1, int txPin = -1, + bool inverted = true); + + /** + * @brief Initializes the SBUS communication. + */ + void begin() override; + void processIncoming() override; + + /** + * @brief Gets the decoded RC channels from the SBUS data. + * @param channelData Pointer to a crsf_channels_t struct where the decoded + * channel data will be stored. + */ + void getChannel(rc_channels_t *channelData) override; + + /** + * @brief Gets the failsafe status from the SBUS data. + * @return True if failsafe is active, false otherwise. + */ + bool getFailsafe(); + + /** + * @brief Gets the frame lost status from the SBUS data. + * @return True if frame lost is active, false otherwise. + */ + bool getFramelost(); + + /** + * @brief Gets the channel 17 status from the SBUS data. + * @return True if channel 17 is active, false otherwise. + */ + bool getChannel17(); + + /** + * @brief Gets the channel 18 status from the SBUS data. + * @return True if channel 18 is active, false otherwise. + */ + bool getChannel18(); + + /** + * @brief Gets the serial connection status. + * @return True if the connection is active, false if it has timed out. + */ + bool getSerialConnectionStatus(); + + uint32_t getLastValidPacketTime(); +}; + +#endif \ No newline at end of file diff --git a/src/input/serialIO/sbus/sbus_protocol.h b/src/input/serialIO/sbus/sbus_protocol.h new file mode 100644 index 0000000..1431d81 --- /dev/null +++ b/src/input/serialIO/sbus/sbus_protocol.h @@ -0,0 +1,52 @@ +/*! + * @file sbus_protocol.h + * @brief Header file for the SBus protocol implementation. + */ +#pragma once + +#ifndef SBUS_PROTOCOL_H +#define SBUS_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define PACKED __attribute__((packed)) +#define HEADER_SBUS 0x0F ///< SBus Header Byte +#define FOOTER_SBUS 0x00 ///< SBus Footer Byte +#define SBUS_BAUDRATE 100000 ///< SBus baudrate +#define SBUS_MAX_PACKET_SIZE 25 ///< SBus packet length +#define SBUS_TIMEOUT 200 ///< SBus timeout in milliseconds + +typedef struct sbus_channels_s { + unsigned header : 8; + unsigned channel1 : 11; + unsigned channel2 : 11; + unsigned channel3 : 11; + unsigned channel4 : 11; + unsigned channel5 : 11; + unsigned channel6 : 11; + unsigned channel7 : 11; + unsigned channel8 : 11; + unsigned channel9 : 11; + unsigned channel10 : 11; + unsigned channel11 : 11; + unsigned channel12 : 11; + unsigned channel13 : 11; + unsigned channel14 : 11; + unsigned channel15 : 11; + unsigned channel16 : 11; + unsigned dummy : 4; + unsigned failsafe : 1; + unsigned framelost : 1; + unsigned channel17 : 1; + unsigned channel18 : 1; + unsigned footer : 8; +} PACKED sbus_channels_t; +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/memory/memory-manager.cpp b/src/memory/memory-manager.cpp new file mode 100644 index 0000000..2f90a28 --- /dev/null +++ b/src/memory/memory-manager.cpp @@ -0,0 +1,58 @@ +#include "memory-manager.h" + +// Instantiate the actual memory blocks in RAM +MainConfig activeMainConfig; +GlobalEffectsConfig activeEffectsConfig; +MemoryManager memoryManager; + +void MemoryManager::begin() { + // Open the namespace in read/write mode (false) + prefs.begin(NVS_NAMESPACE, false); +} + +void MemoryManager::loadConfig() { + // --- 1. Load MainConfig --- + size_t mainSize = prefs.getBytesLength(KEY_MAIN_CFG); + if (mainSize == sizeof(MainConfig)) { + prefs.getBytes(KEY_MAIN_CFG, &activeMainConfig, sizeof(MainConfig)); + } else { + // Memory is empty or struct size changed. + // Initialize critical defaults here to prevent undefined behavior. + activeMainConfig.nodeId = 0; // 0 = Master Controller + activeMainConfig.ppmMode = PpmInputMode::MULTIPLEXED_8CH; + activeMainConfig.failsafeTimeoutMs = 2000; + activeMainConfig.failsafeMask = BIT_HAZARD_LIGHT; // Or e.g., BIT_HAZARD_LIGHT + } + + // --- 2. Load GlobalEffectsConfig --- + size_t effectsSize = prefs.getBytesLength(KEY_EFFECTS_CFG); + if (effectsSize == sizeof(GlobalEffectsConfig)) { + prefs.getBytes(KEY_EFFECTS_CFG, &activeEffectsConfig, sizeof(GlobalEffectsConfig)); + } else { + // Apply safe defaults for the very first boot + activeEffectsConfig.turnSignalFreq = 1000; + activeEffectsConfig.strobeFlashDuration = 40; + activeEffectsConfig.strobeShortPause = 60; + activeEffectsConfig.strobeLongPause = 400; + activeEffectsConfig.flashToPassFreq = 100; + activeEffectsConfig.beacon1Speed = 2000; + activeEffectsConfig.beacon1MaxLeds = 4; + activeEffectsConfig.beacon2Speed = 2000; + activeEffectsConfig.beacon2MaxLeds = 4; + activeEffectsConfig.starterDimFactor = 50; + activeEffectsConfig.corneringLightOffDelay = 5000; + activeEffectsConfig.xenonFlashDuration = 30; + activeEffectsConfig.xenonFadeDuration = 8000; + activeEffectsConfig.xenonLowBeamStartPwm = 50; + } +} + +void MemoryManager::saveConfig() { + prefs.putBytes(KEY_MAIN_CFG, &activeMainConfig, sizeof(MainConfig)); + prefs.putBytes(KEY_EFFECTS_CFG, &activeEffectsConfig, sizeof(GlobalEffectsConfig)); +} + +void MemoryManager::factoryReset() { + prefs.clear(); // Deletes all keys in the current namespace + loadConfig(); // Reloads the safe defaults into RAM +} \ No newline at end of file diff --git a/src/memory/memory-manager.h b/src/memory/memory-manager.h new file mode 100644 index 0000000..e402e44 --- /dev/null +++ b/src/memory/memory-manager.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include "../config/light-mode.h" +#include "../config/main-config.h" // Contains your MainConfig struct +#include "../config/global-effects-config.h" // Contains your GlobalEffectsConfig struct + +// Declare the global instances so any file including this header can access them +extern MainConfig activeMainConfig; +extern GlobalEffectsConfig activeEffectsConfig; + +class MemoryManager { +private: + Preferences prefs; + + // NVS Namespace (Max 15 characters!) + const char* NVS_NAMESPACE = "rc_truck"; + + // Keys for our specific data blobs (Max 15 characters!) + const char* KEY_MAIN_CFG = "main_cfg"; + const char* KEY_EFFECTS_CFG = "effects_cfg"; + +public: + // Initializes the NVS partition + void begin(); + + // Loads data from flash into the active RAM structs, or sets defaults if empty + void loadConfig(); + + // Commits the current RAM structs to the flash memory + void saveConfig(); + + // Wipes the NVS namespace (useful for fresh setups or struct upgrades) + void factoryReset(); +}; + +// Declare the global manager instance +extern MemoryManager memoryManager; \ No newline at end of file diff --git a/src/output/hardware-soft-pwm.cpp b/src/output/hardware-soft-pwm.cpp new file mode 100644 index 0000000..f71a9bd --- /dev/null +++ b/src/output/hardware-soft-pwm.cpp @@ -0,0 +1,89 @@ +#include "hardware-soft-pwm.h" + +// --- Static Member Initialization --- +uint8_t HardwareSoftPWM::activePins[HardwareSoftPWM::MAX_PINS]; +volatile uint8_t HardwareSoftPWM::dutyCycles[HardwareSoftPWM::MAX_PINS]; +uint8_t HardwareSoftPWM::pinCount = 0; +volatile uint8_t HardwareSoftPWM::currentStep = 0; +hw_timer_t* HardwareSoftPWM::timer = nullptr; + +// --- Interrupt Service Routine --- +void IRAM_ATTR HardwareSoftPWM::onTimer() { + currentStep++; + if (currentStep >= PWM_RESOLUTION) { + currentStep = 0; + } + + // Toggle all registered pins based on their target duty cycle + for (uint8_t i = 0; i < pinCount; i++) { + if (dutyCycles[i] > currentStep) { + digitalWrite(activePins[i], HIGH); + } else { + digitalWrite(activePins[i], LOW); + } + } +} + +// --- Public Methods --- +void HardwareSoftPWM::begin() { + if (timer == nullptr) { + // Use Timer 0, prescaler 80 (80MHz / 80 = 1MHz -> 1 tick = 1 microsecond) + timer = timerBegin(0, 80, true); + + // Attach the static ISR function + timerAttachInterrupt(timer, &HardwareSoftPWM::onTimer, true); + + // Set alarm to trigger every 10 microseconds (100 steps * 10us = 1000Hz) + timerAlarmWrite(timer, 10, true); + + // Start the timer + timerAlarmEnable(timer); + } +} + +bool HardwareSoftPWM::attach(uint8_t pin) { + if (pinCount < MAX_PINS) { + // Register the pin + activePins[pinCount] = pin; + dutyCycles[pinCount] = 0; // Default to OFF + + // Configure hardware pin + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + + pinCount++; + return true; // Successfully attached + } + return false; // Array is full +} + +void HardwareSoftPWM::update(uint8_t pin, uint8_t duty) { + // Constrain input to avoid buffer overflows or logical errors + if (duty > PWM_RESOLUTION) { + duty = PWM_RESOLUTION; + } + + // Search for the pin and update its duty cycle + for (uint8_t i = 0; i < pinCount; i++) { + if (activePins[i] == pin) { + dutyCycles[i] = duty; + break; + } + } +} + +void HardwareSoftPWM::reset() { + // 1. Safely turn off all currently active pins to prevent stuck states + for (uint8_t i = 0; i < pinCount; i++) { + digitalWrite(activePins[i], LOW); + + // Optional but clean: clear the arrays + activePins[i] = 0; + dutyCycles[i] = 0; + } + + // 2. Reset the counters. + // The ISR will now skip its execution loop since pinCount is 0. + pinCount = 0; + currentStep = 0; +} \ No newline at end of file diff --git a/src/output/hardware-soft-pwm.h b/src/output/hardware-soft-pwm.h new file mode 100644 index 0000000..82efa48 --- /dev/null +++ b/src/output/hardware-soft-pwm.h @@ -0,0 +1,30 @@ +#pragma once +#include + +class HardwareSoftPWM { +private: + static const uint8_t MAX_PINS = 14; + static const uint8_t PWM_RESOLUTION = 100; // 0-100 steps + + static uint8_t activePins[MAX_PINS]; + static volatile uint8_t dutyCycles[MAX_PINS]; + static uint8_t pinCount; + + static volatile uint8_t currentStep; + static hw_timer_t* timer; + + // The Interrupt Service Routine (ISR) MUST be static and loaded into IRAM + static void IRAM_ATTR onTimer(); + +public: + // Initializes the hardware timer + static void begin(); + + // Configures a pin for software PWM and adds it to the ISR loop + static bool attach(uint8_t pin); + + // Updates the duty cycle (0 to 100) for a specific pin + static void update(uint8_t pin, uint8_t duty); + // Detaches all pins and safely turns them off + static void reset(); +}; \ No newline at end of file diff --git a/src/output/local-outputs.cpp b/src/output/local-outputs.cpp new file mode 100644 index 0000000..8bc5ae2 --- /dev/null +++ b/src/output/local-outputs.cpp @@ -0,0 +1,477 @@ +#include "local-outputs.h" + +LocalOutputController::LocalOutputController() {} + +void LocalOutputController::resetOutput(uint8_t pin, uint8_t channel) { + ledPWM[channel].detachPin(pin); + servos[channel].detach(); + HardwareSoftPWM::reset(); +}; + +bool LocalOutputController::isSoftwarePWMOutput(uint8_t pin) { + for (int i = 0; i < NUM_LOCAL_OUTPUTS; i++) { + if (softwarePWMOutputs[i] == pin) { + return true; + } + } + return false; +} + +void LocalOutputController::begin() { + // Allocate all 4 hardware timers for the ESP32 PWM engine to use for servos. + // This prevents jitter and conflicts with standard LED PWM. + ESP32PWM::allocateTimer(0); + ESP32PWM::allocateTimer(1); + ESP32PWM::allocateTimer(2); + ESP32PWM::allocateTimer(3); + + // Reset the list of outputs for re-evaluation + for (int i = 0; i < NUM_LOCAL_OUTPUTS; i++) { + softwarePWMOutputs[i] = 0; + } + + uint8_t numberOfServoOutputs = 0; + uint8_t numberOfHardwarePWMOutputs = 0; + + // Check how many Servos are in use + for (int i = 0; i < NUM_LOCAL_OUTPUTS; i++) { + LocalOutputConfig& cfg = activeMainConfig.localOutputs[i]; + if (cfg.mode == OutputMode::SERVO) { + numberOfServoOutputs++; + } + } + + // Reserve one extra channel as two channels share the same timer + if(numberOfServoOutputs % 2 != 0) { + numberOfServoOutputs++; + } + + // Define outputs that need to be Software PWM as Hardware PWM outputs are limited + for (int i = 0; i < NUM_LOCAL_OUTPUTS; i++) { + LocalOutputConfig& cfg = activeMainConfig.localOutputs[i]; + uint8_t currentUsedChannels = numberOfServoOutputs + numberOfHardwarePWMOutputs; + + if (cfg.mode == OutputMode::PWM && currentUsedChannels <= MAX_LEDC_CHANNELS) { + numberOfHardwarePWMOutputs++; + } else { + softwarePWMOutputs[i] = cfg.pin; + } + } + + for (int i = 0; i < NUM_LOCAL_OUTPUTS; i++) { + LocalOutputConfig& cfg = activeMainConfig.localOutputs[i]; + + resetOutput(cfg.pin, i); + } + + for (int i = 0; i < NUM_LOCAL_OUTPUTS; i++) { + LocalOutputConfig& cfg = activeMainConfig.localOutputs[i]; + + switch (cfg.mode) { + case OutputMode::NONE: + pinMode(cfg.pin, INPUT); + break; + case OutputMode::DIGITAL: + pinMode(cfg.pin, OUTPUT); + digitalWrite(cfg.pin, LOW); // Safe default state + break; + + case OutputMode::PWM: + if(isSoftwarePWMOutput(cfg.pin)) { + HardwareSoftPWM::attach(cfg.pin); + } else { + // 12-bit gives us 4096 steps of brightness for ultra-smooth fading + // Core 2.x requires a channel (0-15). We use 'i' as the channel. + // 1000 Hz is perfect for LEDs (no visible flicker, no camera banding) + ledPWM[i].attachPin(cfg.pin, 1000, 12); + // Turn it off by writing to the CHANNEL, not the pin + ledPWM[i].write(0); + } + break; + case OutputMode::SERVO: + // param2 = min pulse (e.g., 1000us) + // param3 = max pulse (e.g., 2000us) + servos[i].setPeriodHertz(50); // Standard RC servo frequency + uint8_t GPIOpin = digitalPinToGPIONumber(cfg.pin); + + // Attach the pin and apply the custom pulse width limits from the config + servos[i].attach(GPIOpin, cfg.param2, cfg.param3); + + // Optionally move to a safe center position immediately: + // servos[i].writeMicroseconds(1500); + break; + } + } +} + +void LocalOutputController::update(uint32_t inputState, uint32_t effectState, uint8_t beacon1Pos, uint8_t beacon2Pos, uint16_t* servoStateArray) { + for (int i = 0; i < NUM_LOCAL_OUTPUTS; i++) { + LocalOutputConfig& cfg = activeMainConfig.localOutputs[i]; + + if (cfg.mode == OutputMode::NONE) continue; + + if (cfg.mode == OutputMode::PWM) { + // 1. Brain: What is the base target brightness? + uint16_t target = 0; + + // Ignore Starter for direct check + uint32_t triggerMask = cfg.triggerMask & ~BIT_STARTER_DIM; + if (triggerMask == BIT_BI_XENON) { + target = biXenonTargetPwm(i, cfg, inputState, effectState); + } else { + target = calculateTargetPwm(cfg, inputState, effectState, beacon1Pos, beacon2Pos); + } + + // 1.5. Modifier: Apply Starter Dimming if active and configured for this pin + if ((inputState & BIT_STARTER_DIM) && (cfg.triggerMask & BIT_STARTER_DIM)) { + // Reduce target brightness by the dim factor (assuming factor is 0-100%) + // Cast to uint32_t prevents overflow during multiplication before division + target = (uint16_t)(((uint32_t)target * activeEffectsConfig.starterDimFactor) / 100); + } + + uint16_t actualValue = target; + + // 2. Muscle: Fade towards that target + if (!(cfg.triggerMask & BIT_BI_XENON)) { + actualValue = processFading(i, cfg, target); + } + + if(isSoftwarePWMOutput(cfg.pin)) { + HardwareSoftPWM::update(cfg.pin, (uint8_t)map(actualValue, 0, 4095, 0, 100)); // Map 12-bit brightness to 0-100% duty cycle + // updatePWMMapping(i, actualValue); + // writeSoftwarePWMOutput(i, cfg.pin); + } else { + writeHardwarePWMOutput(i, actualValue); + } + } + else if (cfg.mode == OutputMode::SERVO) { + if (cfg.param1 != InputServoMapping::NONE && cfg.param1 <= InputServoMapping::SRV_REMOTE_6) { + uint16_t normalizedValue = servoStateArray[cfg.param1 - 1]; + uint16_t pulseWidth = map(normalizedValue, 0, 2000, cfg.param2, cfg.param3); + pulseWidth = constrain(pulseWidth, cfg.param2, cfg.param3); + servos[i].writeMicroseconds(pulseWidth); + } + } + else if (cfg.mode == OutputMode::DIGITAL) { + // Digital ignores fading, just snaps based on the target > 0 + uint16_t target = calculateTargetPwm(cfg, inputState, effectState, beacon1Pos, beacon2Pos); + digitalWrite(cfg.pin, target > 0 ? HIGH : LOW); + } + } +} + +uint16_t LocalOutputController::calculateTargetPwm(const LocalOutputConfig& cfg, uint32_t inputState, uint32_t effectState, uint8_t beacon1Pos, uint8_t beacon2Pos) { + // Create a clean evaluation state that ignores the starter dim bit completely. + // This prevents the bit from accidentally triggering the fallback or early exit logic. + uint32_t evalState = inputState & ~BIT_STARTER_DIM; + uint32_t triggerMask = cfg.triggerMask & ~BIT_STARTER_DIM; // Also ignore the starter dim bit in the trigger mask for matching logic + + if (triggerMask & BIT_STATIC_OFF) return 0; + if (triggerMask & BIT_STATIC_ON) return cfg.param1; + + // --- COMBO: US-Style Tail Light LEFT --- + if (triggerMask == COMB_US_TAIL_L) { + // 1. Regular turn signal has highest priority (overrides brake on this side) + if (evalState & BIT_TURN_SIGNAL_L) { + return (effectState & BIT_GLOBAL_TURN_L) ? cfg.param2 : 0; + } + + // 2. Brake has second highest priority (overrides hazard) + if (evalState & BIT_BRAKE_LIGHT) { + return cfg.param2; + } + + // 3. Hazard lights (Priority 3: Only active if NOT braking) + if (evalState & BIT_HAZARD_LIGHT) { + return ((effectState & BIT_GLOBAL_TURN_L) && (effectState & BIT_GLOBAL_TURN_R)) ? cfg.param2 : 0; + } + + // 4. Parking (Lowest priority) + if (evalState & BIT_PARKING_LIGHT) return cfg.param1; + + return 0; // Off + } + + // --- COMBO: US-Style Tail Light RIGHT --- + if (triggerMask == COMB_US_TAIL_R) { + // Same logic, just for the right side + if (evalState & BIT_TURN_SIGNAL_R) { + return (effectState & BIT_GLOBAL_TURN_R) ? cfg.param2 : 0; + } + if (evalState & BIT_BRAKE_LIGHT) return cfg.param2; + if (evalState & BIT_HAZARD_LIGHT) return ((effectState & BIT_GLOBAL_TURN_L) && (effectState & BIT_GLOBAL_TURN_R)) ? cfg.param2 : 0; + if (evalState & BIT_PARKING_LIGHT) return cfg.param1; + + return 0; + } + + // --- COMBO: US-Style Tail Light LEFT --- + if (triggerMask == COMB_PARK_TURN_L) { + // 1. Regular turn signal has highest priority (overrides brake on this side) + if (evalState & BIT_TURN_SIGNAL_L) { + return (effectState & BIT_GLOBAL_TURN_L) ? cfg.param2 : 0; + } + + + // 3. Hazard lights (Priority 2: Only active if NOT braking) + if (evalState & BIT_HAZARD_LIGHT) { + return ((effectState & BIT_GLOBAL_TURN_L) && (effectState & BIT_GLOBAL_TURN_R)) ? cfg.param2 : 0; + } + + // 4. Parking / Low beam (Lowest priority) + if (evalState & BIT_PARKING_LIGHT) return cfg.param1; + + return 0; // Off + } + + // --- COMBO: US-Style Tail Light RIGHT --- + if (triggerMask == COMB_PARK_TURN_R) { + // Same logic, just for the right side + if (evalState & BIT_TURN_SIGNAL_R) { + return (effectState & BIT_GLOBAL_TURN_R) ? cfg.param2 : 0; + } + if (evalState & BIT_HAZARD_LIGHT) return ((effectState & BIT_GLOBAL_TURN_L) && (effectState & BIT_GLOBAL_TURN_R)) ? cfg.param2 : 0; + if (evalState & BIT_PARKING_LIGHT) return cfg.param1; + + return 0; + } + + // --- EFFECTS: Hazard Lights --- + if (triggerMask & BIT_HAZARD_LIGHT) { + if ((evalState & BIT_HAZARD_LIGHT) || ((effectState & BIT_GLOBAL_TURN_L) && (effectState & BIT_GLOBAL_TURN_R))) { + if((effectState & BIT_GLOBAL_TURN_L) && (effectState & BIT_GLOBAL_TURN_R)) { + return cfg.param1; + } else { + return 0; + } + } + } + + // --- EFFECTS: Turn Signal Left --- + if (triggerMask & BIT_TURN_SIGNAL_L) { + if ((evalState & BIT_TURN_SIGNAL_L) || (effectState & BIT_GLOBAL_TURN_L)) { + if(effectState & BIT_GLOBAL_TURN_L) { + return cfg.param1; + } else { + return 0; + } + } + } + + // --- EFFECTS: Turn Signal Right --- + if (triggerMask & BIT_TURN_SIGNAL_R) { + if ((evalState & BIT_TURN_SIGNAL_R) || (effectState & BIT_GLOBAL_TURN_R)) { + if(effectState & BIT_GLOBAL_TURN_R) { + return cfg.param1; + } else { + return 0; + } + } + } + + if (triggerMask & BIT_FOG_L) { + if (evalState & BIT_FOG) { + return cfg.param1; + } + + if (effectState & BIT_GLOBAL_CORNERING_L) { + return cfg.param1; + } else { + return 0; + } + } + + if (triggerMask & BIT_FOG_R) { + if (evalState & BIT_FOG) { + return cfg.param1; + } + + if (effectState & BIT_GLOBAL_CORNERING_R) { + return cfg.param1; + } else { + return 0; + } + } + + // --- EFFECTS: Flash to Pass on High beam output --- + if (triggerMask & BIT_HIGH_BEAM) { + if (evalState & BIT_FLASH_TO_PASS) { + if (effectState & BIT_GLOBAL_FLASH_TO_PASS) { + return cfg.param1; + } else { + return 0; + } + } + } + + // DRL is active when no regular lights are on + if (triggerMask & BIT_DRL) { + if (evalState & (BIT_PARKING_LIGHT | BIT_LOW_BEAM | BIT_HIGH_BEAM)) { + return 0; + } else { + return cfg.param1; + } + } + + // ==================================================================== + // EARLY EXIT: If the state doesn't match the trigger mask AT ALL, target is 0 + if ((evalState & triggerMask) == 0) { + return 0; + } + // ==================================================================== + + // --- COMBO 1: Park & Brake Light --- + if (triggerMask == COMB_PARK_AND_BRAKE) { + if (evalState & BIT_BRAKE_LIGHT) return cfg.param2; + if (evalState & BIT_PARKING_LIGHT) return cfg.param1; + } + + // --- COMBO 2: Headlights (Parking / Low / High) --- + if (triggerMask == COMB_PARK_AND_FULL_BEAM) { + if (evalState & BIT_HIGH_BEAM) return cfg.param3; + if (evalState & BIT_LOW_BEAM) return cfg.param2; + if (evalState & BIT_PARKING_LIGHT) return cfg.param1; + } + + // --- COMBO 3: Headlights (Parking / Low) --- + if (triggerMask == COMB_PARK_AND_LOW_BEAM) { + if (evalState & BIT_LOW_BEAM) return cfg.param2; + if (evalState & BIT_PARKING_LIGHT) return cfg.param1; + } + + // --- COMBO 4: Headlights (Low / High) ---8_ + if (triggerMask == COMB_LOW_AND_HIGH_BEAM) { + if (evalState & BIT_HIGH_BEAM) return cfg.param2; + if (evalState & BIT_LOW_BEAM) return cfg.param1; + } + + // --- EFFECTS: Strobe Light --- + if (triggerMask & BIT_STROBE_LIGHT) { + if (evalState & BIT_STROBE_LIGHT) { + if (effectState & BIT_GLOBAL_STROBE) { + return cfg.param1; + } else { + return 0; + } + } + } + + // --- EFFECTS: Beacon Light --- + if ((triggerMask & BIT_BEACON_LIGHT) && (evalState & BIT_BEACON_LIGHT)) { + if (cfg.param2 == 0 && cfg.param1 == beacon1Pos) { + return cfg.param3; + } else if (cfg.param2 == 1 && cfg.param1 == beacon2Pos) { + return cfg.param3; + } else { + return 0; + } + } + + // --- FALLBACK: Standard Output --- + return cfg.param1; +} + +uint16_t LocalOutputController::biXenonTargetPwm(int index, const LocalOutputConfig& cfg, uint32_t inputState, uint32_t effectState) { + uint32_t currentMillis = millis(); + uint16_t targetPwm = 0; + + if(inputState & BIT_LOW_BEAM) { + targetPwm = cfg.param1; // Base brightness for low beam + } + + if(inputState & BIT_HIGH_BEAM) { + targetPwm = cfg.param2; // Brighter setting for high beam + } + + if(targetPwm == 0) { + xenonCurrentPwmValues[index] = 0; // Snap to 0 immediately when off, no fading needed + return processFading(index, cfg, 0); + } + + if(targetPwm != 0 && xenonCurrentPwmValues[index] == 0) { + xenonStartMillis[index] = currentMillis; // Reset fade timer on target change + } + + // Calculate how much time has passed since the last frame + uint32_t elapsed = currentMillis - xenonStartMillis[index]; + uint32_t flashDuration = (uint32_t)activeEffectsConfig.xenonFlashDuration; // e.g., 30 milliseconds for the initial flash + uint32_t fadeDuration = (uint32_t)activeEffectsConfig.xenonFadeDuration; // Total fade duration after the flash + + if (elapsed <= flashDuration) { + // Flash for 30 milliseconds at max brightness for that "popping" effect + xenonCurrentPwmValues[index] = cfg.param2; // Max brightness + } else if (elapsed <= fadeDuration) { + // After 30ms, snap to half of the low beam target brightness and then fade up to the full target over the next 8s + uint32_t startPwm = (uint32_t)activeEffectsConfig.xenonLowBeamStartPwm; // Start at half brightness + uint32_t endPwm = targetPwm; // End at the calculated target brightness + uint32_t fadeElapsed = elapsed - flashDuration; // Time since the initial flash + + // Linear fade calculation + if (fadeElapsed < fadeDuration) { + uint32_t step = (fadeElapsed * (endPwm - startPwm)) / fadeDuration; + xenonCurrentPwmValues[index] = (uint16_t)(startPwm + step); + } else { + xenonCurrentPwmValues[index] = (uint16_t)endPwm; // Ensure it reaches full target after fade duration + + } + } else { + // Flash to Pass overrides both when active but only if low or high beam is on (makes no sense to trigger it if headlights are off) + if ((inputState & BIT_FLASH_TO_PASS) && (effectState & BIT_GLOBAL_FLASH_TO_PASS)) { + targetPwm = cfg.param2; + } + + xenonCurrentPwmValues[index] = processFading(index, cfg, targetPwm);; // Maintain the target brightness after fade + } + + return xenonCurrentPwmValues[index]; +} + +uint16_t LocalOutputController::processFading(int index, const LocalOutputConfig& cfg, uint16_t targetPwm) { + uint32_t currentMillis = millis(); + + // If we are already at the target, do nothing + if (currentPwmValues[index] == targetPwm) { + lastFadeMillis[index] = currentMillis; + return currentPwmValues[index]; + } + + // If fadeTime is 0, snap instantly (good for strobes) + if (cfg.fadeTime == 0) { + currentPwmValues[index] = targetPwm; + + return currentPwmValues[index]; + } + + // Calculate how much time has passed since the last frame + uint32_t elapsed = currentMillis - lastFadeMillis[index]; + if (elapsed == 0) return currentPwmValues[index]; // Too fast, wait for next millisecond + + // Calculate step size. + // We want to cover 4095 steps (max 12-bit PWM) in 'fadeTime' milliseconds. + // step = (elapsedTime * MaxResolution) / TotalFadeTime + uint32_t step = (elapsed * 4095) / cfg.fadeTime; + if (step == 0) step = 1; // Ensure it always moves by at least 1 unit + + // Move current value towards target + if (currentPwmValues[index] < targetPwm) { + currentPwmValues[index] += step; + if (currentPwmValues[index] > targetPwm) currentPwmValues[index] = targetPwm; // Cap it + } else { + // Prevent underflow when subtracting + if (currentPwmValues[index] > step) { + currentPwmValues[index] -= step; + } else { + currentPwmValues[index] = 0; + } + if (currentPwmValues[index] < targetPwm) currentPwmValues[index] = targetPwm; // Cap it + } + + lastFadeMillis[index] = currentMillis; + + // Apply the new physical brightness + return currentPwmValues[index]; +} + +void LocalOutputController::writeHardwarePWMOutput(uint8_t pinIndex, uint16_t targetPwm) { + ledPWM[pinIndex].write(targetPwm); +} \ No newline at end of file diff --git a/src/output/local-outputs.h b/src/output/local-outputs.h new file mode 100644 index 0000000..f512287 --- /dev/null +++ b/src/output/local-outputs.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include "../config/main-config.h" +#include "../config/light-mode.h" +#include "../config/global-effects-config.h" +#include "hardware-soft-pwm.h" + +#define MAX_LEDC_CHANNELS 8 +// ~244Hz +#define SOFTWARE_PWM_PERIODE 4096UL +// The bitmask is always the period minus 1 +#define SOFTWARE_PWM_MASK 4095UL + +class LocalOutputController { +private: + Servo servos[NUM_LOCAL_OUTPUTS]; + ESP32PWM ledPWM[NUM_LOCAL_OUTPUTS]; + + // --- Fading Engine State --- + uint16_t currentPwmValues[NUM_LOCAL_OUTPUTS]; + uint32_t lastFadeMillis[NUM_LOCAL_OUTPUTS]; + uint8_t softwarePWMOutputs[NUM_LOCAL_OUTPUTS]; + + uint16_t xenonCurrentPwmValues[NUM_LOCAL_OUTPUTS]; + uint32_t xenonStartMillis[NUM_LOCAL_OUTPUTS]; + + void resetOutput(uint8_t pin, uint8_t channel); + + bool isSoftwarePWMOutput(uint8_t pin); + + // Helper: Calculates the exact target brightness based on priorities + uint16_t calculateTargetPwm(const LocalOutputConfig& cfg, uint32_t inputState, uint32_t effectState, uint8_t beacon1Pos, uint8_t beacon2Pos); + uint16_t biXenonTargetPwm(int index, const LocalOutputConfig& cfg, uint32_t inputState, uint32_t effectState); + + // Helper: Moves the current value towards the target over time + uint16_t processFading(int index, const LocalOutputConfig& cfg, uint16_t targetPwm); + void writeHardwarePWMOutput(uint8_t pinIndex, uint16_t targetPwm); + +public: + LocalOutputController(); + void begin(); + void update(uint32_t inputState, uint32_t effectState, uint8_t beacon1Pos, uint8_t beacon2Pos, uint16_t* servoStateArray); +}; \ No newline at end of file diff --git a/tools.cpp b/src/state-machine/blink.cpp similarity index 63% rename from tools.cpp rename to src/state-machine/blink.cpp index 1db9122..1fcda77 100644 --- a/tools.cpp +++ b/src/state-machine/blink.cpp @@ -13,34 +13,7 @@ * If not, see . ************************************/ -#include "tools.h" - -bool EdgeEvaluation::readEdge(bool input){ - if((input) && (!lastEdge)){ - lastEdge = true; - return true; - } else if((!input) && (lastEdge)){ - lastEdge = false; - return false; - } - return false; -} - -void Filter::init(int16_t initialValue) { - lastValue = initialValue; - doneFilter = false; -} - -int16_t Filter::filterValue(int16_t input, int16_t filterFactor, uint16_t filterTime){ - if((millis()%filterTime >= filterTime/2) && (doneFilter == false)) { - lastValue = (input - lastValue) / filterFactor + lastValue; - doneFilter = true; - } else if((millis()%filterTime < filterTime/2) && (doneFilter == true)) { - doneFilter = false; - } - - return lastValue; -} +#include "blink.h" uint8_t Blink::blink(uint16_t blinkTimeMillis) { if((blinkOnTime == 0) || (blinkOnTime > millis())){ //Reset blinkOnTime on startup and on overflow. diff --git a/tools.h b/src/state-machine/blink.h similarity index 70% rename from tools.h rename to src/state-machine/blink.h index 484464a..47c02d4 100644 --- a/tools.h +++ b/src/state-machine/blink.h @@ -13,27 +13,8 @@ * If not, see . ************************************/ -#ifndef _TOOLS_H_ -#define _TOOLS_H_ -//Definition +#pragma once #include "Arduino.h" -#include //https://github.com/bhagman/SoftPWM - - -//Classes -class EdgeEvaluation { - bool lastEdge; - public: - bool readEdge(bool input); -}; - -class Filter { - int16_t lastValue; - bool doneFilter; - public: - void init(int16_t initialValue); - int16_t filterValue(int16_t input, int16_t filterFactor = 20, uint16_t filterTime = 100); -}; class Blink { private: @@ -42,5 +23,3 @@ class Blink { uint8_t blink(uint16_t blinkTimeMillis); void resetBlink(); // Reset Blink after usage for next usage }; - -#endif \ No newline at end of file diff --git a/src/state-machine/edge-eval.cpp b/src/state-machine/edge-eval.cpp new file mode 100644 index 0000000..34832b0 --- /dev/null +++ b/src/state-machine/edge-eval.cpp @@ -0,0 +1,27 @@ +/************************************ + * Copyright (C) 2020-2025 Marina Egner + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + ************************************/ + +#include "edge-eval.h" + +bool EdgeEvaluation::readEdge(bool input){ + if((input) && (!lastEdge)){ + lastEdge = true; + return true; + } else if((!input) && (lastEdge)){ + lastEdge = false; + return false; + } + return false; +} diff --git a/ppmToSwitches.h b/src/state-machine/edge-eval.h similarity index 56% rename from ppmToSwitches.h rename to src/state-machine/edge-eval.h index 918d455..92152e0 100644 --- a/ppmToSwitches.h +++ b/src/state-machine/edge-eval.h @@ -12,20 +12,14 @@ * You should have received a copy of the GNU General Public License along with this program. * If not, see . ************************************/ - -#ifndef _PPM_TO_SWITCHES_h_ -#define _PPM_TO_SWITCHES_h_ -//Definition -#include "Arduino.h" -#define DIRECTION_UP 1 -#define DIRECTION_MID 2 -#define DIRECTION_DOWN 3 -#define PPM_INVERT 1 +#pragma once +#include "Arduino.h" -//Functions -uint8_t ppmToSwitchStages(uint16_t signal, bool invertDirection = 0); // Function to evaluate the ppm signal of a switch -uint8_t ppm2ToSwitch3Stages(uint16_t signal1, uint16_t signal2); // Function to evaluate the ppm signal of a 3 stages switch with two signals -uint32_t ppmServoToRange(uint32_t signal, uint32_t inMin = 1020, uint32_t inMax = 2020, uint32_t outMin = 0, uint32_t outMax = 1023); // Function to evaluate the ppm signal from a servo -#endif \ No newline at end of file +//Classes +class EdgeEvaluation { + bool lastEdge; + public: + bool readEdge(bool input); +}; diff --git a/src/state-machine/global-effects.cpp b/src/state-machine/global-effects.cpp new file mode 100644 index 0000000..4e6d9ee --- /dev/null +++ b/src/state-machine/global-effects.cpp @@ -0,0 +1,218 @@ +#include "global-effects.h" + +GlobalEffects::GlobalEffects() + : blink(), leftTurnSignal(false), rightTurnSignal(false), + strobeSignal(false), strobeStartMillis(0), + flashToPassSignal(false), + corneringLeftSignal(false), corneringRightSignal(false), + corneringLeftOffMillis(0), corneringRightOffMillis(0), + beacon1position(0), beacon1previousMillis(0), + beacon2position(0), beacon2previousMillis(0) {} + +void GlobalEffects::updateTurnIndicators(uint32_t globalInputState) { + if(globalInputState & BIT_HAZARD_LIGHT) { + bool blinkerState = blink.blink(activeEffectsConfig.turnSignalFreq); + leftTurnSignal = blinkerState; + rightTurnSignal = blinkerState; + } else if(globalInputState & BIT_TURN_SIGNAL_L) { + leftTurnSignal = blink.blink(activeEffectsConfig.turnSignalFreq); + rightTurnSignal = false; + } else if(globalInputState & BIT_TURN_SIGNAL_R) { + leftTurnSignal = false; + rightTurnSignal = blink.blink(activeEffectsConfig.turnSignalFreq); + } else if((leftTurnSignal || rightTurnSignal) && !blink.blink(activeEffectsConfig.turnSignalFreq)) { + // Only turn lights off when they have made a full on cycle + leftTurnSignal = false; + rightTurnSignal = false; + } else if (!leftTurnSignal && !rightTurnSignal) { + blink.resetBlink(); + } +} + +void GlobalEffects::updateStrobe(uint32_t globalInputState) { + if(globalInputState & BIT_STROBE_LIGHT) { + uint32_t currentMillis = millis(); + if(strobeStartMillis == 0) { + strobeStartMillis = currentMillis; + } + + uint32_t diff = currentMillis - strobeStartMillis; + + if(diff < activeEffectsConfig.strobeFlashDuration) { + strobeSignal = true; + } else if(diff < ( + activeEffectsConfig.strobeFlashDuration + + activeEffectsConfig.strobeShortPause + )) { + strobeSignal = false; + } else if(diff < ( + activeEffectsConfig.strobeFlashDuration + + activeEffectsConfig.strobeShortPause + + activeEffectsConfig.strobeFlashDuration + )) { + strobeSignal = true; + } else if(diff < ( + activeEffectsConfig.strobeFlashDuration + + activeEffectsConfig.strobeShortPause + + activeEffectsConfig.strobeFlashDuration + + activeEffectsConfig.strobeLongPause + )) { + strobeSignal = false; + } else { + strobeStartMillis = currentMillis; + } + + } else { + strobeSignal = false; + strobeStartMillis = 0; + } +}; + +void GlobalEffects::updateSingleBeacon(uint16_t beaconSpeed, uint8_t beaconMaxLeds, uint32_t* beaconPreviousMillis, uint8_t* beaconPosition) { + uint32_t currentMillis = millis(); + uint32_t intervalPerLed = (uint32_t)beaconSpeed / (uint32_t)beaconMaxLeds; + + if(currentMillis - *beaconPreviousMillis >= intervalPerLed) { + *beaconPreviousMillis = currentMillis; + + *beaconPosition += 1; + if(*beaconPosition >= beaconMaxLeds) { + *beaconPosition = 0; + } + } +}; +void GlobalEffects::updateBeacon(uint32_t globalInputState) { + if(globalInputState & BIT_BEACON_LIGHT) { + updateSingleBeacon(activeEffectsConfig.beacon1Speed, activeEffectsConfig.beacon1MaxLeds, &beacon1previousMillis, &beacon1position); + } + + if(globalInputState & BIT_BEACON_LIGHT) { + updateSingleBeacon(activeEffectsConfig.beacon2Speed, activeEffectsConfig.beacon2MaxLeds, &beacon2previousMillis, &beacon2position); + } +}; + +void GlobalEffects::updateFlashToPass(uint32_t globalInputState) { + if(globalInputState & BIT_FLASH_TO_PASS) { + if (millis() % activeEffectsConfig.flashToPassFreq < activeEffectsConfig.flashToPassFreq / 2) { + flashToPassSignal = true; + } else { + flashToPassSignal = false; + } + } else { + flashToPassSignal = false; + } +} + +void GlobalEffects::updateCornering(uint32_t globalInputState) { + uint32_t now = millis(); + + // Parking light or low beam must be on for cornering lights to work. + if(!(globalInputState & (BIT_PARKING_LIGHT | BIT_LOW_BEAM))) { + corneringLeftSignal = false; + corneringRightSignal = false; + corneringLeftOffMillis = 0; + corneringRightOffMillis = 0; + return; + } + + // If hazards are on, cornering lights should not be triggered by turn signals, but only by steering input + if(!(globalInputState & BIT_HAZARD_LIGHT)) { + if(globalInputState & BIT_TURN_SIGNAL_L) { + corneringLeftSignal = true; + corneringRightSignal = false; + corneringRightOffMillis = 0; + } + + if(globalInputState & BIT_TURN_SIGNAL_R) { + corneringRightSignal = true; + corneringLeftSignal = false; + corneringLeftOffMillis = 0;; + } + } + + // Steering input should override turn signals for cornering lights + if(globalInputState & BIT_STEERING_LEFT) { + corneringLeftSignal = true; + corneringRightSignal = false; + corneringRightOffMillis = 0; + } + + if(globalInputState & BIT_STEERING_RIGHT) { + corneringRightSignal = true; + corneringLeftSignal = false; + corneringLeftOffMillis = 0; + } + + if (corneringLeftSignal == true + && !(globalInputState & (BIT_STEERING_LEFT | BIT_TURN_SIGNAL_L)) + ) { + if(corneringLeftOffMillis == 0) { + corneringLeftOffMillis = now; + } + if (corneringLeftOffMillis > 0 && now - corneringLeftOffMillis >= activeEffectsConfig.corneringLightOffDelay) { + corneringLeftSignal = false; + corneringLeftOffMillis = 0; + } + } + + if (corneringRightSignal == true + && !(globalInputState & (BIT_STEERING_RIGHT | BIT_TURN_SIGNAL_R)) + ) { + if(corneringRightOffMillis == 0) { + corneringRightOffMillis = now; + } + if (corneringRightOffMillis > 0 && now - corneringRightOffMillis >= activeEffectsConfig.corneringLightOffDelay) { + corneringRightSignal = false; + corneringRightOffMillis = 0; + } + } +} + +void GlobalEffects::update(uint32_t globalInputState) { + updateTurnIndicators(globalInputState); + updateStrobe(globalInputState); + updateBeacon(globalInputState); + updateFlashToPass(globalInputState); + updateCornering(globalInputState); +}; + +uint16_t GlobalEffects::getActiveEffectMask() { + uint16_t state = 0; + + if(leftTurnSignal) { + state |= BIT_GLOBAL_TURN_L; + } + + if(rightTurnSignal) { + state |= BIT_GLOBAL_TURN_R; + } + + if(strobeSignal) { + state |= BIT_GLOBAL_STROBE; + } + + if(flashToPassSignal) { + state |= BIT_GLOBAL_FLASH_TO_PASS; + } + + if(corneringLeftSignal) { + state |= BIT_GLOBAL_CORNERING_L; + } + + if(corneringRightSignal) { + state |= BIT_GLOBAL_CORNERING_R; + } + + return state; +}; + +uint8_t GlobalEffects::getBeaconPosition(uint8_t position) { + switch (position) { + case 1: + return beacon1position; + case 2: + return beacon2position; + default: + return 0; + } +} diff --git a/src/state-machine/global-effects.h b/src/state-machine/global-effects.h new file mode 100644 index 0000000..915d6ae --- /dev/null +++ b/src/state-machine/global-effects.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include "blink.h" +#include "../config/light-mode.h" +#include "../config/global-effects-config.h" + +class GlobalEffects { + private: + Blink blink; + bool leftTurnSignal; + bool rightTurnSignal; + + bool strobeSignal; + uint32_t strobeStartMillis; + + bool flashToPassSignal; + + bool corneringLeftSignal; + bool corneringRightSignal; + uint32_t corneringLeftOffMillis; + uint32_t corneringRightOffMillis; + + uint8_t beacon1position; + uint32_t beacon1previousMillis; + uint8_t beacon2position; + uint32_t beacon2previousMillis; + + void updateTurnIndicators(uint32_t globalInputState); + void updateStrobe(uint32_t globalInputState); + void updateSingleBeacon(uint16_t beaconSpeed, uint8_t beaconMaxLeds, uint32_t* beaconPreviousMillis, uint8_t* beaconPosition); + void updateBeacon(uint32_t globalInputState); + void updateFlashToPass(uint32_t globalInputState); + void updateCornering(uint32_t globalInputState); + public: + GlobalEffects(); + + void update(uint32_t globalInputState); + uint16_t getActiveEffectMask(); + uint8_t getBeaconPosition(uint8_t position); +}; \ No newline at end of file diff --git a/starterBrightnessAdjustment.cpp b/starterBrightnessAdjustment.cpp deleted file mode 100644 index b002484..0000000 --- a/starterBrightnessAdjustment.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "starterBrightnessAdjustment.h" - -void StarterAdjustedBrightness::setupAdjustmentParameters(uint8_t newDivisor, uint8_t newMultiplier) { - divisor = newDivisor; - multiplier = newMultiplier; -} - -void StarterAdjustedBrightness::setStarterState(bool active) { - isStarterActive = active; -} - -void StarterAdjustedBrightness::configureBrightnessLevels(LightType type, LightModes mode, uint8_t brightness) { - brightnessConfig[type][mode] = brightness; -} - -uint8_t StarterAdjustedBrightness::getBrightnessLevel(LightType type, LightModes mode) { - if(isStarterActive) return brightnessConfig[type][mode] / divisor * multiplier; - - return brightnessConfig[type][mode]; -} \ No newline at end of file diff --git a/starterBrightnessAdjustment.h b/starterBrightnessAdjustment.h deleted file mode 100644 index 4f4012a..0000000 --- a/starterBrightnessAdjustment.h +++ /dev/null @@ -1,62 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#ifndef _STARTER_BRIGHTNESS_ADJUSTMENT_H_ -#define _STARTER_BRIGHTNESS_ADJUSTMENT_H_ - -#include "Arduino.h" - -enum LightType { - PARKING, - BRAKE, - REAR_LEFT_TURN, - REAR_RIGHT_TURN, - FRONT_LEFT_TURN, - FRONT_RIGHT_TURN, - REVERSE, - FOG, - AUX, - LOW_BEAM, - HIGH_BEAM, - //---- - NUM_LIGHT_TYPES -}; - -enum LightModes { - PRIMARY, - SECONDARY, - TERTIARY, - NUM_LIGHT_MODES -}; - - - - - -//Classes -class StarterAdjustedBrightness { - private: - bool isStarterActive; - uint8_t divisor = 5; - uint8_t multiplier = 2; - uint8_t brightnessConfig[NUM_LIGHT_TYPES][NUM_LIGHT_MODES]; - public: - void setupAdjustmentParameters(uint8_t newDivisor, uint8_t newMultiplier); - void configureBrightnessLevels(LightType type, LightModes mode, uint8_t brightness); - void setStarterState(bool active); - uint8_t getBrightnessLevel(LightType type, LightModes mode = PRIMARY); -}; - -#endif \ No newline at end of file diff --git a/truck-multi-function-sbus.ino b/truck-multi-function-sbus.ino new file mode 100644 index 0000000..a2a348e --- /dev/null +++ b/truck-multi-function-sbus.ino @@ -0,0 +1,95 @@ +/************************************ + * truck-multi-function-sbus v2.0.0 + * Date: 31.03.2026 + * + * Copyright (C) 2020-2026 Marina Egner + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + ************************************/ + +/************************************ + * Include Module and Library Files + ************************************/ +#include +#include "src/memory/memory-manager.h" +#include "src/input/sbus-parser.h" +#include "src/input/ppm-parser.h" +#include "src/input/esc-parser.h" +#include "src/state-machine/global-effects.h" +#include "src/output/local-outputs.h" +#include "src/config-interface/json-interface.h" +#include "src/communication/serialCommMaster.h" +#include "src/output/hardware-soft-pwm.h" + +// Initialize the parser using hardware Serial1, RX on pin 16, TX disabled (-1) +SbusParser sbusInput(&Serial1, D3, -1, false); +PpmParser ppmInput(D4); +EscParser escInput(A2, A4); +GlobalEffects globalEffects; +LocalOutputController localOutputController; +JsonInterface jsonUi; +SerialCommMaster serialCommMaster; +// In main.cpp +// Index 0-5: Local Master Board Servos +// Index 6-11: Remote RS485 Bus Servos +// Initialized to 1000 (which represents the 1500µs center position on our 0-2000 scale) +uint16_t globalServoState[12] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000}; + +void setup() { + Serial.begin(115200); + + // Initialize storage and load configs into RAM + memoryManager.begin(); + memoryManager.loadConfig(); + + ppmInput.setMode(activeMainConfig.ppmMode); + + sbusInput.begin(); + ppmInput.begin(); + escInput.begin(); + + HardwareSoftPWM::begin(); + + localOutputController.begin(); + + serialCommMaster.begin(&Serial0, 19200, SERIAL_8N1, 1000, 30, D2); + + uint32_t initialProccessingTime = millis(); + while (millis() - initialProccessingTime < 1000) { + sbusInput.update(false, globalServoState); + } +} + + +void loop() { + jsonUi.update(localOutputController, memoryManager); + bool systemConnected = (sbusInput.isSerialConnected() && !sbusInput.isFailsafeActive() && ppmInput.isPulsePresent()); + + sbusInput.update(systemConnected, globalServoState); + ppmInput.update(systemConnected, globalServoState); + escInput.update(); + + uint32_t globalState = sbusInput.getActiveMask() | + ppmInput.getActiveMask() | + escInput.getActiveMask(); + + + globalEffects.update(globalState); + + uint16_t effectState = globalEffects.getActiveEffectMask(); + uint8_t beacon1pos = globalEffects.getBeaconPosition(1); + uint8_t beacon2pos = globalEffects.getBeaconPosition(2); + + serialCommMaster.update(globalState, effectState, globalServoState); + + localOutputController.update(globalState, effectState, beacon1pos, beacon2pos, globalServoState); +} \ No newline at end of file diff --git a/truckLightAndFunction.ino b/truckLightAndFunction.oldino similarity index 97% rename from truckLightAndFunction.ino rename to truckLightAndFunction.oldino index ce9abe0..379027c 100644 --- a/truckLightAndFunction.ino +++ b/truckLightAndFunction.oldino @@ -30,6 +30,7 @@ #include "starterBrightnessAdjustment.h" #include "debugging.h" // Handles debbuging info #include "serialCommMaster.h" +#include struct MultiswitchChannel { uint16_t channel[8]; diff --git a/vehicleConfig.h b/vehicleConfig.h deleted file mode 100644 index 94988c9..0000000 --- a/vehicleConfig.h +++ /dev/null @@ -1,151 +0,0 @@ -/************************************ - * Copyright (C) 2020-2025 Marina Egner - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - ************************************/ - -#ifndef _VEHICLE_CONFIG_H_ -#define _VEHICLE_CONFIG_H_ - -#include "Arduino.h" - -enum CountryOption { - US, - EU -}; - -enum DebugLevel { - NONE, - STATUS_ONLY, - PPM_CHANNEL1, - PPM_CHANNEL2, - FUNCTION_STATE, - FUNCTION_OUT, - STARTER_DIMMING -}; - -enum ProtocolVersion { - V1 = 1, - V2 -}; - -struct GeneralConfig { - // Setup Region EU or US for Truck - Use `CountryOption` enum - CountryOption countryOption; - // Level for debugging - Use `DebugLevel` enum - DebugLevel debugLevel; - // Pin for status LED on the Arduino - uint8_t statusLightPin; -}; - -struct PPMConfig { - // First PPM Multiswitch Signal from Remote Control - uint8_t pinChannel1; - // Second PPM Multiswitch Signal from Remote Control - uint8_t pinChannel2; - // PPM Servo Signal from Sound Module - uint8_t pinSoundChannel; -}; - -struct GeneralLightConfig { - // Fade in time for all lights - uint16_t fadeOnTime; - // Fade out time for all lights - uint16_t fadeOffTime; - // Starter Brightness decrease factor - uint8_t starterDimmingFactor; - // Starter Brightness decrease multiplier - uint8_t starterDimmingMultiplier; -}; - -struct SerialConfig { - // Enable Serial Communication - bool isEnabled; - // Pin for TX Enable on MAX485 - uint8_t outTxEnablePin; - // Baud rate for Serial Communication (e.g. 19200) - uint32_t baudRate; - // e.g. SERIAL_8N1 | start bit, data bit, stop bit - uint8_t byteFormat; - // Time to wait for a response - long timeout; - // Time between polling requests - long pollingInterval; - // Protocol version - ProtocolVersion protocolVersion; -}; - -struct LightInputChannel { - // Pin for Reverse Signal from External Controller - uint8_t reverseSignal; - // Pin for Brake Signal from External Controller - uint8_t brakeSignal; -}; - -struct LightOutputChannel { - // Hardware Pin on the Arduino for the Light Output Channel - uint8_t outputPin; - // Brightness level for Primary Light Function - uint8_t primaryOnBrightness; - // Brightness level for Secondary Light Function - uint8_t secondaryOnBrightness; - // Brightness level for Tertiary Light Function - uint8_t tertiaryOnBrightness; - // Brightness level for Off Light Function - uint8_t offBrightness; - // Fade in time for all lights - uint16_t fadeOnTime; - // Fade out time for all lights - uint16_t fadeOffTime; -}; - -struct LowBeamConfig { - // Low Beam also functions as Parking Light (tertiary ) - bool isParkingLight; - // Low Beam also functions as High Beam (primary ) - bool isHighBeam; -}; - -struct HighBeamConfig { - // Frequency for High Beam Flashing functionality - uint16_t flashFrequency; -}; - -struct TurnSignalConfig { - // Frequency for Turn Signal - uint16_t flashFrequency; -}; - - -struct VehicleConfig { - GeneralConfig generalConfig; - PPMConfig ppmConfig; - GeneralLightConfig generalLightConfig; - SerialConfig serialConfig; - LightInputChannel lightInputChannel; - LightOutputChannel rearLeftTurnLight; - LightOutputChannel rearRightTurnLight; - LightOutputChannel frontLeftTurnLight; - LightOutputChannel frontRightTurnLight; - LightOutputChannel lowBeamLight; - LightOutputChannel highBeamLight; - LightOutputChannel parkingLight; - LightOutputChannel fogLight; - LightOutputChannel reverseLight; - LightOutputChannel brakeLight; - LightOutputChannel auxLight; - LowBeamConfig lowBeamConfig; - HighBeamConfig highBeamConfig; - TurnSignalConfig turnSignalConfig; -}; - -#endif \ No newline at end of file