diff --git a/.gitignore b/.gitignore index eebd94ef9..55e90a8f2 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,10 @@ data/boot/logo.* managed_components/* arduino-lib-builder* dependencies.lock + +# JLink / RTT debug artifacts (nRF SoCs) +flash.jlink +rtt_*.txt idf_component.yml CMakeLists.txt /sdkconfig.* diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index a36974327..88b0f51d4 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,11 +4,11 @@ cli: plugins: sources: - id: trunk - ref: v1.9.0 + ref: v1.10.0 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.528 + - checkov@3.2.529 - renovate@43.150.0 - prettier@3.8.3 - trufflehog@3.95.3 @@ -16,7 +16,7 @@ lint: - bandit@1.9.4 - trivy@0.70.0 - taplo@0.10.0 - - ruff@0.15.12 + - ruff@0.15.13 - isort@8.0.1 - markdownlint@0.48.0 - oxipng@10.1.1 @@ -26,7 +26,7 @@ lint: - hadolint@2.14.0 - shfmt@3.6.0 - shellcheck@0.11.0 - - black@26.3.1 + - black@26.5.1 - git-diff-check - gitleaks@8.30.1 - clang-format@16.0.3 diff --git a/bin/build-esp32.sh b/bin/build-esp32.sh index d07a09a16..4e799b30a 100755 --- a/bin/build-esp32.sh +++ b/bin/build-esp32.sh @@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/ cp bin/device-update.* $OUTDIR/ echo "Copying manifest" -cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true +cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index b75c66624..f1946770c 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -293,9 +293,12 @@ if ("HAS_TFT", 1) in env.get("CPPDEFINES", []): board_arch = infer_architecture(env.BoardConfig()) should_skip_manifest = board_arch is None -# For host/native envs, avoid depending on 'buildprog' (some targets don't define it) -mtjson_deps = [] if should_skip_manifest else ["buildprog"] -if not should_skip_manifest and platform.name == "espressif32": +# Most platforms can generate the manifest as part of the default 'buildprog' target. +# Typically this passes success/failure properly. +mtjson_deps = ["buildprog"] +if platform.name == "espressif32": + # On ESP32, we need to explicitly depend upon the binary to prevent fake-success upon failure. + mtjson_deps = ["$BUILD_DIR/${PROGNAME}.bin"] # Build littlefs image as part of mtjson target # Equivalent to `pio run -t buildfs` target_lfs = env.DataToBin( @@ -309,7 +312,8 @@ if should_skip_manifest: env.AddCustomTarget( name="mtjson", - dependencies=mtjson_deps, + # For host/native envs, avoid depending on 'buildprog' (some targets don't define it) + dependencies=[], actions=[skip_manifest], title="Meshtastic Manifest (skipped)", description="mtjson generation is skipped for native environments", diff --git a/boards/CDEBYTE_EoRa-S3.json b/boards/CDEBYTE_EoRa-S3.json index afaabc5a7..2355cecd3 100644 --- a/boards/CDEBYTE_EoRa-S3.json +++ b/boards/CDEBYTE_EoRa-S3.json @@ -7,7 +7,7 @@ "extra_flags": [ "-D CDEBYTE_EORA_S3", "-D ARDUINO_USB_CDC_ON_BOOT=1", - "-D ARDUINO_USB_MODE=0", + "-D ARDUINO_USB_MODE=1", "-D ARDUINO_RUNNING_CORE=1", "-D ARDUINO_EVENT_RUNNING_CORE=1", "-D BOARD_HAS_PSRAM" diff --git a/boards/bpi_picow_esp32_s3.json b/boards/bpi_picow_esp32_s3.json index 75983d845..62ad666f1 100644 --- a/boards/bpi_picow_esp32_s3.json +++ b/boards/bpi_picow_esp32_s3.json @@ -6,7 +6,7 @@ "core": "esp32", "extra_flags": [ "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1", "-DBOARD_HAS_PSRAM" diff --git a/boards/hackaday-communicator.json b/boards/hackaday-communicator.json index 6e6c1ad2d..5aedf5d19 100644 --- a/boards/hackaday-communicator.json +++ b/boards/hackaday-communicator.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json index 152515cf3..d9d5f8582 100644 --- a/boards/heltec_vision_master_e213.json +++ b/boards/heltec_vision_master_e213.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_vision_master_e290.json b/boards/heltec_vision_master_e290.json index b7cbac878..171125338 100644 --- a/boards/heltec_vision_master_e290.json +++ b/boards/heltec_vision_master_e290.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_vision_master_t190.json b/boards/heltec_vision_master_t190.json index 440f76ad0..fbdf1f09d 100644 --- a/boards/heltec_vision_master_t190.json +++ b/boards/heltec_vision_master_t190.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_wireless_tracker.json b/boards/heltec_wireless_tracker.json index 04c6e5553..59d0daa15 100644 --- a/boards/heltec_wireless_tracker.json +++ b/boards/heltec_wireless_tracker.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DHELTEC_WIRELESS_TRACKER", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/heltec_wireless_tracker_v2.json b/boards/heltec_wireless_tracker_v2.json index 502954e69..3d20f7edb 100644 --- a/boards/heltec_wireless_tracker_v2.json +++ b/boards/heltec_wireless_tracker_v2.json @@ -7,7 +7,7 @@ "core": "esp32", "extra_flags": [ "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/nrf54l15dk.json b/boards/nrf54l15dk.json new file mode 100644 index 000000000..863ad290b --- /dev/null +++ b/boards/nrf54l15dk.json @@ -0,0 +1,26 @@ +{ + "build": { + "cpu": "cortex-m33", + "f_cpu": "128000000L", + "mcu": "nrf54l15", + "zephyr": { + "variant": "nrf54l15dk/nrf54l15/cpuapp" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "default_tools": ["jlink"], + "jlink_device": "nRF54L15_M33", + "svd_path": "nrf54l15.svd" + }, + "frameworks": ["zephyr"], + "name": "Nordic nRF54L15-DK (PCA10156)", + "upload": { + "maximum_ram_size": 262144, + "maximum_size": 1572864, + "protocol": "jlink", + "protocols": ["jlink"] + }, + "url": "https://www.nordicsemi.com/Products/nRF54L15", + "vendor": "Nordic Semiconductor" +} diff --git a/boards/seeed-xiao-s3.json b/boards/seeed-xiao-s3.json index 6981085dd..d8e5d6b94 100644 --- a/boards/seeed-xiao-s3.json +++ b/boards/seeed-xiao-s3.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=0" ], diff --git a/boards/t-beam-1w.json b/boards/t-beam-1w.json index 40f16195d..80776ee05 100644 --- a/boards/t-beam-1w.json +++ b/boards/t-beam-1w.json @@ -9,7 +9,7 @@ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_1W", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/t-deck.json b/boards/t-deck.json index b112921b9..33a34b60d 100644 --- a/boards/t-deck.json +++ b/boards/t-deck.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json index 7bda2e5a0..8d2c3eed6 100644 --- a/boards/tbeam-s3-core.json +++ b/boards/tbeam-s3-core.json @@ -8,7 +8,7 @@ "-DBOARD_HAS_PSRAM", "-DLILYGO_TBEAM_S3_CORE", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/boards/tlora-t3s3-v1.json b/boards/tlora-t3s3-v1.json index 0bfd17afc..652b4178e 100644 --- a/boards/tlora-t3s3-v1.json +++ b/boards/tlora-t3s3-v1.json @@ -7,7 +7,7 @@ "extra_flags": [ "-DLILYGO_T3S3_V1", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1", "-DBOARD_HAS_PSRAM" diff --git a/boards/unphone.json b/boards/unphone.json index 4d37f7bb5..72075f5ae 100644 --- a/boards/unphone.json +++ b/boards/unphone.json @@ -10,7 +10,7 @@ "-DBOARD_HAS_PSRAM", "-DUNPHONE_SPIN=9", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], diff --git a/default_16MB.csv b/default_16MB.csv new file mode 100644 index 000000000..67d773728 --- /dev/null +++ b/default_16MB.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x640000, +app1, app, ota_1, 0x650000,0x640000, +spiffs, data, spiffs, 0xc90000,0x360000, +coredump, data, coredump,0xFF0000,0x10000, diff --git a/default_8MB.csv b/default_8MB.csv new file mode 100644 index 000000000..4e92afa69 --- /dev/null +++ b/default_8MB.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x330000, +app1, app, ota_1, 0x340000,0x330000, +spiffs, data, spiffs, 0x670000,0x180000, +coredump, data, coredump,0x7F0000,0x10000, diff --git a/extra_scripts/esp32_extra.py b/extra_scripts/esp32_extra.py index f7698561a..975ec0f30 100755 --- a/extra_scripts/esp32_extra.py +++ b/extra_scripts/esp32_extra.py @@ -70,17 +70,6 @@ def esp32_create_combined_bin(source, target, env): env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) -esp32_kind = env.GetProjectOption("custom_esp32_kind") -if esp32_kind == "esp32": - # Free up some IRAM by removing auxiliary SPI flash chip drivers. - # Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c. - env.Append( - LINKFLAGS=[ - "-Wl,--wrap=esp_flash_chip_gd", - "-Wl,--wrap=esp_flash_chip_issi", - "-Wl,--wrap=esp_flash_chip_winbond", - ] - ) -else: - # For newer ESP32 targets, using newlib nano works better. - env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) +# Enable Newlib Nano formatting to save space +# ...but allow printf float support (compromise) +env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) diff --git a/extra_scripts/ld_response_file.py b/extra_scripts/ld_response_file.py new file mode 100644 index 000000000..e79475f19 --- /dev/null +++ b/extra_scripts/ld_response_file.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports + +# force linker response file instead of command line arguments + +Import("env") + + +def wrap_with_tempfile(command_key): + command = env.get(command_key) + if not command or not isinstance(command, str): + return + if "TEMPFILE(" in command: + return + env.Replace(**{command_key: "${TEMPFILE('%s')}" % command}) + + +# Force SCons to spill long commands into response files on this target. +env.Replace(MAXLINELENGTH=8192) + +for key in ("LINKCOM", "CXXLINKCOM", "SHLINKCOM", "SHCXXLINKCOM"): + wrap_with_tempfile(key) diff --git a/extra_scripts/nrf54l15_linker.py b/extra_scripts/nrf54l15_linker.py new file mode 100644 index 000000000..fa1041553 --- /dev/null +++ b/extra_scripts/nrf54l15_linker.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# trunk-ignore-all(ruff/F821) +# trunk-ignore-all(flake8/F821): For SConstruct imports +# +# post:extra_scripts/nrf54l15_linker.py +# +# Fix for Zephyr two-pass link on nRF54L15: +# platformio-build.py registers env.Depends("$PROG_PATH", final_ld_script) but +# the SCons dependency chain is broken (final_ld_script Command never runs). +# This script adds a PreAction on the final firmware binary that runs the gcc +# preprocessing command directly (extracted from build.ninja) to generate +# zephyr/linker.cmd before the link step. +# +# PlatformIO bundles an old Ninja that can't handle multi-output depslog rules, +# so we parse the COMMAND line from build.ninja and run just the gcc -E part, +# skipping the cmake_transform_depfile step (only needed for Ninja deps tracking). + +import os +import re +import subprocess + +Import("env") + +if env.get("PIOENV") != "nrf54l15dk": + pass # Only for the nrf54l15dk environment +else: + + def _extract_gcc_command(ninja_build): + """Parse build.ninja to find the gcc -E command that generates linker.cmd. + + The rule format depends on the host: + Windows (CMake's RunCMake wraps every command): + COMMAND = cmd.exe /C "cd /D DIR && arm-none-eabi-gcc.exe ... -o linker.cmd && cmake.exe -E cmake_transform_depfile ..." + POSIX (Linux/macOS — no wrapper): + COMMAND = cd DIR && arm-none-eabi-gcc ... -o linker.cmd && cmake -E cmake_transform_depfile ... + + Returns (gcc_cmd_string, cwd_path) or raises RuntimeError. + """ + in_rule = False + with open(ninja_build, "r", encoding="utf-8", errors="replace") as f: + for line in f: + # Detect start of the linker.cmd custom command rule + if not in_rule: + if "build zephyr/linker.cmd" in line and "CUSTOM_COMMAND" in line: + in_rule = True + continue + + stripped = line.strip() + if not stripped.startswith("COMMAND = "): + continue + + command_val = stripped[len("COMMAND = ") :] + + # On Windows the value is wrapped in `cmd.exe /C "..."` — strip + # the wrapper. On POSIX hosts the inner sequence is the value + # itself (no quoting layer). + m = re.search(r'/C\s+"(.*)"\s*$', command_val) + inner = m.group(1) if m else command_val + parts = inner.split(" && ") + + cwd = None + gcc_cmd = None + for part in parts: + part = part.strip() + if part.startswith("cd /D "): # Windows form + cwd = part[len("cd /D ") :] + elif part.startswith("cd "): # POSIX form + cwd = part[len("cd ") :] + elif "arm-none-eabi-gcc" in part: + gcc_cmd = part + + if not gcc_cmd: + raise RuntimeError( + "nRF54L15 linker fix: arm-none-eabi-gcc command not found in:\n%s" + % inner[:400] + ) + + return gcc_cmd, cwd + + raise RuntimeError( + "nRF54L15 linker fix: 'build zephyr/linker.cmd' rule not found in build.ninja" + ) + + def _generate_linker_cmd(target, source, env): + """Generate zephyr/linker.cmd via direct gcc invocation before the final link.""" + build_dir = env.subst("$BUILD_DIR") + zephyr_dir = os.path.join(build_dir, "zephyr") + linker_cmd = os.path.join(zephyr_dir, "linker.cmd") + + if os.path.exists(linker_cmd): + return # Already present — nothing to do + + ninja_build = os.path.join(build_dir, "build.ninja") + if not os.path.exists(ninja_build): + raise RuntimeError( + "nRF54L15 linker fix: build.ninja not found at %s\n" + "Run a full build first so CMake generates the Ninja files." + % ninja_build + ) + + gcc_cmd, cwd = _extract_gcc_command(ninja_build) + run_cwd = cwd if cwd else zephyr_dir + + print( + "==> nRF54L15: Generating zephyr/linker.cmd (LINKER_ZEPHYR_FINAL) via GCC" + ) + # gcc_cmd comes verbatim from our own build.ninja (never user input) and + # contains Windows-style paths with spaces that cannot be safely argv-split + # with shlex, so we run it via the platform shell. nosec/nosemgrep below + # acknowledge this deliberate, scoped use of shell=True. + result = subprocess.run( # nosec B602 + gcc_cmd, + shell=True, # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true + cwd=run_cwd, + capture_output=True, + text=True, + ) + if result.returncode != 0: + print("GCC stdout:", result.stdout[:2000]) + print("GCC stderr:", result.stderr[:2000]) + raise RuntimeError( + "nRF54L15 linker fix: GCC failed to generate linker.cmd (rc=%d)" + % result.returncode + ) + if not os.path.exists(linker_cmd): + raise RuntimeError( + "nRF54L15 linker fix: GCC returned 0 but linker.cmd was not created at %s" + % linker_cmd + ) + print("==> linker.cmd generated successfully") + + # Use PIOMAINPROG (set by ZephyrBuildProgram) to get the exact SCons node + prog = env.get("PIOMAINPROG") + if prog: + env.AddPreAction(prog, _generate_linker_cmd) + else: + print( + "[nrf54l15_linker] WARNING: PIOMAINPROG not set, falling back to $PROG_PATH" + ) + env.AddPreAction(env.subst("$PROG_PATH"), _generate_linker_cmd) diff --git a/platformio.ini b/platformio.ini index db689d8bb..fd25bfa59 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,7 +2,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +default_envs = heltec-v3 extra_configs = variants/*/*.ini @@ -17,6 +17,7 @@ test_build_src = true extra_scripts = pre:bin/platformio-pre.py bin/platformio-custom.py + post:extra_scripts/nrf54l15_linker.py ; note: we add src to our include search path so that lmic_project_config can override ; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile ; of code is a heap corruption bug! diff --git a/protobufs b/protobufs index 7ffb4bb60..e978a1850 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 7ffb4bb60ded743a1ce23fe2edd5ead32be52bbb +Subproject commit e978a1850b905e05913c6ef6c73c1d3b79486d4a diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h index a78dde78e..f8ab28a5e 100644 --- a/src/DebugConfiguration.h +++ b/src/DebugConfiguration.h @@ -158,9 +158,8 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...); #include #endif // HAS_ETHERNET -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #if HAS_WIFI diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp index 8fafc6c52..a229dcbfc 100644 --- a/src/FSCommon.cpp +++ b/src/FSCommon.cpp @@ -277,7 +277,7 @@ void fsInit() */ void setupSDCard() { -#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) +#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC) concurrency::LockGuard g(spiLock); SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI); if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) { diff --git a/src/FSCommon.h b/src/FSCommon.h index 9fe71e47b..e2e77da9a 100644 --- a/src/FSCommon.h +++ b/src/FSCommon.h @@ -48,6 +48,14 @@ using namespace STM32_LittleFS_Namespace; using namespace Adafruit_LittleFS_Namespace; #endif +#if defined(ARCH_NRF54L15) +// nRF54L15 — Zephyr LittleFS on 36 KB storage_partition (internal RRAM) +#include "InternalFileSystem.h" +#define FSCom InternalFS +#define FSBegin() FSCom.begin() +using namespace Adafruit_LittleFS_Namespace; +#endif + void fsInit(); void fsListFiles(); bool copyFile(const char *from, const char *to); diff --git a/src/Power.cpp b/src/Power.cpp index 0318dd9f3..43e9f1059 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -24,6 +24,13 @@ #include "meshUtils.h" #include "power/PowerHAL.h" #include "sleep.h" +#ifdef ARCH_ESP32 +// #include +#include +#include +#include +#include +#endif #if defined(ARCH_PORTDUINO) #include "api/WiFiServerAPI.h" @@ -63,9 +70,8 @@ #include #endif -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #endif @@ -77,21 +83,86 @@ #if defined(BATTERY_PIN) && defined(ARCH_ESP32) #ifndef BAT_MEASURE_ADC_UNIT // ADC1 is default -static const adc1_channel_t adc_channel = ADC_CHANNEL; +static const adc_channel_t adc_channel = ADC_CHANNEL; static const adc_unit_t unit = ADC_UNIT_1; -#else // ADC2 -static const adc2_channel_t adc_channel = ADC_CHANNEL; +#else // ADC2 +static const adc_channel_t adc_channel = ADC_CHANNEL; static const adc_unit_t unit = ADC_UNIT_2; -RTC_NOINIT_ATTR uint64_t RTC_reg_b; - #endif // BAT_MEASURE_ADC_UNIT -esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t)); +static adc_oneshot_unit_handle_t adc_handle = nullptr; +static adc_cali_handle_t adc_cali_handle = nullptr; +static bool adc_calibrated = false; #ifndef ADC_ATTENUATION static const adc_atten_t atten = ADC_ATTEN_DB_12; #else static const adc_atten_t atten = ADC_ATTENUATION; #endif +#ifdef ADC_BITWIDTH +static const adc_bitwidth_t adc_width = ADC_BITWIDTH; +#else +static const adc_bitwidth_t adc_width = ADC_BITWIDTH_DEFAULT; +#endif + +static int adcBitWidthToBits(adc_bitwidth_t width) +{ + switch (width) { + case ADC_BITWIDTH_9: + return 9; + case ADC_BITWIDTH_10: + return 10; + case ADC_BITWIDTH_11: + return 11; + case ADC_BITWIDTH_12: + return 12; +#ifdef ADC_BITWIDTH_13 + case ADC_BITWIDTH_13: + return 13; +#endif + default: + return 12; + } +} + +static bool initAdcCalibration() +{ +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = adc_width, + }; + esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle); + if (ret == ESP_OK) { + LOG_INFO("ADC calibration: curve fitting enabled"); + return true; + } + if (ret != ESP_ERR_NOT_SUPPORTED) { + LOG_WARN("ADC calibration: curve fitting failed: %s", esp_err_to_name(ret)); + } +#endif + +#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_line_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = adc_width, + .default_vref = DEFAULT_VREF, + }; + esp_err_t ret = adc_cali_create_scheme_line_fitting(&cali_config, &adc_cali_handle); + if (ret == ESP_OK) { + LOG_INFO("ADC calibration: line fitting enabled"); + return true; + } + if (ret != ESP_ERR_NOT_SUPPORTED) { + LOG_WARN("ADC calibration: line fitting failed: %s", esp_err_to_name(ret)); + } +#endif + + LOG_INFO("ADC calibration not supported; using approximate scaling"); + return false; +} + #endif // BATTERY_PIN && ARCH_ESP32 #ifdef EXT_PWR_DETECT @@ -367,8 +438,20 @@ class AnalogBatteryLevel : public HasBatteryLevel scaled *= operativeAdcMultiplier; #elif defined(ARCH_ESP32) // ADC block for espressif platforms raw = espAdcRead(); - scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); - scaled *= operativeAdcMultiplier; + int voltage_mv = 0; + if (adc_calibrated && adc_cali_handle) { + if (adc_cali_raw_to_voltage(adc_cali_handle, raw, &voltage_mv) != ESP_OK) { + LOG_WARN("ADC calibration read failed; using raw value"); + voltage_mv = 0; + } + } + if (voltage_mv == 0) { + // Fallback approximate conversion without calibration + const int bits = adcBitWidthToBits(adc_width); + const float max_code = powf(2.0f, bits) - 1.0f; + voltage_mv = (int)((raw / max_code) * DEFAULT_VREF); + } + scaled = voltage_mv * operativeAdcMultiplier; #else // block for all other platforms #ifdef ARCH_NRF52 concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock); @@ -410,51 +493,22 @@ class AnalogBatteryLevel : public HasBatteryLevel uint32_t raw = 0; uint8_t raw_c = 0; // raw reading counter -#ifndef BAT_MEASURE_ADC_UNIT // ADC1 + if (!adc_handle) { + LOG_ERROR("ADC oneshot handle not initialized"); + return 0; + } + for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { - int val_ = adc1_get_raw(adc_channel); - if (val_ >= 0) { // save only valid readings - raw += val_; + int val = 0; + esp_err_t err = adc_oneshot_read(adc_handle, adc_channel, &val); + if (err == ESP_OK) { + raw += val; raw_c++; - } - // delayMicroseconds(100); - } -#else // ADC2 -#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 - // ADC2 wifi bug workaround not required, breaks compile - // On ESP32S3, ADC2 can take turns with Wifi (?) - - int32_t adc_buf; - esp_err_t read_result; - - // Multiple samples - for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { - adc_buf = 0; - read_result = -1; - - read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf); - if (read_result == ESP_OK) { - raw += adc_buf; - raw_c++; // Count valid samples } else { - LOG_DEBUG("An attempt to sample ADC2 failed"); + LOG_DEBUG("ADC read failed: %s", esp_err_to_name(err)); } } -#else // Other ESP32 - int32_t adc_buf = 0; - for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) { - // ADC2 wifi bug workaround, see - // https://github.com/espressif/arduino-esp32/issues/102 - WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b); - SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV); - adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf); - raw += adc_buf; - raw_c++; - } -#endif // BAT_MEASURE_ADC_UNIT - -#endif // End BAT_MEASURE_ADC_UNIT return (raw / (raw_c < 1 ? 1 : raw_c)); } #endif @@ -666,42 +720,31 @@ bool Power::analogInit() #ifdef ARCH_STM32WL analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); #elif defined(ARCH_ESP32) // ESP32 needs special analog stuff + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = unit, + }; -#ifndef ADC_WIDTH // max resolution by default - static const adc_bits_width_t width = ADC_WIDTH_BIT_12; -#else - static const adc_bits_width_t width = ADC_WIDTH; -#endif -#ifndef BAT_MEASURE_ADC_UNIT // ADC1 - adc1_config_width(width); - adc1_config_channel_atten(adc_channel, atten); -#else // ADC2 - adc2_config_channel_atten(adc_channel, atten); -#ifndef CONFIG_IDF_TARGET_ESP32S3 - // ADC2 wifi bug workaround - // Not required with ESP32S3, breaks compile - RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG); -#endif -#endif - // calibrate ADC - esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs); - // show ADC characterization base - if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { - LOG_INFO("ADC config based on Two Point values stored in eFuse"); - } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { - LOG_INFO("ADC config based on reference voltage stored in eFuse"); + if (!adc_handle) { + esp_err_t err = adc_oneshot_new_unit(&init_config, &adc_handle); + if (err != ESP_OK) { + LOG_ERROR("ADC oneshot init failed: %s", esp_err_to_name(err)); + return false; + } } -#ifdef CONFIG_IDF_TARGET_ESP32S3 - // ESP32S3 - else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) { - LOG_INFO("ADC config based on Two Point values and fitting curve " - "coefficients stored in eFuse"); + + adc_oneshot_chan_cfg_t chan_cfg = { + .atten = atten, + .bitwidth = adc_width, + }; + + esp_err_t err = adc_oneshot_config_channel(adc_handle, adc_channel, &chan_cfg); + if (err != ESP_OK) { + LOG_ERROR("ADC channel config failed: %s", esp_err_to_name(err)); + return false; } -#endif - else { - LOG_INFO("ADC config based on default reference voltage"); - } -#endif // ARCH_ESP32 + + adc_calibrated = initAdcCalibration(); +#endif // ARCH_ESP32 // NRF52 ADC init moved to powerHAL_init in nrf52 platform diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 2d6cc13ec..e887f01ee 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -225,6 +225,8 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected(); #elif defined(ARCH_NRF52) isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); +#elif defined(ARCH_NRF54L15) + isBleConnected = nrf54l15Bluetooth != nullptr && nrf54l15Bluetooth->isConnected(); #endif if (isBleConnected) { auto thread = concurrency::OSThread::currentThread; @@ -241,6 +243,8 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ nimbleBluetooth->sendLog(buffer.get(), size); #elif defined(ARCH_NRF52) nrf52Bluetooth->sendLog(buffer.get(), size); +#elif defined(ARCH_NRF54L15) + nrf54l15Bluetooth->sendLog(buffer.get(), size); #endif } } diff --git a/src/configuration.h b/src/configuration.h index d263d9ae1..a31198346 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -561,5 +561,9 @@ along with this program. If not, see . #define HAS_SCREEN 0 #endif +#ifndef USE_ETHERNET_DEFAULT +#define USE_ETHERNET_DEFAULT 0 +#endif + #include "DebugConfiguration.h" #include "RF95Configuration.h" diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 044db3637..4de23bce7 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -425,6 +425,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O #elif defined(USE_SSD1306) dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); +#if defined(OLED_Y_OFFSET_PAGES) + // Panels whose active window does not start at GDDRAM row 0 (e.g. 72x40 + // modules on pages 3..7) need a fixed vertical page shift on every write. + static_cast(dispdev)->setYOffset(OLED_Y_OFFSET_PAGES); +#endif #elif defined(USE_SPISSD1306) dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48); if (!dispdev->init()) { @@ -910,7 +915,7 @@ int32_t Screen::runOnce() #ifndef DISABLE_WELCOME_UNSET if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) menuHandler::LoraRegionPicker(); #else menuHandler::OnboardMessage(); @@ -1147,7 +1152,7 @@ void Screen::setFrames(FrameFocus focus) #if defined(DISPLAY_CLOCK_FRAME) if (!hiddenFrames.clock) { fsi.positions.clock = numframes; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame; #else normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame @@ -1606,7 +1611,7 @@ void Screen::showFrame(FrameDirection direction) void Screen::setFastFramerate() { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) dispdev->clear(); #if GRAPHICS_TFT_COLORING_ENABLED prepareFrameColorRegions(); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 82ceb5406..624465f33 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -106,7 +106,7 @@ #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 -#elif defined(M5STACK_UNITC6L) +#elif defined(OLED_TINY) #define FONT_SMALL FONT_SMALL_LOCAL // Height: 13 #define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13 #define FONT_LARGE FONT_SMALL_LOCAL // Height: 13 diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index becd3e75d..691a6fc58 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -31,6 +31,12 @@ ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenw return ScreenResolution::UltraLow; } +#ifdef DISPLAY_FORCE_SMALL_FONTS + if (screenwidth <= 160 && screenheight <= 80) { + return ScreenResolution::Low; + } +#endif + // Standard OLED screens if (screenwidth > 128 && screenheight <= 64) { return ScreenResolution::Low; @@ -240,7 +246,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti int batteryX = 1; int batteryY = HEADER_OFFSET_Y + 1; -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Battery Icons === if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging batteryX += 1; diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 67136437a..0f8198254 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -460,7 +460,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], frequencyslot); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Fifth Row: Channel Utilization === const char *chUtil = "ChUtil:"; char chUtilPercentage[10]; @@ -592,7 +592,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x // Label display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawString(labelX, getTextPositions(display)[line], label); -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // Bar int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2; display->setColor(WHITE); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 386a4c077..9fc7fa410 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -555,7 +555,7 @@ void menuHandler::TZPicker() void menuHandler::clockMenu() { -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static const char *optionsArray[] = {"Back", "Time Format", "Timezone"}; #else static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"}; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index d0b027c13..dfe6671c8 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -26,7 +26,7 @@ extern bool haveGlyphs(const char *str); // Global screen instance extern graphics::Screen *screen; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static uint32_t lastSwitchTime = 0; #endif namespace graphics @@ -788,7 +788,7 @@ void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state unsigned long now = millis(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); if (now - lastSwitchTime >= 3000) { display->display(); @@ -824,7 +824,7 @@ void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *st unsigned long now = millis(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); if (now - lastSwitchTime >= 3000) { display->display(); @@ -894,7 +894,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, double lat = DegD(ourSelfPos.latitude_i); double lon = DegD(ourSelfPos.longitude_i); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->clear(); uint32_t now = millis(); if (now - lastSwitchTime >= 2000) { diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index d7eba0be4..02bfec31f 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -728,7 +728,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay } int16_t boxTop = (display->height() / 2) - (boxHeight / 2); boxHeight += (currentResolution == ScreenResolution::High) ? 2 : 1; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) if (visibleTotalLines == 1) { boxTop += 25; } diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index cfde10124..e7b00e5d5 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -25,7 +25,7 @@ // External variables extern graphics::Screen *screen; -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) static uint32_t lastSwitchTime = 0; #endif namespace graphics @@ -753,7 +753,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat if (!node || node->num == nodeDB->getNodeNum() || !nodeInfoLiteIsFavorite(node)) return; display->clear(); -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) uint32_t now = millis(); if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒 { @@ -959,7 +959,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat if (seenStr[0]) { display->drawString(x, getTextPositions(display)[line++], seenStr); } -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; meshtastic_DeviceMetrics nodeMetrics; @@ -1172,7 +1172,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta } #endif -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) line += 1; // === Node Identity === @@ -1456,7 +1456,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED // needs to be drawn relative to x and y // draw centered icon left to right and centered above the one line of app text -#if defined(M5STACK_UNITC6L) +#if defined(OLED_TINY) display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits); if (gBootSplashBoldPass) { display->drawXbm(x + (SCREEN_WIDTH - 50) / 2 + 1, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits); @@ -1666,7 +1666,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU } display->drawString(x, textPos[line++], altitudeLine); } -#if !defined(M5STACK_UNITC6L) +#if !defined(OLED_TINY) // === Draw Compass === if (validHeading || statusLine1) { // --- Compass Rendering: landscape (wide) screens use original side-aligned logic --- diff --git a/src/graphics/images.h b/src/graphics/images.h index 66fcbc79c..f11ad5686 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -318,7 +318,7 @@ const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f}; #define connection_icon_height 5 const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36}; -#ifdef M5STACK_UNITC6L +#ifdef OLED_TINY #include "img/icon_small.xbm" #else #include "img/icon.xbm" diff --git a/src/graphics/niche/InkHUD/MessageStore.cpp b/src/graphics/niche/InkHUD/MessageStore.cpp index 44a1ef633..671f38057 100644 --- a/src/graphics/niche/InkHUD/MessageStore.cpp +++ b/src/graphics/niche/InkHUD/MessageStore.cpp @@ -53,10 +53,11 @@ void InkHUD::MessageStore::saveToFlash() f.write(reinterpret_cast(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes f.write(reinterpret_cast(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes f.write(reinterpret_cast(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte - f.write(reinterpret_cast(m.text.c_str()), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text - f.write('\0'); // Append null term - LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast(i), min(MAX_MESSAGE_SIZE, m.text.size()), - m.text.c_str()); + f.write(reinterpret_cast(m.text.c_str()), + min((size_t)MAX_MESSAGE_SIZE, m.text.size())); // Write message text + f.write('\0'); // Append null term + LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast(i), + min((size_t)MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str()); } // Release firmware's SPI lock, because SafeFile::close needs it diff --git a/src/input/ExpressLRSFiveWay.h b/src/input/ExpressLRSFiveWay.h index 7c7f210f8..e98031710 100644 --- a/src/input/ExpressLRSFiveWay.h +++ b/src/input/ExpressLRSFiveWay.h @@ -13,6 +13,8 @@ #ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE +// REVISIT esp_adc_cal.h +// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h" #include #include diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp index 4efa5d6e2..3b7e45d18 100644 --- a/src/input/TLoraPagerKeyboard.cpp +++ b/src/input/TLoraPagerKeyboard.cpp @@ -68,7 +68,7 @@ TLoraPagerKeyboard::TLoraPagerKeyboard() : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); #else ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); @@ -108,7 +108,7 @@ void TLoraPagerKeyboard::setBacklight(bool on) uint32_t _brightness = 0; if (on) _brightness = brightness; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) ledcWrite(KB_BL_PIN, _brightness); #else ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness); diff --git a/src/main.cpp b/src/main.cpp index 28842734c..979517f0a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,9 @@ #if !MESHTASTIC_EXCLUDE_GPS #include "GPS.h" #endif +#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/InputBroker.h" +#endif #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -59,6 +62,12 @@ NimbleBluetooth *nimbleBluetooth = nullptr; NRF52Bluetooth *nrf52Bluetooth = nullptr; #endif +#ifdef ARCH_NRF54L15 +void nrf54l15Setup(); +void nrf54l15Loop(); +NRF54L15Bluetooth *nrf54l15Bluetooth = nullptr; +#endif + #if HAS_WIFI || defined(USE_WS5500) || defined(USE_CH390D) #include "mesh/api/WiFiServerAPI.h" #include "mesh/wifi/WiFiAPClient.h" @@ -696,6 +705,9 @@ void setup() #ifdef ARCH_NRF52 nrf52Setup(); #endif +#ifdef ARCH_NRF54L15 + nrf54l15Setup(); +#endif #ifdef ARCH_RP2040 rp2040Setup(); @@ -741,6 +753,11 @@ void setup() } } #endif +#ifdef OLED_GEOMETRY_OVERRIDE + // Per-variant geometry (e.g. 72x40 micro-OLEDs). Takes precedence over the + // default GEOMETRY_128_64 set at the top of setup(). + screen_geometry = OLED_GEOMETRY_OVERRIDE; +#endif #endif #if !MESHTASTIC_EXCLUDE_I2C @@ -1126,6 +1143,9 @@ void loop() #endif #ifdef ARCH_NRF52 nrf52Loop(); +#endif +#ifdef ARCH_NRF54L15 + nrf54l15Loop(); #endif power->powerCommandsCheck(); diff --git a/src/main.h b/src/main.h index 56f048134..058a2aebc 100644 --- a/src/main.h +++ b/src/main.h @@ -19,6 +19,10 @@ extern NimbleBluetooth *nimbleBluetooth; #include "NRF52Bluetooth.h" extern NRF52Bluetooth *nrf52Bluetooth; #endif +#ifdef ARCH_NRF54L15 +#include "NRF54L15Bluetooth.h" +extern NRF54L15Bluetooth *nrf54l15Bluetooth; +#endif #if !MESHTASTIC_EXCLUDE_I2C #include "detect/ScanI2CTwoWire.h" #endif diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 1583567fe..be75e3d42 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -93,6 +93,23 @@ void Channels::initDefaultLoraConfig() #ifdef USERPREFS_LORACONFIG_CHANNEL_NUM loraConfig.channel_num = USERPREFS_LORACONFIG_CHANNEL_NUM; #endif + + // Apply any hardcoded USERPREFS overrides for custom modem config (e.g. region-locked boards) +#ifdef USERPREFS_LORACONFIG_USE_PRESET + loraConfig.use_preset = USERPREFS_LORACONFIG_USE_PRESET; +#endif +#ifdef USERPREFS_LORACONFIG_BANDWIDTH + loraConfig.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH; +#endif +#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR + loraConfig.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR; +#endif +#ifdef USERPREFS_LORACONFIG_CODING_RATE + loraConfig.coding_rate = USERPREFS_LORACONFIG_CODING_RATE; +#endif +#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY + loraConfig.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY; +#endif } bool Channels::ensureLicensedOperation() diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 151831b96..f219e60d2 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -334,7 +334,9 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p) if (toPhoneQueue.enqueue(p, 0) == false) { LOG_CRIT("Failed to queue a packet into toPhoneQueue!"); - abort(); + releaseToPool(p); + fromNum++; // notify observers so phone can resync + return; } fromNum++; } @@ -351,7 +353,8 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage if (toPhoneMqttProxyQueue.enqueue(m, 0) == false) { LOG_CRIT("Failed to queue a packet into toPhoneMqttProxyQueue!"); - abort(); + releaseMqttClientProxyMessageToPool(m); + return; } fromNum++; } @@ -383,7 +386,8 @@ void MeshService::sendClientNotification(meshtastic_ClientNotification *n) if (toPhoneClientNotificationQueue.enqueue(n, 0) == false) { LOG_CRIT("Failed to queue a notification into toPhoneClientNotificationQueue!"); - abort(); + releaseClientNotificationToPool(n); + return; } fromNum++; } diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index a0899a71b..5316c49b1 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -393,6 +393,13 @@ NodeDB::NodeDB() } else { LOG_WARN("Failed to read unique id from efuse"); } +#elif defined(ARCH_NRF54L15) + // nRF54L15: DEVICEID is under FICR->INFO sub-struct (not top-level as on nRF52) + uint64_t device_id_start = ((uint64_t)NRF_FICR->INFO.DEVICEID[1] << 32) | NRF_FICR->INFO.DEVICEID[0]; + uint64_t device_id_end = ((uint64_t)NRF_FICR->DEVICEADDR[1] << 32) | NRF_FICR->DEVICEADDR[0]; + memcpy(myNodeInfo.device_id.bytes, &device_id_start, sizeof(device_id_start)); + memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end)); + myNodeInfo.device_id.size = 16; #elif defined(ARCH_NRF52) // Nordic applies a FIPS compliant Random ID to each chip at the factory // We concatenate the device address to the Random ID to create a unique ID for now @@ -811,6 +818,23 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.lora.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET; #else config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; +#endif +#ifdef USERPREFS_LORACONFIG_USE_PRESET + config.lora.use_preset = USERPREFS_LORACONFIG_USE_PRESET; +#else + config.lora.use_preset = true; +#endif +#ifdef USERPREFS_LORACONFIG_BANDWIDTH + config.lora.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH; +#endif +#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR + config.lora.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR; +#endif +#ifdef USERPREFS_LORACONFIG_CODING_RATE + config.lora.coding_rate = USERPREFS_LORACONFIG_CODING_RATE; +#endif +#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY + config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY; #endif config.lora.hop_limit = HOP_RELIABLE; #ifdef USERPREFS_CONFIG_LORA_IGNORE_MQTT @@ -946,6 +970,10 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.network.wifi_enabled = USERPREFS_NETWORK_WIFI_ENABLED; #endif +#if USE_ETHERNET_DEFAULT + config.network.eth_enabled = true; +#endif + #ifdef USERPREFS_NETWORK_WIFI_SSID strncpy(config.network.wifi_ssid, USERPREFS_NETWORK_WIFI_SSID, sizeof(config.network.wifi_ssid)); #endif @@ -1028,7 +1056,8 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; moduleConfig.has_external_notification = true; -#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) +#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) || \ + defined(NEOPIXEL_STATUS_NOTIFICATION_PIN) moduleConfig.external_notification.enabled = true; #endif #if defined(PIN_BUZZER) @@ -1049,7 +1078,7 @@ void NodeDB::installDefaultModuleConfig() #endif #if defined(PIN_VIBRATION) moduleConfig.external_notification.nag_timeout = 2; -#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) +#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) || defined(NEOPIXEL_STATUS_NOTIFICATION_PIN) moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif @@ -1202,7 +1231,14 @@ void NodeDB::initModuleConfigIntervals() #else moduleConfig.telemetry.device_update_interval = MAX_INTERVAL; #endif +#ifdef USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL + moduleConfig.telemetry.environment_update_interval = USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL; +#else moduleConfig.telemetry.environment_update_interval = 0; +#endif +#ifdef USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED + moduleConfig.telemetry.environment_measurement_enabled = USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED; +#endif moduleConfig.telemetry.air_quality_interval = 0; moduleConfig.telemetry.power_update_interval = 0; moduleConfig.telemetry.health_update_interval = 0; @@ -1711,6 +1747,28 @@ void NodeDB::loadFromDisk() config.lora.tx_enabled = false; #endif + // Always-apply LoRa overrides: applied after loading saved config so they + // take effect even when NVS already has a valid config (e.g. region-locked + // dev boards with no BLE/serial to set the region at runtime). +#ifdef USERPREFS_CONFIG_LORA_REGION + config.lora.region = USERPREFS_CONFIG_LORA_REGION; +#endif +#ifdef USERPREFS_LORACONFIG_USE_PRESET + config.lora.use_preset = USERPREFS_LORACONFIG_USE_PRESET; +#endif +#ifdef USERPREFS_LORACONFIG_BANDWIDTH + config.lora.bandwidth = USERPREFS_LORACONFIG_BANDWIDTH; +#endif +#ifdef USERPREFS_LORACONFIG_SPREAD_FACTOR + config.lora.spread_factor = USERPREFS_LORACONFIG_SPREAD_FACTOR; +#endif +#ifdef USERPREFS_LORACONFIG_CODING_RATE + config.lora.coding_rate = USERPREFS_LORACONFIG_CODING_RATE; +#endif +#ifdef USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY + config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY; +#endif + if (backupSecurity.private_key.size > 0) { LOG_DEBUG("Restoring backup of security config"); config.security = backupSecurity; diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index ecf6ff809..349069de2 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -71,7 +71,15 @@ void PhoneAPI::handleStartConfig() } pauseBluetoothLogging = true; spiLock->lock(); +#if defined(MESHTASTIC_EXCLUDE_FILES_MANIFEST) + // Skip the recursive FS walk. Used by platforms whose Zephyr LittleFS + // backend can't safely traverse a deep tree (e.g. nRF54L15) and platforms + // that don't support OTA browsing — the manifest is only consumed by + // companion apps for those flows. + filesManifest.clear(); +#else filesManifest = getFiles("/", 10); +#endif spiLock->unlock(); LOG_DEBUG("Got %d files in manifest", filesManifest.size()); diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index de468cf97..ef9a17f8d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -46,16 +46,6 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c #endif } -RadioLibInterface::~RadioLibInterface() -{ - // If the static `instance` pointer still references us, clear it. - // A later successful init() may have replaced `instance` with a newer - // interface — don't clobber that case. - if (instance == this) { - instance = nullptr; - } -} - #ifdef ARCH_ESP32 // ESP32 doesn't use that flag #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() @@ -434,6 +424,9 @@ void RadioLibInterface::completeSending() // that can take a long time auto p = sendingPacket; sendingPacket = NULL; +#ifdef LED_LORA + digitalWrite(LED_LORA, LED_STATE_OFF); +#endif if (p) { // Packet has been sent, count it toward our TX airtime utilization. @@ -555,6 +548,9 @@ void RadioLibInterface::pollMissedIrqs() if (isReceiving) { checkRxDoneIrqFlag(); } + if (sendingPacket) { + checkTxDoneIrqFlag(); + } } void RadioLibInterface::resetAGC() @@ -570,6 +566,14 @@ void RadioLibInterface::checkRxDoneIrqFlag() } } +void RadioLibInterface::checkTxDoneIrqFlag() +{ + if (iface->checkIrq(RADIOLIB_IRQ_TX_DONE)) { + LOG_WARN("caught missed TX_DONE"); + notify(ISR_TX, true); + } +} + void RadioLibInterface::configHardwareForSend() { powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn); @@ -611,6 +615,9 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp) enableInterrupt(isrTxLevel0); lastTxStart = millis(); printPacket("Started Tx", txp); +#ifdef LED_LORA + digitalWrite(LED_LORA, LED_STATE_ON); +#endif } return res == RADIOLIB_ERR_NONE; diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 0740561f9..5b850d675 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -104,6 +104,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ static RadioLibInterface *instance; + /** Clear instance on destruction so stale pointer checks in loop() are safe */ + virtual ~RadioLibInterface() + { + if (instance == this) + instance = nullptr; + } + /** * Glue functions called from ISR land */ @@ -136,13 +143,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL); - /** - * Clear the static `instance` pointer if it still points at us, so callers - * that check `RadioLibInterface::instance != nullptr` don't dereference a - * freed object after a failed init() + unique_ptr reset. - */ - virtual ~RadioLibInterface(); - virtual ErrorCode send(meshtastic_MeshPacket *p) override; /** @@ -293,4 +293,5 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override; void checkRxDoneIrqFlag(); + void checkTxDoneIrqFlag(); }; diff --git a/src/mesh/api/WiFiServerAPI.h b/src/mesh/api/WiFiServerAPI.h index 5f2019983..4d754e7ed 100644 --- a/src/mesh/api/WiFiServerAPI.h +++ b/src/mesh/api/WiFiServerAPI.h @@ -3,9 +3,8 @@ #include "ServerAPI.h" #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET /** diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index d614a6438..820bb2764 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -198,7 +198,9 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType { /* Can not be auto detected but set by proto. Used for 128x64 screens */ meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3, /* Can not be auto detected but set by proto. Used for 128x128 screens */ - meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4 + meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4, + /* Can not be auto detected but set by proto. Used for 64x128 rotated screens */ + meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED = 5 } meshtastic_Config_DisplayConfig_OledType; typedef enum _meshtastic_Config_DisplayConfig_DisplayMode { @@ -720,8 +722,8 @@ extern "C" { #define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1)) #define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO -#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 -#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1)) +#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED +#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED+1)) #define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT #define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index f0fea08d7..192aeeffe 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -321,6 +321,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_MESH_NODE_T1 = 133, /* B&Q Consulting Station G3: TBD */ meshtastic_HardwareModel_STATION_G3 = 134, + /* Lilygo T-Impulse-Plus */ + meshtastic_HardwareModel_T_IMPULSE_PLUS = 135, + /* Lilygo T-Echo Card */ + meshtastic_HardwareModel_T_ECHO_CARD = 136, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index b8cf60bf0..25937e972 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -144,7 +144,7 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig { (the default official mqtt.meshtastic.org server can handle encrypted packets) Decrypted packets may be useful for external systems that want to consume meshtastic packets */ bool encryption_enabled; - /* Whether to send / consume json packets on MQTT */ + /* Deprecated: JSON packet support on MQTT was removed, and this field is ignored. */ bool json_enabled; /* If true, we attempt to establish a secure connection using TLS */ bool tls_enabled; diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 90fd8b084..fddc118a7 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -13,9 +13,8 @@ #include #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #ifdef ARCH_ESP32 diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index 493cc5353..eb4d90d40 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -12,9 +12,8 @@ #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #define UDP_MULTICAST_DEFAUL_PORT 4403 // Default port for UDP multicast is same as TCP api server diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 9930d0a55..82201ab82 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -10,9 +10,8 @@ #include "target_specific.h" #include -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #if HAS_ETHERNET && defined(USE_CH390D) diff --git a/src/mesh/wifi/WiFiAPClient.h b/src/mesh/wifi/WiFiAPClient.h index 1de897d7a..710868361 100644 --- a/src/mesh/wifi/WiFiAPClient.h +++ b/src/mesh/wifi/WiFiAPClient.h @@ -9,9 +9,8 @@ #include #endif -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET extern bool needReconnect; diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index c227d541c..fa4b71f78 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -262,7 +262,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta break; } case meshtastic_AdminMessage_ota_request_tag: { -#if defined(ARCH_ESP32) +#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI LOG_INFO("OTA Requested"); if (r->ota_request.ota_hash.size != 32) { @@ -666,7 +666,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers) bool requiresReboot = true; switch (c.which_payload_variant) { - case meshtastic_Config_device_tag: + case meshtastic_Config_device_tag: { LOG_INFO("Set config: Device"); config.has_device = true; #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && \ @@ -720,6 +720,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers) } #endif break; + } // case meshtastic_Config_device_tag case meshtastic_Config_position_tag: LOG_INFO("Set config: Position"); config.has_position = true; @@ -1337,6 +1338,10 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r if (config.bluetooth.enabled && nrf52Bluetooth) { conn.bluetooth.is_connected = nrf52Bluetooth->isConnected(); } +#elif defined(ARCH_NRF54L15) + if (config.bluetooth.enabled && nrf54l15Bluetooth) { + conn.bluetooth.is_connected = nrf54l15Bluetooth->isConnected(); + } #endif #endif conn.has_serial = true; // No serial-less devices @@ -1597,6 +1602,9 @@ void disableBluetooth() #elif defined(ARCH_NRF52) if (nrf52Bluetooth) nrf52Bluetooth->shutdown(); +#elif defined(ARCH_NRF54L15) + if (nrf54l15Bluetooth) + nrf54l15Bluetooth->shutdown(); #endif #endif } diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 1a0b69930..8a98e86df 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -206,6 +206,10 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on) #ifdef PCA_LED_NOTIFICATION io.digitalWrite(PCA_LED_NOTIFICATION, on); +#endif +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + notificationPixel.setPixelColor(0, on ? NEOPIXEL_STATUS_NOTIFICATION_COLOR : 0); + notificationPixel.show(); #endif break; } @@ -324,6 +328,12 @@ ExternalNotificationModule::ExternalNotificationModule() LOG_INFO("Use Pin %i in digital mode", output); pinMode(output, OUTPUT); } +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + LOG_INFO("Use WS2812 on GPIO %d as notification LED", NEOPIXEL_STATUS_NOTIFICATION_PIN); + notificationPixel.begin(); + notificationPixel.clear(); + notificationPixel.show(); +#endif setExternalState(0, false); externalTurnedOn[0] = 0; if (moduleConfig.external_notification.output_vibra) { diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 94b021360..8781c1ca8 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -10,6 +10,19 @@ extern AmbientLightingThread *ambientLightingThread; #endif +// Drive a single WS2812 as the notification LED (M1/M2-style LED_NOTIFICATION +// but addressable). A variant defines NEOPIXEL_STATUS_NOTIFICATION_PIN to +// enable. Colour defaults to green but can be overridden. +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN +#include +#ifndef NEOPIXEL_STATUS_TYPE +#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800) +#endif +#ifndef NEOPIXEL_STATUS_NOTIFICATION_COLOR +#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x00FF00 // green +#endif +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else @@ -38,6 +51,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: CallbackObserver(this, &ExternalNotificationModule::handleInputEvent); uint32_t output = 0; +#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN + Adafruit_NeoPixel notificationPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_NOTIFICATION_PIN, NEOPIXEL_STATUS_TYPE); +#endif + public: ExternalNotificationModule(); diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 4ea34fb52..f3a0e7a03 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -17,8 +17,29 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") if (inputBroker) inputObserver.observe(inputBroker); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + powerPixel.begin(); + powerPixel.clear(); + powerPixel.show(); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + pairingPixel.begin(); + pairingPixel.clear(); + pairingPixel.show(); +#endif } +// Helper: write a 1-pixel NeoPixel strand to `color` when stateOn, else clear. +// Kept as a static inline here (rather than a member) so it compiles out +// completely when no NeoPixel status pins are defined. +#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN) +static inline void writeStatusPixel(Adafruit_NeoPixel &pixel, uint32_t color, bool stateOn) +{ + pixel.setPixelColor(0, stateOn ? color : 0); + pixel.show(); +} +#endif + int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) { switch (arg->getStatusType()) { @@ -176,6 +197,12 @@ int32_t StatusLEDModule::runOnce() #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, CHARGE_LED_state == LED_STATE_ON); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, PAIRING_LED_state == LED_STATE_ON); +#endif #ifdef RGB_LED_POWER if (!config.device.led_heartbeat_disabled) { @@ -225,6 +252,12 @@ void StatusLEDModule::setPowerLED(bool LEDon) #ifdef LED_PAIRING digitalWrite(LED_PAIRING, ledState); #endif +#ifdef NEOPIXEL_STATUS_POWER_PIN + writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, LEDon); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, LEDon); +#endif #ifdef Battery_LED_1 digitalWrite(Battery_LED_1, ledState); diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index f66a536f6..f20198e39 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -13,6 +13,25 @@ #include "input/InputBroker.h" #endif +// WS2812/NeoPixel status-LED support. A variant may define +// NEOPIXEL_STATUS_POWER_PIN (required to enable the power/charge pixel) +// NEOPIXEL_STATUS_POWER_COLOR (optional, default red 0xFF0000) +// NEOPIXEL_STATUS_PAIRING_PIN / _COLOR (default blue 0x0000FF) +// Each pixel is a standalone 1-LED strand on its own GPIO — this mirrors how +// boards like the LilyGo T-Echo-Card expose three independent WS2812s. +#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN) +#include +#ifndef NEOPIXEL_STATUS_TYPE +#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800) +#endif +#ifndef NEOPIXEL_STATUS_POWER_COLOR +#define NEOPIXEL_STATUS_POWER_COLOR 0xFF0000 // red +#endif +#ifndef NEOPIXEL_STATUS_PAIRING_COLOR +#define NEOPIXEL_STATUS_PAIRING_COLOR 0x0000FF // blue +#endif +#endif + class StatusLEDModule : private concurrency::OSThread { bool slowTrack = false; @@ -27,6 +46,13 @@ class StatusLEDModule : private concurrency::OSThread void setPowerLED(bool); +#ifdef NEOPIXEL_STATUS_POWER_PIN + Adafruit_NeoPixel powerPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_POWER_PIN, NEOPIXEL_STATUS_TYPE); +#endif +#ifdef NEOPIXEL_STATUS_PAIRING_PIN + Adafruit_NeoPixel pairingPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_PAIRING_PIN, NEOPIXEL_STATUS_TYPE); +#endif + protected: unsigned int my_interval = 1000; // interval in millisconds virtual int32_t runOnce() override; diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 9c25177bc..4bee4fb7e 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -90,8 +90,8 @@ int32_t PaxcounterModule::runOnce() configuration.blecounter = 1; configuration.blescantime = 0; // infinite configuration.wificounter = 1; - configuration.wifi_channel_map = WIFI_CHANNEL_ALL; - configuration.wifi_channel_switch_interval = 50; + // configuration.wifi_channel_map = WIFI_CHANNEL_ALL; + // configuration.wifi_channel_switch_interval = 50; configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80); configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80); libpax_update_config(&configuration); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index a6ae79614..46ad952f4 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -19,12 +19,8 @@ #include "mesh/wifi/WiFiAPClient.h" #include #endif -#if HAS_ETHERNET && defined(USE_WS5500) -#include -#define ETH ETH2 -#elif HAS_ETHERNET && defined(USE_CH390D) -#include "ESP32_CH390.h" -#define ETH CH390 +#if HAS_ETHERNET && defined(ARCH_ESP32) +#include #endif // HAS_ETHERNET #include "Default.h" #if !defined(ARCH_NRF52) || NRF52_USE_JSON diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index ac52a99bc..e66e738e3 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -10,23 +10,18 @@ #include "mesh/PhoneAPI.h" #include "mesh/mesh-pb-constants.h" #include "sleep.h" -#include +#include +#include +#include +#include +#include #include #include -#ifdef NIMBLE_TWO -#include "NimBLEAdvertising.h" -#include "NimBLEExtAdvertising.h" #include "PowerStatus.h" -#endif -#if defined(CONFIG_NIMBLE_CPP_IDF) #include "host/ble_gap.h" -#else -#include "nimble/nimble/host/include/host/ble_gap.h" -#endif - -#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6) +#include "host/ble_store.h" namespace { @@ -34,7 +29,6 @@ constexpr uint16_t kPreferredBleMtu = 517; constexpr uint16_t kPreferredBleTxOctets = 251; constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8; } // namespace -#endif // Debugging options: careful, they slow things down quite a bit! // #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration @@ -44,10 +38,10 @@ constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8; #define NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE 3 #define NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE 3 -NimBLECharacteristic *fromNumCharacteristic; -NimBLECharacteristic *BatteryCharacteristic; -NimBLECharacteristic *logRadioCharacteristic; -NimBLEServer *bleServer; +BLECharacteristic *fromNumCharacteristic; +BLECharacteristic *BatteryCharacteristic; +BLECharacteristic *logRadioCharacteristic; +BLEServer *bleServer; static bool passkeyShowing; static std::atomic nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection" @@ -118,7 +112,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread - Yes, we have to do some copy operations on pop because of this, but it's worth it to avoid cross-task memory management. NOTIFY IS BROKEN: - - Adding NIMBLE_PROPERTY::NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards compatible. + - Adding BLECharacteristic::PROPERTY_NOTIFY to FromRadioCharacteristic appears to break things. It is NOT backwards + compatible. ZERO-SIZE READS: - Returning a zero-size read from onRead breaks some clients during the config phase. So we have to block onRead until we @@ -139,7 +134,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread std::mutex fromPhoneMutex; std::atomic fromPhoneQueueSize{0}; // We use array here (and pay the cost of memcpy) to avoid dynamic memory allocations and frees across FreeRTOS tasks. - std::array fromPhoneQueue{}; + std::array fromPhoneQueue{}; /* Packets to phone (BLE onRead callback) */ std::mutex toPhoneMutex; @@ -301,7 +296,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread LOG_DEBUG("NimbleBluetooth: handling ToRadio packet, fromPhoneQueueSize=%u", fromPhoneQueueSize.load()); // Pop the front of fromPhoneQueue, holding the mutex only briefly while we pop. - NimBLEAttValue val; + BLEValue val; { // scope for fromPhoneMutex mutex std::lock_guard guard(fromPhoneMutex); val = fromPhoneQueue[0]; @@ -316,7 +311,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread fromPhoneQueueSize--; } - handleToRadio(val.data(), val.length()); + handleToRadio(val.getData(), val.getLength()); } } @@ -328,9 +323,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread PhoneAPI::onNowHasData(fromRadioNum); #ifdef DEBUG_NIMBLE_NOTIFY - int currentNotifyCount = notifyCount.fetch_add(1); - uint8_t cc = bleServer->getConnectedCount(); // This logging slows things down when there are lots of packets going to the phone, like initial connection: LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc); @@ -340,13 +333,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread put_le32(val, fromRadioNum); fromNumCharacteristic->setValue(val, sizeof(val)); -#ifdef NIMBLE_TWO - // NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be - // notify(). - fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE); -#else fromNumCharacteristic->notify(); -#endif } /// Check the current underlying physical link to see if the client is currently connected @@ -409,14 +396,9 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI; // Last ToRadio value received from the phone static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; -class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks +class NimbleBluetoothToRadioCallback : public BLECharacteristicCallbacks { -#ifdef NIMBLE_TWO - virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override -#else - virtual void onWrite(NimBLECharacteristic *pCharacteristic) override - -#endif + void onWrite(BLECharacteristic *pCharacteristic) override { // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce. // Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls. @@ -428,15 +410,17 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks LOG_DEBUG("BLE onWrite(%d): start millis=%d", currentWriteCount, startMillis); #endif - auto val = pCharacteristic->getValue(); + // Create a BLEValue and populate it with the received data + BLEValue val; + val.setValue(pCharacteristic->getData(), pCharacteristic->getLength()); - if (memcmp(lastToRadio, val.data(), val.length()) != 0) { + if (memcmp(lastToRadio, val.getData(), val.getLength()) != 0) { if (bluetoothPhoneAPI->fromPhoneQueueSize < NIMBLE_BLUETOOTH_FROM_PHONE_QUEUE_SIZE) { // Note: the comparison above is safe without a mutex because we are the only method that *increases* // fromPhoneQueueSize. (It's okay if fromPhoneQueueSize *decreases* in the main task meanwhile.) - memcpy(lastToRadio, val.data(), val.length()); + memcpy(lastToRadio, val.getData(), val.getLength()); - { // scope for fromPhoneMutex mutex + { // scope for fromPhoneMutex mutexv, pCharacteristic->getLen // Append to fromPhoneQueue, protected by fromPhoneMutex. Hold the mutex as briefly as possible. std::lock_guard guard(bluetoothPhoneAPI->fromPhoneMutex); bluetoothPhoneAPI->fromPhoneQueue.at(bluetoothPhoneAPI->fromPhoneQueueSize) = val; @@ -450,24 +434,21 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks #ifdef DEBUG_NIMBLE_ON_WRITE_TIMING int finishMillis = millis(); LOG_DEBUG("BLE onWrite(%d): append to fromPhoneQueue took %u ms. numBytes=%d", currentWriteCount, - finishMillis - startMillis, val.length()); + finishMillis - startMillis, val.getLength()); #endif } else { - LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, val.length()); + LOG_WARN("BLE onWrite(%d): Drop ToRadio packet, fromPhoneQueue full (%u bytes)", currentWriteCount, + val.getLength()); } } else { - LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.length()); + LOG_DEBUG("BLE onWrite(%d): Drop duplicate ToRadio packet (%u bytes)", currentWriteCount, val.getLength()); } } }; -class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks +class NimbleBluetoothFromRadioCallback : public BLECharacteristicCallbacks { -#ifdef NIMBLE_TWO - virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override -#else - virtual void onRead(NimBLECharacteristic *pCharacteristic) override -#endif + void onRead(BLECharacteristic *pCharacteristic) override { // CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce. @@ -573,51 +554,46 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks } }; -class NimbleBluetoothServerCallback : public NimBLEServerCallbacks +class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks { -#ifdef NIMBLE_TWO - public: - NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; } - - private: - NimbleBluetooth *ble; - - virtual uint32_t onPassKeyDisplay() override -#else - virtual uint32_t onPassKeyRequest() override -#endif + void onPassKeyNotify(uint32_t passkey) override { - uint32_t configuredPasskey = config.bluetooth.fixed_pin; - - if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) { - LOG_INFO("Use random passkey"); - // This is the passkey to be entered on peer - we pick a number >100,000 to ensure 6 digits - configuredPasskey = random(100000, 999999); - } - LOG_INFO("*** Enter passkey %d on the peer side ***", configuredPasskey); - + LOG_INFO("*** Enter passkey %06u on the peer side ***", passkey); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - std::string passkey = std::to_string(configuredPasskey); - meshtastic::BluetoothStatus newStatus(passkey); + meshtastic::BluetoothStatus newStatus(std::to_string(passkey)); bluetoothStatus->updateStatus(&newStatus); - -#if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus +#if HAS_SCREEN if (screen) { + screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { + char btPIN[16] = "888888"; + snprintf(btPIN, sizeof(btPIN), "%06u", passkey); + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 12; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, y_offset + y, "Bluetooth"); +#if !defined(OLED_TINY) + display->setFont(FONT_SMALL); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; + display->drawString(x_offset + x, y_offset + y, "Enter this code"); +#endif + display->setFont(FONT_LARGE); + char pin[8]; + snprintf(pin, sizeof(pin), "%.3s %.3s", btPIN, btPIN + 3); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; + display->drawString(x_offset + x, y_offset + y, pin); - std::string ble_message = "Bluetooth\nPIN\n[M]" + passkey.substr(0, 3) + " " + passkey.substr(3, 6); - screen->showSimpleBanner(ble_message.c_str(), 30000); + display->setFont(FONT_SMALL); + char deviceName[64]; + snprintf(deviceName, sizeof(deviceName), "Name: %s", getDeviceName()); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; + display->drawString(x_offset + x, y_offset + y, deviceName); + }); } #endif passkeyShowing = true; - - return configuredPasskey; } - -#ifdef NIMBLE_TWO - virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo) override -#else - virtual void onAuthenticationComplete(ble_gap_conn_desc *desc) override -#endif + void onAuthenticationComplete(ble_gap_conn_desc *desc) override { LOG_INFO("BLE authentication complete"); @@ -625,58 +601,47 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks bluetoothStatus->updateStatus(&newStatus); clearPairingDisplay(); - // Store the connection handle for future use -#ifdef NIMBLE_TWO - nimbleBluetoothConnHandle = connInfo.getConnHandle(); -#else nimbleBluetoothConnHandle = desc->conn_handle; -#endif } +}; -#ifdef NIMBLE_TWO - virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override +class NimbleBluetoothServerCallback : public BLEServerCallbacks +{ + public: + explicit NimbleBluetoothServerCallback(NimbleBluetooth *ble) : ble(ble) {} + + private: + NimbleBluetooth *ble; + + void onConnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) { - LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str()); + BLEAddress peer_addr(desc->peer_id_addr); + LOG_INFO("BLE incoming connection %s", peer_addr.toString().c_str()); - const uint16_t connHandle = connInfo.getConnHandle(); -#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)) - int phyResult = - ble_gap_set_prefered_le_phy(connHandle, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_CODED_ANY); - if (phyResult == 0) { - LOG_INFO("BLE conn %u requested 2M PHY", connHandle); - } else { - LOG_WARN("Failed to prefer 2M PHY for conn %u, rc=%d", connHandle, phyResult); - } -#endif + const uint16_t connHandle = desc->conn_handle; + // With Google Pixel 8 Android devices, this causes ESP32 device crash + // when phone reconnects. Disable this to make progress on the + // Arduino v3 migration while we investigate the Android compatibility + // issue. +#if 0 int dataLenResult = ble_gap_set_data_len(connHandle, kPreferredBleTxOctets, kPreferredBleTxTimeUs); if (dataLenResult == 0) { LOG_INFO("BLE conn %u requested data length %u bytes", connHandle, kPreferredBleTxOctets); } else { LOG_WARN("Failed to raise data length for conn %u, rc=%d", connHandle, dataLenResult); } +#endif - LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu); + LOG_INFO("BLE conn %u peer MTU %u (target %u)", connHandle, pServer->getPeerMTU(connHandle), kPreferredBleMtu); pServer->updateConnParams(connHandle, 6, 12, 0, 200); } -#endif -#ifdef NIMBLE_TWO - virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override + void onDisconnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) { - LOG_INFO("BLE disconnect reason: %d", reason); -#else - virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) override - { - LOG_INFO("BLE disconnect"); -#endif -#ifdef NIMBLE_TWO + LOG_INFO("BLE disconnected"); if (ble->isDeInit) return; -#else - if (nimbleBluetooth && nimbleBluetooth->isDeInit) - return; -#endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); @@ -701,43 +666,51 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks bluetoothPhoneAPI->writeCount = 0; } - // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection memset(lastToRadio, 0, sizeof(lastToRadio)); - nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection" + nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; -#ifdef NIMBLE_TWO - // Restart Advertising ble->startAdvertising(); -#else - NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); - if (!pAdvertising->start(0)) { - if (pAdvertising->isAdvertising()) { - LOG_DEBUG("BLE advertising already running"); - } else { - LOG_ERROR("BLE failed to restart advertising"); - } - } -#endif } }; static NimbleBluetoothToRadioCallback *toRadioCallbacks; static NimbleBluetoothFromRadioCallback *fromRadioCallbacks; +void NimbleBluetooth::startAdvertising() +{ + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->stop(); + pAdvertising->reset(); + + pAdvertising->addServiceUUID(MESH_SERVICE_UUID); + // if (powerStatus->getHasBattery() == 1) { + // pAdvertising->addServiceUUID(BLEUUID((uint16_t)0x180f)); + // } + + BLEAdvertisementData scan = BLEAdvertisementData(); + scan.setName(getDeviceName()); + pAdvertising->setScanResponseData(scan); + pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMaxPreferred(0x12); + + if (!pAdvertising->start(0)) { + LOG_ERROR("BLE failed to start advertising"); + } else { + LOG_DEBUG("BLE Advertising started"); + } +} + void NimbleBluetooth::shutdown() { - // No measurable power saving for ESP32 during light-sleep(?) #ifndef ARCH_ESP32 - // Shutdown bluetooth for minimum power draw LOG_INFO("Disable bluetooth"); - NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->reset(); pAdvertising->stop(); #endif } -// Proper shutdown for ESP32. Needs reboot to reverse. void NimbleBluetooth::deinit() { #ifdef ARCH_ESP32 @@ -747,21 +720,19 @@ void NimbleBluetooth::deinit() #ifdef BLE_LED digitalWrite(BLE_LED, LED_STATE_OFF); #endif -#ifndef NIMBLE_TWO - NimBLEDevice::deinit(); -#endif + + BLEDevice::deinit(true); #endif } -// Has initial setup been completed bool NimbleBluetooth::isActive() { - return bleServer; + return bleServer != nullptr; } bool NimbleBluetooth::isConnected() { - return bleServer->getConnectedCount() > 0; + return bleServer && bleServer->getConnectedCount() > 0; } int NimbleBluetooth::getRssi() @@ -774,9 +745,9 @@ int NimbleBluetooth::getRssi() uint16_t connHandle = nimbleBluetoothConnHandle.load(); if (connHandle == BLE_HS_CONN_HANDLE_NONE) { - const auto peers = bleServer->getPeerDevices(); + const auto peers = bleServer->getPeerDevices(true); if (!peers.empty()) { - connHandle = peers.front(); + connHandle = peers.begin()->first; nimbleBluetoothConnHandle = connHandle; } } @@ -804,74 +775,84 @@ void NimbleBluetooth::setup() LOG_INFO("Init the NimBLE bluetooth module"); - NimBLEDevice::init(getDeviceName()); - NimBLEDevice::setPower(ESP_PWR_LVL_P9); + BLEDevice::init(getDeviceName()); + BLEDevice::setPower(ESP_PWR_LVL_P9); -#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)) - int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu); + int mtuResult = BLEDevice::setMTU(kPreferredBleMtu); if (mtuResult == 0) { LOG_INFO("BLE MTU request set to %u", kPreferredBleMtu); } else { LOG_WARN("Unable to request MTU %u, rc=%d", kPreferredBleMtu, mtuResult); } - int phyResult = ble_gap_set_prefered_default_le_phy(BLE_GAP_LE_PHY_2M_MASK, BLE_GAP_LE_PHY_2M_MASK); - if (phyResult == 0) { - LOG_INFO("BLE default PHY preference set to 2M"); - } else { - LOG_WARN("Failed to prefer 2M PHY by default, rc=%d", phyResult); - } - - int dataLenResult = ble_gap_write_sugg_def_data_len(kPreferredBleTxOctets, kPreferredBleTxTimeUs); - if (dataLenResult == 0) { - LOG_INFO("BLE suggested data length set to %u bytes", kPreferredBleTxOctets); - } else { - LOG_WARN("Failed to raise suggested data length (%u/%u), rc=%d", kPreferredBleTxOctets, kPreferredBleTxTimeUs, - dataLenResult); - } -#endif - + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { - NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC); - NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); - NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); - NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); + // Set IO capability to DisplayOnly for MITM authentication + pSecurity->setCapability(ESP_IO_CAP_OUT); + // Set the passkey + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) { + LOG_INFO("Use random passkey"); + pSecurity->setPassKey(false); // generate a random passkey + } else { + LOG_INFO("Use fixed passkey"); + pSecurity->setPassKey(true, config.bluetooth.fixed_pin); + } + // Enable authorization requirements: + // - bonding: true (for persistent storage of the keys) + // - MITM: true (enables Man-In-The-Middle protection for password prompts) + // - secure connection: true (enables secure connection for encryption) + pSecurity->setAuthenticationMode(true, true, true); + } else { + // No IO capability for no PIN mode + pSecurity->setCapability(ESP_IO_CAP_NONE); + // No PIN mode: no MITM protection + pSecurity->setAuthenticationMode(true, false, false); } - bleServer = NimBLEDevice::createServer(); -#ifdef NIMBLE_TWO - NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this); -#else - NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(); -#endif - bleServer->setCallbacks(serverCallbacks, true); + // Set the security callbacks + BLEDevice::setSecurityCallbacks(new NimbleBluetoothSecurityCallback()); + bleServer = BLEDevice::createServer(); + + // BLEDevice::createServer calls ble_svc_gap_init, which resets the device + // name to default, so set it again. + int nameRc = ble_svc_gap_device_name_set(BLEDevice::getDeviceName().c_str()); + if (nameRc != 0) { + LOG_ERROR("ble_svc_gap_device_name_set: rc=%d %s", nameRc, BLEUtils::returnCodeToString(nameRc)); + } + + bleServer->setCallbacks(new NimbleBluetoothServerCallback(this)); setupService(); startAdvertising(); } void NimbleBluetooth::setupService() { - NimBLEService *bleService = bleServer->createService(MESH_SERVICE_UUID); - NimBLECharacteristic *ToRadioCharacteristic; - NimBLECharacteristic *FromRadioCharacteristic; + BLEService *bleService = bleServer->createService(MESH_SERVICE_UUID); + BLECharacteristic *ToRadioCharacteristic; + BLECharacteristic *FromRadioCharacteristic; // Define the characteristics that the app is looking for if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { - ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); + ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE); // Allow notifications so phones can stream FromRadio without polling. - FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); - fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); - logRadioCharacteristic = - bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U); - } else { - ToRadioCharacteristic = bleService->createCharacteristic( - TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC); - FromRadioCharacteristic = bleService->createCharacteristic( - FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); + FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ); fromNumCharacteristic = - bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); + bleService->createCharacteristic(FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ); + logRadioCharacteristic = bleService->createCharacteristic(LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_READ); + } else { + ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_WRITE_AUTHEN | + BLECharacteristic::PROPERTY_WRITE_ENC); + FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | + BLECharacteristic::PROPERTY_READ_ENC); + fromNumCharacteristic = bleService->createCharacteristic( + FROMNUM_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC); logRadioCharacteristic = bleService->createCharacteristic( - LOGRADIO_UUID, - NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC, 512U); + LOGRADIO_UUID, BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_READ_AUTHEN | BLECharacteristic::PROPERTY_READ_ENC); } bluetoothPhoneAPI = new BluetoothPhoneAPI(); @@ -884,76 +865,31 @@ void NimbleBluetooth::setupService() bleService->start(); // Setup the battery service - NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service - BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic) - (uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1); -#ifdef NIMBLE_TWO - NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904(); -#else - NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904); -#endif - batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8); + BLEService *batteryService = bleServer->createService(BLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service + BLE2904 *batteryLevelDescriptor = new BLE2904(); + batteryLevelDescriptor->setFormat(BLE2904::FORMAT_UINT8); batteryLevelDescriptor->setNamespace(1); batteryLevelDescriptor->setUnit(0x27ad); - + BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic) + (uint16_t)0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BatteryCharacteristic->addDescriptor(batteryLevelDescriptor); batteryService->start(); } -void NimbleBluetooth::startAdvertising() -{ -#ifdef NIMBLE_TWO - NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); - NimBLEExtAdvertisement legacyAdvertising; - - legacyAdvertising.setLegacyAdvertising(true); - legacyAdvertising.setScannable(true); - legacyAdvertising.setConnectable(true); - legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN); - if (powerStatus->getHasBattery() == 1) { - legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f)); - } - legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID)); - legacyAdvertising.setMinInterval(500); - legacyAdvertising.setMaxInterval(1000); - - NimBLEExtAdvertisement legacyScanResponse; - legacyScanResponse.setLegacyAdvertising(true); - legacyScanResponse.setConnectable(true); - legacyScanResponse.setName(getDeviceName()); - - if (!pAdvertising->setInstanceData(0, legacyAdvertising)) { - LOG_ERROR("BLE failed to set legacyAdvertising"); - } else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) { - LOG_ERROR("BLE failed to set legacyScanResponse"); - } else if (!pAdvertising->start(0, 0, 0)) { - LOG_ERROR("BLE failed to start legacyAdvertising"); - } -#else - NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); - pAdvertising->reset(); - pAdvertising->addServiceUUID(MESH_SERVICE_UUID); - pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service - pAdvertising->start(0); -#endif -} - /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level) { if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) { BatteryCharacteristic->setValue(&level, 1); -#ifdef NIMBLE_TWO - BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE); -#else BatteryCharacteristic->notify(); -#endif } } void NimbleBluetooth::clearBonds() { LOG_INFO("Clearing bluetooth bonds!"); - NimBLEDevice::deleteAllBonds(); + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, nullptr); + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD, nullptr); } void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) @@ -961,11 +897,16 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length) if (!bleServer || !isConnected() || length > 512) { return; } -#ifdef NIMBLE_TWO - logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE); -#else - logRadioCharacteristic->notify(logMessage, length, true); -#endif + logRadioCharacteristic->setValue(logMessage, length); + logRadioCharacteristic->notify(); } +void clearNVS() +{ + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, nullptr); + ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD, nullptr); +#ifdef ARCH_ESP32 + ESP.restart(); +#endif +} #endif diff --git a/src/nimble/NimbleBluetooth.h b/src/nimble/NimbleBluetooth.h index a7b14ff73..dad0a8c98 100644 --- a/src/nimble/NimbleBluetooth.h +++ b/src/nimble/NimbleBluetooth.h @@ -12,16 +12,11 @@ class NimbleBluetooth : BluetoothApi bool isConnected(); int getRssi(); void sendLog(const uint8_t *logMessage, size_t length); -#if defined(NIMBLE_TWO) void startAdvertising(); -#endif bool isDeInit = false; private: void setupService(); -#if !defined(NIMBLE_TWO) - void startAdvertising(); -#endif }; void setBluetoothEnable(bool enable); \ No newline at end of file diff --git a/src/platform/esp32/IramMemcpy.c b/src/platform/esp32/IramMemcpy.c new file mode 100644 index 000000000..898f1bdc6 --- /dev/null +++ b/src/platform/esp32/IramMemcpy.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "esp_attr.h" + +#ifdef ESP32_FORCE_IRAM_MEMSET + +/* + * T-Beam/classic ESP32 boot workaround + * ------------------------------------ + * During early flash operations the ESP32 disables cache, but some IRAM flash + * code paths still reach libc memcpy/memset. If those resolve to flash-resident + * implementations, startup can panic with cache-disabled access errors. + * + * We wrap memcpy/memset for the T-Beam environment. Fast path uses the + * normal libc routines when cache is enabled; slow path uses IRAM-safe byte + * loops when cache is disabled. + */ + +extern void *__real_memcpy(void *dst, const void *src, size_t n); + +static inline bool IRAM_ATTR cache_is_enabled(void) +{ + return (*(volatile uint32_t *)0x3FF00040u) & (1u << 3); +} + +extern void *IRAM_ATTR __wrap_memcpy(void *dst, const void *src, size_t n) +{ + if (cache_is_enabled()) { + return __real_memcpy(dst, src, n); + } + + uint8_t *d = (uint8_t *)dst; + const uint8_t *s = (const uint8_t *)src; + while (n--) { + *d++ = *s++; + } + return dst; +} + +#endif diff --git a/src/platform/esp32/IramMemset.c b/src/platform/esp32/IramMemset.c new file mode 100644 index 000000000..66d74d8bc --- /dev/null +++ b/src/platform/esp32/IramMemset.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "esp_attr.h" + +#ifdef ESP32_FORCE_IRAM_MEMSET + +/* + * T-Beam/classic ESP32 boot workaround + * ------------------------------------ + * During early flash operations the ESP32 disables cache, but some IRAM flash + * code paths still reach libc memcpy/memset. If those resolve to flash-resident + * implementations, startup can panic with cache-disabled access errors. + * + * We wrap memcpy/memset for the T-Beam environment. Fast path uses the + * normal libc routines when cache is enabled; slow path uses IRAM-safe byte + * loops when cache is disabled. + */ + +extern void *__real_memset(void *dst, int c, size_t n); + +static inline bool IRAM_ATTR cache_is_enabled(void) +{ + return (*(volatile uint32_t *)0x3FF00040u) & (1u << 3); +} + +extern void *IRAM_ATTR __wrap_memset(void *dst, int c, size_t n) +{ + if (cache_is_enabled()) { + return __real_memset(dst, c, n); + } + + uint8_t *ptr = (uint8_t *)dst; + const uint8_t fill = (uint8_t)c; + while (n--) { + *ptr++ = fill; + } + return dst; +} + +#endif \ No newline at end of file diff --git a/src/platform/esp32/align-text.handler_execute-4.ld b/src/platform/esp32/align-text.handler_execute-4.ld new file mode 100644 index 000000000..fca4f27a2 --- /dev/null +++ b/src/platform/esp32/align-text.handler_execute-4.ld @@ -0,0 +1,11 @@ +/* Arduino fix: catch esp_event's orphaned .text.handler_execute section and align to 4 bytes */ +SECTIONS +{ + .text.handler_execute ALIGN(4) : + { + KEEP(*(.text.handler_execute)) + KEEP(*(.text.handler_execute.*)) + . = ALIGN(4); + } +} +INSERT AFTER .flash.text; \ No newline at end of file diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index dbc573c95..69b69ff68 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -165,17 +165,30 @@ void esp32Setup() // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 -#ifdef CONFIG_IDF_TARGET_ESP32C6 - esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t)); - wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000; - wdt_config->trigger_panic = true; - res = esp_task_wdt_init(wdt_config); +#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) + const esp_task_wdt_config_t wdt_config = { + .timeout_ms = APP_WATCHDOG_SECS * 1000, + .idle_core_mask = (1U << CONFIG_FREERTOS_NUMBER_OF_CORES) - 1U, + .trigger_panic = true, + }; + res = esp_task_wdt_init(&wdt_config); + if (res == ESP_ERR_INVALID_STATE) { + LOG_WARN("Task watchdog already initialized, reconfiguring existing instance"); + res = esp_task_wdt_reconfigure(&wdt_config); + } assert(res == ESP_OK); #else res = esp_task_wdt_init(APP_WATCHDOG_SECS, true); + if (res == ESP_ERR_INVALID_STATE) { + LOG_WARN("Task watchdog already initialized, reusing existing instance"); + res = ESP_OK; + } assert(res == ESP_OK); #endif - res = esp_task_wdt_add(NULL); + res = esp_task_wdt_status(NULL); + if (res == ESP_ERR_NOT_FOUND) { + res = esp_task_wdt_add(NULL); + } assert(res == ESP_OK); #if HAS_32768HZ @@ -258,8 +271,10 @@ void cpuDeepSleep(uint32_t msecToWake) #endif variant_shutdown(); +#if SOC_PM_SUPPORT_RTC_PERIPH_PD // We want RTC peripherals to stay on esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); +#endif esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs esp_deep_sleep_start(); // TBD mA sleep current (battery) diff --git a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp index 7ec9dca80..6bd86ab4b 100644 --- a/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp +++ b/src/platform/extra_variants/m5stack_cardputer_adv/variant.cpp @@ -3,14 +3,63 @@ #ifdef M5STACK_CARDPUTER_ADV #include "AudioBoard.h" +#include DriverPins PinsAudioBoardES8311; AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311); +// PI4IOE5V6408 on the optional Cap LoRa-1262 (and Cap LoRa868). +#define PI4IO_ADDR 0x43 +#define PI4IO_REG_IO_DIR 0x03 +#define PI4IO_REG_OUT_SET 0x05 +#define PI4IO_REG_OUT_H_IM 0x07 + +static TwoWire *findLoraCapBus() +{ + TwoWire *candidates[] = {&Wire1, &Wire}; + for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { + candidates[i]->beginTransmission(PI4IO_ADDR); + if (candidates[i]->endTransmission() == 0) { + return candidates[i]; + } + } + return nullptr; +} + +static bool pi4ioWrite(TwoWire *bus, uint8_t reg, uint8_t val) +{ + bus->beginTransmission(PI4IO_ADDR); + bus->write(reg); + bus->write(val); + uint8_t status = bus->endTransmission(); + if (status != 0) { + LOG_DEBUG("PI4IO write reg=0x%02x val=0x%02x failed, I2C status=%u", reg, val, status); + return false; + } + return true; +} + +static void initLoraCap() +{ + TwoWire *bus = findLoraCapBus(); + if (!bus) { + LOG_ERROR("Cap LoRa-1262 not found"); + return; + } + bool ok = pi4ioWrite(bus, PI4IO_REG_IO_DIR, 0b00000001); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000000); + ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_SET, 0b00000001); + if (!ok) { + LOG_ERROR("Antenna switch init failed"); + } +} + // M5stack Cardputer ADV specific init void lateInitVariant() { + initLoraCap(); + // AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug); // I2C: function, scl, sda PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire); diff --git a/src/platform/nrf54l15/Arduino.h b/src/platform/nrf54l15/Arduino.h new file mode 100644 index 000000000..d94d9fd4e --- /dev/null +++ b/src/platform/nrf54l15/Arduino.h @@ -0,0 +1,824 @@ +/** + * Arduino.h — Zephyr compatibility shim for nRF54L15 + * + * Provides the Arduino API surface expected by Meshtastic, backed by + * Zephyr primitives. Only the subset actually used by Meshtastic is + * implemented; the rest compiles as no-ops / stubs for now. + * + * Phase 2: compile only. Real GPIO / SPI / Wire implementations follow + * in Phase 3 once the build is clean. + */ + +#pragma once +#ifndef Arduino_h +#define Arduino_h + +// ── C standard headers ─────────────────────────────────────────────────────── +#include +#include +#include +#include +#include +#include +#include +#include /* strcasecmp, strncasecmp */ + +// ── Zephyr kernel ──────────────────────────────────────────────────────────── +#include +#include + +// ── Basic Arduino types ────────────────────────────────────────────────────── +typedef bool boolean; +typedef uint8_t byte; +typedef uint16_t word; + +// ── Pin / digital constants ────────────────────────────────────────────────── +#define INPUT 0u +#define OUTPUT 1u +#define INPUT_PULLUP 2u +#define INPUT_PULLDOWN 3u +#define OUTPUT_OPENDRAIN 4u + +#define HIGH 1u +#define LOW 0u + +#define CHANGE 1 +#define FALLING 2 +#define RISING 3 + +#ifndef LED_BUILTIN +#define LED_BUILTIN -1 +#endif + +// ── Math / trig constants ──────────────────────────────────────────────────── +#ifndef PI +#define PI 3.14159265358979323846 +#endif +#define HALF_PI 1.57079632679489661923 +#define TWO_PI 6.28318530717958647693 +#define DEG_TO_RAD 0.01745329251994329576 +#define RAD_TO_DEG 57.2957795130823208767 +#define EULER 2.71828182845904523536 + +// ── Bit utilities ──────────────────────────────────────────────────────────── +#define bitRead(v, b) (((v) >> (b)) & 1) +#define bitSet(v, b) ((v) |= (1UL << (b))) +#define bitClear(v, b) ((v) &= ~(1UL << (b))) +#define bitToggle(v, b) ((v) ^= (1UL << (b))) +#define bitWrite(v, b, x) ((x) ? bitSet(v, b) : bitClear(v, b)) +#define bit(b) (1UL << (b)) +#define lowByte(w) ((uint8_t)((w)&0xff)) +#define highByte(w) ((uint8_t)((w) >> 8)) +// word(h,l) — only define if not already defined (conflicts with typedef above) +#undef word +#define word(h, l) ((uint16_t)(((h) << 8) | (l))) + +// ── UART config constants ───────────────────────────────────────────────────── +#define SERIAL_8N1 0x800001cu +#define SERIAL_8N2 0x8000001eu +#define SERIAL_8E1 0x8000001eu +#define SERIAL_7E1 0x8000001cu + +// ── Integer order ──────────────────────────────────────────────────────────── +// Adafruit BusIO's SPIDevice.h has `typedef BitOrder BusIOBitOrder;` which +// requires BitOrder to be a *type*, not a macro. Mirror the ArduinoCore-API +// enum definition rather than #defines. +enum BitOrder : uint8_t { + LSBFIRST = 0, + MSBFIRST = 1, +}; + +// ── pgmspace compatibility (no-ops on Cortex-M) ────────────────────────────── +#define PROGMEM +#define PSTR(s) (s) +#define F(s) (s) +#define pgm_read_byte(addr) (*((const uint8_t *)(addr))) +#define pgm_read_word(addr) (*((const uint16_t *)(addr))) +#define pgm_read_dword(addr) (*((const uint32_t *)(addr))) +#define pgm_read_float(addr) (*((const float *)(addr))) +#define pgm_read_ptr(addr) (*((const void **)(addr))) +#define strlen_P(s) strlen(s) +#define strcpy_P(d, s) strcpy(d, s) +#define strncpy_P(d, s, n) strncpy(d, s, n) +#define strcmp_P(a, b) strcmp(a, b) +#define memcpy_P(d, s, n) memcpy(d, s, n) +#define sprintf_P sprintf +typedef const char *PGM_P; +typedef const char *PGM_VOID_P; + +// ── Arduino numeric base constants (used by Print, RadioLib, etc.) ─────────── +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +// ── ulong / uint typedef (used by RadioLibInterface, etc.) ─────────────────── +typedef unsigned long ulong; +typedef unsigned int uint; + +// ── Interrupt stubs ────────────────────────────────────────────────────────── +static inline void interrupts() {} +static inline void noInterrupts() {} +#define digitalPinToInterrupt(p) (p) + +// ── portMAX_DELAY — freertosinc.h also defines this; let it win ────────────── +// We intentionally do NOT define portMAX_DELAY here. freertosinc.h defines +// it for the FreeRTOS / Meshtastic threading layer and must not be overridden. + +// ── Timing & system functions — declared with C linkage ────────────────────── +// buzz.cpp and others forward-declare delay() as extern "C"; keep linkage +// consistent by wrapping in extern "C" here. +#ifdef __cplusplus +extern "C" { +#endif +void NVIC_SystemReset(void); +uint32_t millis(void); +uint32_t micros(void); +void delay(uint32_t ms); +void delayMicroseconds(uint32_t us); +void yield(void); +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus + +#include +#include + +// ── C++ STL — include BEFORE defining any min/max helpers ─────────────────── +// Include algorithm first so its min/max templates are in scope. +// We must NOT define min/max as function-like macros: the C++ STL uses +// 3-argument versions (min(a,b,comp)) that the preprocessor would treat as +// calling a 2-arg macro with 3 args. +#include +// Bring 2-arg std::min / std::max into the global namespace as unqualified +// names so that Arduino code calling min(a,b) continues to compile. +// (Arduino convention; kept minimal to avoid surprises.) +#undef min +#undef max +using std::max; +using std::min; + +// ── Arduino math helpers (macros safe for mixed-type / C calls) ────────────── +#ifndef abs +#define abs(x) ((x) >= 0 ? (x) : -(x)) +#endif +#define constrain(x, l, h) ((x) < (l) ? (l) : ((x) > (h) ? (h) : (x))) +#define round(x) ((x) >= 0 ? (long)((x) + 0.5) : (long)((x)-0.5)) +#define radians(d) ((d)*DEG_TO_RAD) +#define degrees(r) ((r)*RAD_TO_DEG) +#define sq(x) ((x) * (x)) + +// ── Random ─────────────────────────────────────────────────────────────────── +static inline void randomSeed(unsigned long seed) +{ + srand((unsigned int)seed); +} +static inline long random(void) +{ + return (long)rand(); +} +static inline long random(long bound) +{ + return bound > 0 ? (rand() % bound) : 0; +} +static inline long random(long lo, long hi) +{ + return hi > lo ? lo + rand() % (hi - lo) : lo; +} + +// ── GPIO — real Zephyr implementation (Phase 3) ────────────────────────────── +// Implemented in nrf54l15_arduino.cpp using Zephyr GPIO/SPI APIs. +// Pin numbering: P0.n = n, P1.n = 16+n, P2.n = 32+n +void pinMode(uint32_t pin, uint32_t mode); +void digitalWrite(uint32_t pin, uint32_t value); +int digitalRead(uint32_t pin); +static inline void digitalToggle(uint32_t pin) +{ + digitalWrite(pin, !digitalRead(pin)); +} +static inline uint32_t analogRead(uint32_t) +{ + return 0; +} +static inline void analogWrite(uint32_t, uint32_t) {} +static inline void analogReadResolution(int) {} +static inline void analogWriteResolution(int) {} + +// ── __WFI — provided by CMSIS core_cm33.h; do NOT redefine here ───────────── + +// ── __FlashStringHelper — Arduino PROGMEM string class (no-op on Cortex-M) ── +class __FlashStringHelper; + +// ── attachInterrupt / detachInterrupt — real Zephyr GPIO interrupt impl ────── +typedef void (*voidFuncPtr)(void); +void attachInterrupt(uint32_t pin, voidFuncPtr cb, int mode); +void detachInterrupt(uint32_t pin); + +// ── Forward declaration of String (needed by Print / Stream) ───────────────── +class String; + +// ── Print base class ───────────────────────────────────────────────────────── +class Print +{ + public: + virtual size_t write(uint8_t c) = 0; + virtual size_t write(const uint8_t *buf, size_t n) + { + size_t written = 0; + while (n--) + written += write(*buf++); + return written; + } + size_t write(const char *s) { return s ? write((const uint8_t *)s, strlen(s)) : 0; } + size_t write(const char *s, size_t n) { return write((const uint8_t *)s, n); } + + size_t print(const char *s) { return s ? write((const uint8_t *)s, strlen(s)) : 0; } + int printf(const char *fmt, ...) __attribute__((format(printf, 2, 3))); + + size_t print(char c) { return write((uint8_t)c); } + size_t print(const String &s); + size_t print(unsigned char n, int base = 10); + size_t print(int n, int base = 10); + size_t print(long n, int base = 10); + size_t print(unsigned int n, int base = 10); + size_t print(unsigned long n, int base = 10); + size_t print(float n, int digits = 2); + size_t print(double n, int digits = 2); + size_t print(bool b) { return print(b ? "true" : "false"); } + + size_t println() { return write((uint8_t)'\n'); } + size_t println(const char *s) + { + size_t r = print(s); + return r + println(); + } + size_t println(char c) + { + size_t r = print(c); + return r + println(); + } + size_t println(const String &s); + size_t println(int n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(long n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(unsigned long n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(unsigned int n, int base = 10) + { + size_t r = print(n, base); + return r + println(); + } + size_t println(float n, int d = 2) + { + size_t r = print(n, d); + return r + println(); + } + size_t println(double n, int d = 2) + { + size_t r = print(n, d); + return r + println(); + } + size_t println(bool b) + { + size_t r = print(b); + return r + println(); + } + + virtual void flush() {} +}; + +// ── Stream base class ──────────────────────────────────────────────────────── +class Stream : public Print +{ + public: + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void setTimeout(unsigned long) {} + virtual bool find(const char *) { return false; } + String readString(); + String readStringUntil(char terminator); +}; + +// ── Minimal Arduino String class (backed by a char buffer) ─────────────────── +class String +{ + public: + String() : _buf(nullptr), _len(0), _cap(0) {} + // Implicit conversion is part of the Arduino String contract, used pervasively across the codebase. + // cppcheck-suppress noExplicitConstructor + String(const char *cstr) : _buf(nullptr), _len(0), _cap(0) + { + if (cstr) + assign(cstr, strlen(cstr)); + } + // cppcheck-suppress noExplicitConstructor + String(const String &s) : _buf(nullptr), _len(0), _cap(0) { assign(s._buf ? s._buf : "", s._len); } + // cppcheck-suppress noExplicitConstructor + String(char c) : _buf(nullptr), _len(0), _cap(0) + { + const char tmp[2] = {c, 0}; + assign(tmp, 1); + } + // cppcheck-suppress noExplicitConstructor + String(int n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[16]; + snprintf(tmp, 16, "%d", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(unsigned int n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[16]; + snprintf(tmp, 16, "%u", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(long n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[24]; + snprintf(tmp, 24, "%ld", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(unsigned long n) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[24]; + snprintf(tmp, 24, "%lu", n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(float n, int d = 2) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[32]; + snprintf(tmp, 32, "%.*f", d, n); + assign(tmp, strlen(tmp)); + } + // cppcheck-suppress noExplicitConstructor + String(double n, int d = 2) : _buf(nullptr), _len(0), _cap(0) + { + char tmp[32]; + snprintf(tmp, 32, "%.*f", d, (double)n); + assign(tmp, strlen(tmp)); + } + ~String() { free(_buf); } + + String &operator=(const String &s) + { + assign(s._buf ? s._buf : "", s._len); + return *this; + } + String &operator=(const char *s) + { + assign(s ? s : "", s ? strlen(s) : 0); + return *this; + } + String &operator=(char c) + { + const char tmp[2] = {c, 0}; + assign(tmp, 1); + return *this; + } + + String &operator+=(const String &s) + { + concat(s._buf ? s._buf : "", s._len); + return *this; + } + String &operator+=(const char *s) + { + if (s) + concat(s, strlen(s)); + return *this; + } + String &operator+=(char c) + { + concat(&c, 1); + return *this; + } + String &operator+=(int n) { return *this += String(n); } + String &operator+=(unsigned long n) { return *this += String(n); } + + String operator+(const String &rhs) const + { + String r(*this); + r += rhs; + return r; + } + String operator+(const char *rhs) const + { + String r(*this); + r += rhs; + return r; + } + String operator+(char rhs) const + { + String r(*this); + r += rhs; + return r; + } + + bool operator==(const String &s) const { return _len == s._len && (_len == 0 || strcmp(_buf, s._buf) == 0); } + bool operator==(const char *s) const { return s && strcmp(c_str(), s) == 0; } + bool operator!=(const String &s) const { return !(*this == s); } + bool operator!=(const char *s) const { return !(*this == s); } + bool operator<(const String &s) const { return strcmp(c_str(), s.c_str()) < 0; } + bool operator>(const String &s) const { return strcmp(c_str(), s.c_str()) > 0; } + + char operator[](unsigned int i) const { return (_buf && i < _len) ? _buf[i] : 0; } + char &operator[](unsigned int i) + { + static char dummy = 0; + return (_buf && i < _len) ? _buf[i] : dummy; + } + + const char *c_str() const { return _buf ? _buf : ""; } + unsigned int length() const { return _len; } + bool isEmpty() const { return _len == 0; } + bool equals(const String &s) const { return *this == s; } + bool equals(const char *s) const { return *this == s; } + bool equalsIgnoreCase(const String &s) const + { + if (_len != s._len) + return false; + for (unsigned i = 0; i < _len; i++) + if (std::tolower(_buf[i]) != std::tolower(s._buf[i])) + return false; + return true; + } + bool startsWith(const String &pfx) const + { + if (pfx._len > _len) + return false; + return strncmp(c_str(), pfx.c_str(), pfx._len) == 0; + } + bool startsWith(const char *pfx) const + { + if (!pfx) + return false; + size_t pl = strlen(pfx); + return pl <= _len && strncmp(c_str(), pfx, pl) == 0; + } + bool endsWith(const String &sfx) const + { + if (sfx._len > _len) + return false; + return strcmp(c_str() + _len - sfx._len, sfx.c_str()) == 0; + } + int indexOf(char c, unsigned from = 0) const + { + if (!_buf) + return -1; + const char *p = strchr(_buf + from, c); + return p ? (int)(p - _buf) : -1; + } + int indexOf(const String &s, unsigned from = 0) const + { + if (!_buf) + return -1; + const char *p = strstr(_buf + from, s.c_str()); + return p ? (int)(p - _buf) : -1; + } + int lastIndexOf(char c) const + { + if (!_buf) + return -1; + const char *p = strrchr(_buf, c); + return p ? (int)(p - _buf) : -1; + } + String substring(unsigned beginIndex) const + { + if (!_buf || beginIndex >= _len) + return String(); + return String(_buf + beginIndex); + } + String substring(unsigned beginIndex, unsigned endIndex) const + { + if (!_buf || beginIndex >= _len) + return String(); + if (endIndex > _len) + endIndex = _len; + if (endIndex <= beginIndex) + return String(); + String r; + r.assign(_buf + beginIndex, endIndex - beginIndex); + return r; + } + void toUpperCase() + { + if (_buf) + for (unsigned i = 0; i < _len; i++) + _buf[i] = (char)std::toupper(_buf[i]); + } + void toLowerCase() + { + if (_buf) + for (unsigned i = 0; i < _len; i++) + _buf[i] = (char)std::tolower(_buf[i]); + } + void trim() + { + if (!_buf || _len == 0) + return; + unsigned s = 0; + while (s < _len && std::isspace(_buf[s])) + s++; + unsigned e = _len; + while (e > s && std::isspace(_buf[e - 1])) + e--; + if (s > 0 || e < _len) { + memmove(_buf, _buf + s, e - s); + _len = e - s; + _buf[_len] = 0; + } + } + void replace(char from, char to) + { + if (_buf) + for (unsigned i = 0; i < _len; i++) + if (_buf[i] == from) + _buf[i] = to; + } + void replace(const String &from, const String &to); + bool remove(unsigned index, unsigned count = 1) + { + if (!_buf || index >= _len) + return false; + if (index + count > _len) + count = _len - index; + memmove(_buf + index, _buf + index + count, _len - index - count + 1); + _len -= count; + return true; + } + void clear() + { + _len = 0; + if (_buf) + _buf[0] = 0; + } + char charAt(unsigned i) const { return (*this)[i]; } + void setCharAt(unsigned i, char c) + { + if (_buf && i < _len) + _buf[i] = c; + } + void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const + { + if (!buf || bufsize == 0) + return; + unsigned int avail = (_buf && _len > index) ? (_len - index) : 0; + unsigned int copy = avail < bufsize - 1 ? avail : bufsize - 1; + if (copy > 0) + memcpy(buf, _buf + index, copy); + buf[copy] = '\0'; + } + void concat(const String &s) { *this += s; } + void concat(const char *s) { *this += s; } + long toInt() const { return _buf ? atol(_buf) : 0; } + float toFloat() const { return _buf ? (float)atof(_buf) : 0.0f; } + double toDouble() const { return _buf ? atof(_buf) : 0.0; } + + private: + char *_buf; + unsigned int _len; + unsigned int _cap; + + void assign(const char *s, unsigned int n) + { + if (n >= _cap) + reserve(n + 1); + if (_buf) { + memcpy(_buf, s, n); + _buf[n] = 0; + _len = n; + } + } + void concat(const char *s, unsigned int n) + { + if (!s || n == 0) + return; + unsigned newlen = _len + n; + if (newlen >= _cap) + reserve(newlen + 1); + if (_buf) { + memcpy(_buf + _len, s, n); + _len = newlen; + _buf[_len] = 0; + } + } + void reserve(unsigned int n) + { + char *b = (char *)realloc(_buf, n); + if (b) { + _buf = b; + _cap = n; + } + } +}; + +inline String operator+(const char *lhs, const String &rhs) +{ + return String(lhs) + rhs; +} +inline String operator+(char lhs, const String &rhs) +{ + return String(lhs) + rhs; +} + +// ── Print inline definitions that need String ──────────────────────────────── +inline size_t Print::print(const String &s) +{ + return write((const uint8_t *)s.c_str(), s.length()); +} +inline size_t Print::println(const String &s) +{ + size_t r = print(s); + return r + println(); +} + +// ── Stream inline definitions that need String ─────────────────────────────── +inline String Stream::readString() +{ + return String(); +} +inline String Stream::readStringUntil(char) +{ + return String(); +} + +// ── HardwareSerial ─────────────────────────────────────────────────────────── +class HardwareSerial : public Stream +{ + public: + void begin(unsigned long) {} + void begin(unsigned long, uint16_t) {} + void end() {} + void setPins(int rx, int tx) {} + void setPinout(int tx, int rx) {} + void setFIFOSize(size_t) {} + void setRxBufferSize(size_t) {} + void begin(unsigned long baud, uint32_t config, int8_t rx = -1, int8_t tx = -1, bool invert = false) {} + int available() override { return 0; } + int read() override { return -1; } + int peek() override { return -1; } + size_t write(uint8_t c) override; + size_t write(const uint8_t *buf, size_t n) override; + using Print::write; // un-hide base class write(const char*) + size_t readBytes(uint8_t *buf, size_t len) { return 0; } + size_t readBytes(char *buf, size_t len) { return 0; } + operator bool() const { return true; } + void flush() override {} + String readString() { return String(); } + String readStringUntil(char) { return String(); } +}; + +// Uart — nRF52 BSP alias for HardwareSerial (used by GPS.h when ARCH_NRF52) +typedef HardwareSerial Uart; + +extern HardwareSerial Serial; +extern HardwareSerial Serial1; +extern HardwareSerial Serial2; + +// ── map() utility ──────────────────────────────────────────────────────────── +static inline long map(long x, long in_min, long in_max, long out_min, long out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +// ── shiftIn / shiftOut stubs ───────────────────────────────────────────────── +static inline uint8_t shiftIn(uint8_t, uint8_t, uint8_t) +{ + return 0; +} +static inline void shiftOut(uint8_t, uint8_t, uint8_t, uint8_t) {} + +// ── tone / noTone stubs ────────────────────────────────────────────────────── +static inline void tone(uint8_t, unsigned int, unsigned long = 0) {} +static inline void noTone(uint8_t) {} + +// ── pulseIn stub ───────────────────────────────────────────────────────────── +static inline unsigned long pulseIn(uint8_t, uint8_t, unsigned long = 1000000UL) +{ + return 0; +} + +// ── strdup / stpcpy — POSIX extensions not in Zephyr newlib ───────────────── +#ifndef strdup +static inline char *strdup(const char *s) +{ + size_t n = strlen(s) + 1; + char *d = (char *)malloc(n); + if (d) + memcpy(d, s, n); + return d; +} +#endif +#ifndef stpcpy +static inline char *stpcpy(char *dst, const char *src) +{ + while ((*dst++ = *src++) != '\0') { + } + return dst - 1; +} +#endif + +// ── strnstr — BSD extension not in Zephyr libc; defined in meshUtils.cpp ───── +// Declare here so callers (GPS.cpp etc.) don't need ARCH_PORTDUINO. +#ifndef STRNSTR +#define STRNSTR +char *strnstr(const char *s, const char *find, size_t slen); +#endif + +// ── strlcpy — BSD extension; implementation in nrf54l15_arduino.cpp ────────── +#ifndef HAVE_STRLCPY +#define HAVE_STRLCPY +#ifdef __cplusplus +extern "C" { +#endif +size_t strlcpy(char *dst, const char *src, size_t size); +#ifdef __cplusplus +} +#endif +#endif + +// ── setenv / getenv / tzset — Zephyr stubs for timezone support ────────────── +#include +static inline int setenv(const char *, const char *, int) +{ + return 0; +} +static inline void tzset(void) {} + +// ── dbgHeapFree / dbgHeapTotal — nRF52 BSP heap diagnostics ───────────────── +// Used by memGet.cpp when ARCH_NRF52 is defined. Return 0 for Phase 2. +static inline uint32_t dbgHeapFree(void) +{ + return 0; +} +static inline uint32_t dbgHeapTotal(void) +{ + return 0; +} + +// ── WCharacter helpers ─────────────────────────────────────────────────────── +static inline bool isAlpha(char c) +{ + return std::isalpha((unsigned char)c) != 0; +} +static inline bool isAlphaNumeric(char c) +{ + return std::isalnum((unsigned char)c) != 0; +} +static inline bool isDigit(char c) +{ + return std::isdigit((unsigned char)c) != 0; +} +static inline bool isSpace(char c) +{ + return std::isspace((unsigned char)c) != 0; +} +static inline bool isUpperCase(char c) +{ + return std::isupper((unsigned char)c) != 0; +} +static inline bool isLowerCase(char c) +{ + return std::islower((unsigned char)c) != 0; +} +static inline char toUpperCase(char c) +{ + return (char)std::toupper((unsigned char)c); +} +static inline char toLowerCase(char c) +{ + return (char)std::tolower((unsigned char)c); +} + +#else /* C only */ +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef abs +#define abs(x) ((x) >= 0 ? (x) : -(x)) +#endif +#define constrain(x, l, h) ((x) < (l) ? (l) : ((x) > (h) ? (h) : (x))) +#define round(x) ((x) >= 0 ? (long)((x) + 0.5) : (long)((x)-0.5)) +#endif /* __cplusplus */ + +#endif /* Arduino_h */ diff --git a/src/platform/nrf54l15/IPAddress.h b/src/platform/nrf54l15/IPAddress.h new file mode 100644 index 000000000..bb68580b9 --- /dev/null +++ b/src/platform/nrf54l15/IPAddress.h @@ -0,0 +1,34 @@ +// IPAddress.h — stub for nRF54L15/Zephyr +// MQTT.cpp includes this for IPv4 address representation. +// Phase 2: compile-only stub. +#pragma once +#include + +class IPAddress +{ + public: + IPAddress() : _addr(0) {} + explicit IPAddress(uint32_t addr) : _addr(addr) {} + IPAddress(uint8_t a, uint8_t b, uint8_t c, uint8_t d) + : _addr(((uint32_t)a) | ((uint32_t)b << 8) | ((uint32_t)c << 16) | ((uint32_t)d << 24)) + { + } + + uint8_t operator[](int i) const { return reinterpret_cast(&_addr)[i]; } + operator uint32_t() const { return _addr; } + bool operator==(const IPAddress &o) const { return _addr == o._addr; } + bool operator!=(const IPAddress &o) const { return _addr != o._addr; } + + bool fromString(const char *addr) + { + unsigned a, b, c, d; + if (sscanf(addr, "%u.%u.%u.%u", &a, &b, &c, &d) == 4 && a <= 255 && b <= 255 && c <= 255 && d <= 255) { + _addr = a | (b << 8) | (c << 16) | (d << 24); + return true; + } + return false; + } + + private: + uint32_t _addr; +}; diff --git a/src/platform/nrf54l15/InternalFileSystem.cpp b/src/platform/nrf54l15/InternalFileSystem.cpp new file mode 100644 index 000000000..1aed726f2 --- /dev/null +++ b/src/platform/nrf54l15/InternalFileSystem.cpp @@ -0,0 +1,274 @@ +// InternalFileSystem.cpp — Zephyr LittleFS backend for nRF54L15 +// +// Implements Adafruit_LittleFS_Namespace used by FSCommon.h/cpp. +// Storage: 36 KB storage_partition in nRF54L15 internal RRAM (defined in +// zephyr/dts/nordic/nrf54l15_partition.dtsi, included by the board DTS). + +#include "InternalFileSystem.h" +#include "configuration.h" + +#include +#include +#include + +using namespace Adafruit_LittleFS_Namespace; + +// ── LittleFS mount ──────────────────────────────────────────────────────── + +FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(nrf54l15_lfs_data); + +static struct fs_mount_t _lfs_mnt = { + .type = FS_LITTLEFS, + .mnt_point = NRF54L15_FS_MOUNT, + .fs_data = &nrf54l15_lfs_data, + .storage_dev = (void *)(uintptr_t)FIXED_PARTITION_ID(storage_partition), + .flags = 0, +}; + +// ── Global singleton ────────────────────────────────────────────────────── + +Adafruit_LittleFS_Namespace::InternalFileSystem InternalFS; + +// ── Path helpers ────────────────────────────────────────────────────────── + +void InternalFileSystem::toabs(const char *rel, char *abs, size_t abssz) +{ + // Root "/" maps to the mount point itself (no trailing slash) + if (rel[0] == '/' && rel[1] == '\0') { + strncpy(abs, NRF54L15_FS_MOUNT, abssz - 1); + abs[abssz - 1] = '\0'; + } else if (rel[0] == '/') { + snprintf(abs, abssz, "%s%s", NRF54L15_FS_MOUNT, rel); + } else { + snprintf(abs, abssz, "%s/%s", NRF54L15_FS_MOUNT, rel); + } +} + +// Strip mount-point prefix to get the FS-root-relative path ("/prefs/..."). +static void torel(const char *abs, char *rel, size_t relsz) +{ + const char *mp = NRF54L15_FS_MOUNT; + size_t mplen = strlen(mp); + if (strncmp(abs, mp, mplen) == 0) { + const char *suffix = abs + mplen; + if (suffix[0] == '\0') { + strncpy(rel, "/", relsz); + } else { + strncpy(rel, suffix, relsz - 1); + rel[relsz - 1] = '\0'; + } + } else { + strncpy(rel, abs, relsz - 1); + rel[relsz - 1] = '\0'; + } +} + +// ── InternalFileSystem methods ──────────────────────────────────────────── + +bool InternalFileSystem::begin() +{ + if (_mounted) + return true; + + int rc = fs_mount(&_lfs_mnt); + if (rc == 0) { + _mounted = true; + return true; + } + + // Mount failed: attempt to format (creates a fresh LittleFS) + LOG_WARN("LittleFS mount failed (%d), formatting storage partition...", rc); + int fmt_rc = fs_mkfs(FS_LITTLEFS, (uintptr_t)FIXED_PARTITION_ID(storage_partition), NULL, 0); + if (fmt_rc != 0) { + LOG_ERROR("LittleFS format failed (%d)", fmt_rc); + return false; + } + + rc = fs_mount(&_lfs_mnt); + if (rc == 0) { + _mounted = true; + return true; + } + + LOG_ERROR("LittleFS mount failed after format (%d)", rc); + return false; +} + +File InternalFileSystem::open(const char *path, const char *mode) +{ + if (!_mounted) + return File(); + + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + + auto s = std::make_shared(); + if (!s) + return File(); + + strncpy(s->fullpath, abs, sizeof(s->fullpath) - 1); + torel(abs, s->relpath, sizeof(s->relpath)); + + // Check whether the path is a directory + struct fs_dirent entry; + int stat_rc = fs_stat(abs, &entry); + if (stat_rc == 0 && entry.type == FS_DIR_ENTRY_DIR) { + s->is_dir = true; + if (fs_opendir(&s->dir, abs) == 0) { + s->valid = true; + return File(s); + } + return File(); + } + + // Open as a regular file + fs_mode_t flags; + if (strcmp(mode, FILE_O_WRITE) == 0) { + // Truncate on write — unlink first to ensure a clean start + fs_unlink(abs); + flags = FS_O_WRITE | FS_O_CREATE; + } else { + flags = FS_O_READ; + } + + if (fs_open(&s->file, abs, flags) == 0) { + s->is_dir = false; + s->valid = true; + return File(s); + } + + return File(); +} + +bool InternalFileSystem::exists(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + struct fs_dirent entry; + return fs_stat(abs, &entry) == 0; +} + +bool InternalFileSystem::remove(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + return fs_unlink(abs) == 0; +} + +bool InternalFileSystem::rename(const char *from, const char *to) +{ + if (!_mounted) + return false; + char absfrom[NRF54L15_FS_PATHLEN], absto[NRF54L15_FS_PATHLEN]; + toabs(from, absfrom, sizeof(absfrom)); + toabs(to, absto, sizeof(absto)); + return fs_rename(absfrom, absto) == 0; +} + +bool InternalFileSystem::mkdir(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + int rc = fs_mkdir(abs); + return rc == 0 || rc == -EEXIST; +} + +bool InternalFileSystem::rmdir(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + return fs_unlink(abs) == 0; +} + +bool InternalFileSystem::rmdir_r(const char *path) +{ + if (!_mounted) + return false; + char abs[NRF54L15_FS_PATHLEN]; + toabs(path, abs, sizeof(abs)); + + struct fs_dir_t dir; + fs_dir_t_init(&dir); + if (fs_opendir(&dir, abs) != 0) { + // Not a directory — try to delete as file + return fs_unlink(abs) == 0; + } + + struct fs_dirent entry; + char child[NRF54L15_FS_PATHLEN]; + while (fs_readdir(&dir, &entry) == 0 && entry.name[0] != '\0') { + snprintf(child, sizeof(child), "%s/%s", abs, entry.name); + if (entry.type == FS_DIR_ENTRY_DIR) { + // Recurse: pass the absolute path stripped of mount prefix + char childrel[NRF54L15_FS_PATHLEN]; + torel(child, childrel, sizeof(childrel)); + rmdir_r(childrel); + } else { + fs_unlink(child); + } + } + fs_closedir(&dir); + return fs_unlink(abs) == 0; +} + +bool InternalFileSystem::format() +{ + if (_mounted) { + fs_unmount(&_lfs_mnt); + _mounted = false; + } + int rc = fs_mkfs(FS_LITTLEFS, (uintptr_t)FIXED_PARTITION_ID(storage_partition), NULL, 0); + if (rc != 0) { + LOG_ERROR("LittleFS format failed (%d)", rc); + return false; + } + return begin(); +} + +// ── File::openNextFile ──────────────────────────────────────────────────── +// Defined here because it accesses Zephyr fs_readdir/fs_open APIs. + +File File::openNextFile() +{ + if (!_s || !_s->valid || !_s->is_dir) + return File(); + + struct fs_dirent entry; + if (fs_readdir(&_s->dir, &entry) != 0) + return File(); + if (entry.name[0] == '\0') + return File(); // end of directory + + char childabs[NRF54L15_FS_PATHLEN]; + snprintf(childabs, sizeof(childabs), "%s/%s", _s->fullpath, entry.name); + + auto s = std::make_shared(); + if (!s) + return File(); + + strncpy(s->fullpath, childabs, sizeof(s->fullpath) - 1); + torel(childabs, s->relpath, sizeof(s->relpath)); + + if (entry.type == FS_DIR_ENTRY_DIR) { + s->is_dir = true; + if (fs_opendir(&s->dir, childabs) == 0) { + s->valid = true; + return File(s); + } + } else { + s->is_dir = false; + if (fs_open(&s->file, childabs, FS_O_READ) == 0) { + s->valid = true; + return File(s); + } + } + return File(); +} diff --git a/src/platform/nrf54l15/InternalFileSystem.h b/src/platform/nrf54l15/InternalFileSystem.h new file mode 100644 index 000000000..d6b1a9d00 --- /dev/null +++ b/src/platform/nrf54l15/InternalFileSystem.h @@ -0,0 +1,212 @@ +// InternalFileSystem.h — Zephyr LittleFS backend for nRF54L15 +// +// Implements the Adafruit InternalFileSystem API subset used by Meshtastic, +// backed by Zephyr's fs/littlefs on the storage_partition of the nRF54L15's +// internal RRAM. Partition size is taken from the DTS at compile time via +// FIXED_PARTITION_SIZE(storage_partition) — the DK overlay currently maps +// ~700 KB into slot1 (see zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay). +// +// Mount point: /lfs +// All paths passed to open/exists/mkdir etc. are relative to the FS root +// (e.g. "/prefs/config.proto") and are prepended with "/lfs" internally. +// +// File objects are copyable via std::shared_ptr. +// The underlying Zephyr handle is closed when the last copy is destroyed. + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#ifndef FILE_O_READ +#define FILE_O_READ "r" +#define FILE_O_WRITE "w" +#endif + +#define NRF54L15_FS_MOUNT "/lfs" +#define NRF54L15_FS_PATHLEN 256 + +namespace Adafruit_LittleFS_Namespace +{ + +class InternalFileSystem; // forward + +// ── Internal file/dir state ─────────────────────────────────────────────── + +struct NRF54L15FileState { + bool valid = false; + bool is_dir = false; + + // Absolute Zephyr path, e.g. "/lfs/prefs/config.proto" + char fullpath[NRF54L15_FS_PATHLEN] = {0}; + // Path from FS root, e.g. "/prefs/config.proto" (returned by name()) + char relpath[NRF54L15_FS_PATHLEN] = {0}; + + struct fs_file_t file; + struct fs_dir_t dir; + + NRF54L15FileState() + { + fs_file_t_init(&file); + fs_dir_t_init(&dir); + } + + ~NRF54L15FileState() + { + if (valid) { + if (is_dir) + fs_closedir(&dir); + else + fs_close(&file); + valid = false; + } + } +}; + +// ── File ───────────────────────────────────────────────────────────────── + +class File +{ + public: + File() = default; + explicit File(InternalFileSystem &) {} // nRF52 compat constructor + + explicit operator bool() const { return _s && _s->valid; } + + int read(void *buf, uint16_t nbyte) + { + if (!_s || !_s->valid || _s->is_dir) + return -1; + ssize_t n = fs_read(&_s->file, buf, nbyte); + return n < 0 ? -1 : (int)n; + } + + int read() + { + uint8_t b; + return read(&b, 1) == 1 ? (int)b : -1; + } + + size_t write(const uint8_t *buf, size_t len) + { + if (!_s || !_s->valid || _s->is_dir) + return 0; + ssize_t n = fs_write(&_s->file, buf, len); + return n < 0 ? 0 : (size_t)n; + } + + size_t write(uint8_t b) { return write(&b, 1); } + + void flush() + { + if (_s && _s->valid && !_s->is_dir) + fs_sync(&_s->file); + } + + void close() { _s.reset(); } + + size_t size() + { + if (!_s || !_s->valid || _s->is_dir) + return 0; + struct fs_dirent entry; + if (fs_stat(_s->fullpath, &entry) == 0) + return (size_t)entry.size; + return 0; + } + + bool isDirectory() { return _s && _s->valid && _s->is_dir; } + + // Returns path from FS root, e.g. "/prefs/config.proto" + const char *name() { return _s ? _s->relpath : ""; } + + // Returns the next entry in a directory. Modifies the dir stream in _s. + File openNextFile(); + + void rewindDirectory() + { + if (!_s || !_s->valid || !_s->is_dir) + return; + // Zephyr has no rewinddir(); close + reopen the same handle. Skipping + // the close would leak the LittleFS dir state and the next openNextFile + // could return stale entries on some Zephyr versions. + fs_closedir(&_s->dir); + fs_dir_t_init(&_s->dir); + if (fs_opendir(&_s->dir, _s->fullpath) != 0) { + _s->valid = false; + } + } + + bool seek(uint32_t pos) + { + if (!_s || !_s->valid || _s->is_dir) + return false; + return fs_seek(&_s->file, (off_t)pos, FS_SEEK_SET) == 0; + } + + int available() + { + if (!_s || !_s->valid || _s->is_dir) + return 0; + off_t pos = fs_tell(&_s->file); + if (pos < 0) + return 0; + struct fs_dirent entry; + if (fs_stat(_s->fullpath, &entry) != 0) + return 0; + long rem = (long)entry.size - (long)pos; + return rem > 0 ? (int)rem : 0; + } + + int peek() { return -1; } + + // Internal: constructed by InternalFileSystem and openNextFile() + explicit File(std::shared_ptr s) : _s(std::move(s)) {} + + private: + std::shared_ptr _s; +}; + +// ── InternalFileSystem ──────────────────────────────────────────────────── + +class InternalFileSystem +{ + public: + bool begin(); + File open(const char *path, const char *mode); + bool exists(const char *path); + bool remove(const char *path); + bool rename(const char *from, const char *to); + bool mkdir(const char *path); + bool rmdir(const char *path); + bool rmdir_r(const char *path); // recursive delete (used by FSCommon rmDir) + uint32_t usedBytes() + { + struct fs_statvfs st = {}; + if (fs_statvfs(NRF54L15_FS_MOUNT, &st) != 0) + return 0; + // Zephyr returns block counts; convert to bytes. f_frsize is the + // fundamental fragment size (LittleFS reports it equal to the block + // size). used = (total - free) * frag_size. + if (st.f_blocks <= st.f_bfree) + return 0; + return (uint32_t)((st.f_blocks - st.f_bfree) * st.f_frsize); + } + uint32_t totalBytes() { return (uint32_t)FIXED_PARTITION_SIZE(storage_partition); } + bool format(); + + // Convert a FS-root-relative path to an absolute Zephyr path. + static void toabs(const char *rel, char *abs, size_t abssz); + + private: + bool _mounted = false; +}; + +} // namespace Adafruit_LittleFS_Namespace + +extern Adafruit_LittleFS_Namespace::InternalFileSystem InternalFS; diff --git a/src/platform/nrf54l15/NRF52Bluetooth.h b/src/platform/nrf54l15/NRF52Bluetooth.h new file mode 100644 index 000000000..13cc73485 --- /dev/null +++ b/src/platform/nrf54l15/NRF52Bluetooth.h @@ -0,0 +1,18 @@ +// NRF52Bluetooth.h — stub for nRF54L15/Zephyr +// main.h includes this when ARCH_NRF52 is defined. +// Bluetooth is excluded (MESHTASTIC_EXCLUDE_BLUETOOTH=1); this satisfies the +// include chain without pulling in the nRF52 Bluefruit SDK. +#pragma once + +class NRF52Bluetooth +{ + public: + void setup() {} + void shutdown() {} + void startDisabled() {} + void resumeAdvertising() {} + void clearBonds() {} + bool isConnected() { return false; } + int getRssi() { return 0; } + void sendLog(const uint8_t *, size_t) {} +}; diff --git a/src/platform/nrf54l15/NRF54L15Bluetooth.cpp b/src/platform/nrf54l15/NRF54L15Bluetooth.cpp new file mode 100644 index 000000000..1e9281b7b --- /dev/null +++ b/src/platform/nrf54l15/NRF54L15Bluetooth.cpp @@ -0,0 +1,805 @@ +// NRF54L15Bluetooth.cpp — Zephyr BLE GATT peripheral for Meshtastic nRF54L15 +// +// GATT profile (identical UUIDs to the nRF52 / NimBLE implementations): +// Service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd +// fromNum: ed9da18c-a800-4f66-a670-aa7547e34453 READ | NOTIFY +// fromRadio: 2c55e69e-4993-11ed-b878-0242ac120002 READ +// toRadio: f75c76d2-129e-4dad-a1dd-7866124401e7 WRITE +// logRadio: 5a3d6e49-06e6-4423-9944-e9de8cdf9547 READ | NOTIFY | INDICATE +// +// Threading model: +// - BT RX thread: connected_cb / disconnected_cb / GATT read_/write_ +// callbacks +// - Meshtastic OSThread scheduler (cooperative, main thread): +// BleDeferredThread +// polls pendingToRadio and runs the zombie-connection watchdog every 100 ms +// - PhoneAPI::onNowHasData: sends fromNum notify synchronously from whichever +// thread pushed the packet (bt_gatt_notify is thread-safe in Zephyr) +// - active_conn protected by ble_mutex where needed + +#include "NRF54L15Bluetooth.h" +#include "BluetoothCommon.h" +#include "BluetoothStatus.h" +#include "PowerFSM.h" +#include "concurrency/OSThread.h" +#include "configuration.h" +#include "main.h" +#include "mesh/PhoneAPI.h" +#include "mesh/mesh-pb-constants.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// ── UUID definitions (little-endian per Bluetooth spec) +// ─────────────────────── Syntax: replace hyphens with commas, prefix 0x — +// matches BT_UUID_128_ENCODE doc. + +#define MESH_SVC_UUID_VAL BT_UUID_128_ENCODE(0x6ba1b218, 0x15a8, 0x461f, 0x9fa8, 0x5dcae273eafd) +#define FROMNUM_UUID_VAL BT_UUID_128_ENCODE(0xed9da18c, 0xa800, 0x4f66, 0xa670, 0xaa7547e34453) +#define FROMRADIO_UUID_VAL BT_UUID_128_ENCODE(0x2c55e69e, 0x4993, 0x11ed, 0xb878, 0x0242ac120002) +#define TORADIO_UUID_VAL BT_UUID_128_ENCODE(0xf75c76d2, 0x129e, 0x4dad, 0xa1dd, 0x7866124401e7) +#define LOGRADIO_UUID_VAL BT_UUID_128_ENCODE(0x5a3d6e49, 0x06e6, 0x4423, 0x9944, 0xe9de8cdf9547) + +static const struct bt_uuid_128 mesh_svc_uuid = BT_UUID_INIT_128(MESH_SVC_UUID_VAL); +static const struct bt_uuid_128 fromnum_uuid = BT_UUID_INIT_128(FROMNUM_UUID_VAL); +static const struct bt_uuid_128 fromradio_uuid = BT_UUID_INIT_128(FROMRADIO_UUID_VAL); +static const struct bt_uuid_128 toradio_uuid = BT_UUID_INIT_128(TORADIO_UUID_VAL); +static const struct bt_uuid_128 logradio_uuid = BT_UUID_INIT_128(LOGRADIO_UUID_VAL); + +// ── Module state ───────────────────────────────────────────────────────────── + +static struct bt_conn *active_conn = nullptr; +static K_MUTEX_DEFINE(ble_mutex); + +// Take a reference to active_conn under ble_mutex. Returns nullptr if there is +// no active connection. Caller MUST bt_conn_unref() when done. +// +// Reading `active_conn` outside this lock races with disconnected_cb which can +// unref + null it on the BT RX thread — touching the freed pointer (even just +// to bt_conn_ref it) is a use-after-free. +static struct bt_conn *acquire_active_conn() +{ + struct bt_conn *conn = nullptr; + k_mutex_lock(&ble_mutex, K_FOREVER); + if (active_conn) { + conn = bt_conn_ref(active_conn); + } + k_mutex_unlock(&ble_mutex); + return conn; +} + +static bool bt_initialized = false; // bt_enable() called at most once +static bool ble_enabled = false; // set by setup(), cleared by shutdown() + +// Forward declarations — BT_GATT_SERVICE_DEFINE(mesh_svc, ...) is below, but +// read_fromradio() (defined earlier) needs to reference the service to notify +// on fromNum after each non-empty read. +#define FROMNUM_ATTR_IDX 2 +#define LOGRADIO_ATTR_IDX 9 +extern const struct bt_gatt_service_static mesh_svc; + +static void start_advertising(); // forward declaration (defined in advertising + // section below) + +// Work item for advertising restart after disconnect. +// +// disconnected_cb runs on the BT RX thread (the same thread that processes +// HCI Command Complete events). Calling bt_le_adv_start() → +// bt_hci_cmd_send_sync() directly from that thread deadlocks: the thread blocks +// on k_sem_take waiting for Command Complete, but it is the very thread that +// would process it. After 10 s the host panics with "Controller unresponsive, +// opcode 0x2006 timeout". +// +// Fix: submit a k_work item. The system workqueue runs bt_adv_restart_work_fn +// on its own thread → no deadlock. +static struct k_work adv_restart_work; + +static void adv_restart_work_fn(struct k_work *work) +{ + if (ble_enabled) { + start_advertising(); + } +} + +// CCC state: 0=off, BT_GATT_CCC_NOTIFY=notify, BT_GATT_CCC_INDICATE=indicate +static uint16_t fromnum_ccc_val = 0; +static uint16_t logradio_ccc_val = 0; + +// Scratch buffers — only one BLE operation at a time +static uint8_t fromRadioBytes[meshtastic_FromRadio_size]; +static size_t fromRadioLen = 0; +static uint8_t toRadioBytes[meshtastic_ToRadio_size]; +static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; +static uint32_t fromNumValue = 0; + +// Deferred ToRadio processing +// +// write_toradio() runs on the BT RX workqueue thread (6 KB stack). Calling +// phoneAPI->handleToRadio() directly triggers handleStartConfig → +// getFiles("/", 10) → nanopb encode, which overflows the stack on the exact +// "Client wants config" write. Instead we copy the payload into a pending +// buffer under a mutex and let BleDeferredThread (running on the Meshtastic +// OSThread scheduler, 24 KB stack) do the actual call outside the lock. +// +// The mutex makes the producer/consumer handoff race-free — producer may +// overwrite a pending buffer the consumer hasn't read yet (dropped packet), +// but partial reads / torn writes are impossible. +K_MUTEX_DEFINE(pendingToRadioMutex); +static uint8_t pendingToRadioBuf[MAX_TO_FROM_RADIO_SIZE]; +static size_t pendingToRadioLen = 0; +static bool pendingToRadio = false; + +// Zombie-connection watchdog state. +// +// The nRF54L15 Zephyr 4.2.1 SW-LL occasionally fails to forward an +// LE Disconnection Complete event to the host: when iOS tears down the link +// (either explicitly by the user or via supervision timeout), the LL layer +// drops the connection but disconnected_cb never fires, active_conn stays +// non-null and advertising never restarts — the device vanishes from scans +// until power cycle. Track the connected timestamp and the last time we +// observed ATT traffic; a long ATT idle on an "active" connection means we +// are zombied. A cold reboot is the only path that reliably recovers (any +// bt_hci_cmd_send_sync after this state, e.g. bt_le_adv_start or +// bt_conn_disconnect, hangs in k_sem_take and later panics with "Controller +// unresponsive, opcode 0x2006 timeout"). +static uint32_t connect_time_ms = 0; +static uint32_t last_att_time_ms = 0; + +// ── BluetoothPhoneAPI +// ───────────────────────────────────────────────────────── + +class BluetoothPhoneAPI : public PhoneAPI +{ + virtual void onNowHasData(uint32_t fromRadioNum) override; + virtual bool checkIsConnected() override; + + public: + BluetoothPhoneAPI() { api_type = TYPE_BLE; } +}; + +static BluetoothPhoneAPI *phoneAPI = nullptr; + +// ── CCC change callbacks +// ────────────────────────────────────────────────────── + +static void fromnum_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + fromnum_ccc_val = value; + LOG_INFO("BLE fromNum CCC: %u", value); +} + +static void logradio_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + logradio_ccc_val = value; + LOG_INFO("BLE logRadio CCC: %u", value); +} + +// ── GATT attribute callbacks +// ────────────────────────────────────────────────── + +static ssize_t read_fromnum(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) +{ + LOG_INFO("GATT read_fromnum: fromNum=%u offset=%u", fromNumValue, offset); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &fromNumValue, sizeof(fromNumValue)); +} + +static ssize_t read_fromradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) +{ + if (offset == 0) { + // First chunk: pull the next packet from the queue. + // Subsequent chunks (offset > 0) are ATT_READ_BLOB continuations of the + // same value and must reuse fromRadioBytes untouched. + fromRadioLen = phoneAPI ? phoneAPI->getFromRadio(fromRadioBytes) : 0; + LOG_DEBUG("GATT read_fromradio len=%u", (unsigned)fromRadioLen); + } + last_att_time_ms = k_uptime_get_32(); + return bt_gatt_attr_read(conn, attr, buf, len, offset, fromRadioBytes, fromRadioLen); +} + +static ssize_t read_logradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) +{ + // logRadio is write-only from the device side (notify/indicate). + // Return an empty read so GATT discovery doesn't fail with NOT_PERMITTED. + return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); +} + +static ssize_t write_toradio(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + // Writes >MTU-3 arrive here with offset=0 and + // flags=BT_GATT_WRITE_FLAG_EXECUTE after Zephyr reassembles the ATT Prepare + // Write fragments (CONFIG_BT_ATT_PREPARE_COUNT>0). Single writes arrive with + // flags=0. + LOG_DEBUG("GATT write_toradio len=%u flags=0x%x", len, flags); + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + // Reject any write that won't fit in the dedup buffer (lastToRadio) or the + // pending handoff buffer (pendingToRadioBuf), both sized + // MAX_TO_FROM_RADIO_SIZE. Returning success while silently dropping a + // payload would let the phone believe a config write was applied. + if (len > sizeof(toRadioBytes) || len > MAX_TO_FROM_RADIO_SIZE) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + // Deduplicate — drop packet if identical to the last one we processed + if (memcmp(lastToRadio, buf, len) != 0) { + memcpy(lastToRadio, buf, len); + if (len < MAX_TO_FROM_RADIO_SIZE) { + memset(lastToRadio + len, 0, MAX_TO_FROM_RADIO_SIZE - len); + } + // Defer handleToRadio() to BleDeferredThread (24 KB stack). + // Running it here on bt_workq (6 KB) overflows during handleStartConfig. + // Always overwrite pending — we already dedup'd above via lastToRadio, + // so any new write here is genuinely new data that must be delivered. + k_mutex_lock(&pendingToRadioMutex, K_FOREVER); + memcpy(pendingToRadioBuf, buf, len); + pendingToRadioLen = len; + pendingToRadio = true; + k_mutex_unlock(&pendingToRadioMutex); + } + last_att_time_ms = k_uptime_get_32(); + return (ssize_t)len; +} + +// ── GATT service definition (static, linked at compile time) +// ────────────────── +// +// Attribute indices (0-based): +// [0] Primary Service declaration +// [1] fromNum characteristic declaration +// [2] fromNum value ← notify target (FROMNUM_ATTR_IDX) +// [3] fromNum CCC descriptor +// [4] fromRadio characteristic declaration +// [5] fromRadio value +// [6] toRadio characteristic declaration +// [7] toRadio value +// [8] logRadio characteristic declaration +// [9] logRadio value ← notify target (LOGRADIO_ATTR_IDX) +// [10] logRadio CCC descriptor + +// All user characteristics require authenticated encryption (MITM passkey) +// before the client can read/write. This mirrors the nrf52 +// SECMODE_ENC_WITH_MITM service permission. The stack returns "Insufficient +// Authentication" on the first access attempt, prompting the client to pair +// with the configured PIN. +#define MESH_PERM_READ (BT_GATT_PERM_READ | BT_GATT_PERM_READ_AUTHEN) +#define MESH_PERM_WRITE (BT_GATT_PERM_WRITE | BT_GATT_PERM_WRITE_AUTHEN) + +BT_GATT_SERVICE_DEFINE(mesh_svc, BT_GATT_PRIMARY_SERVICE(&mesh_svc_uuid.uuid), + + // fromNum: READ | NOTIFY — packet-counter triggers phone to read fromRadio + BT_GATT_CHARACTERISTIC(&fromnum_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, MESH_PERM_READ, + read_fromnum, NULL, &fromNumValue), + BT_GATT_CCC(fromnum_ccc_changed, MESH_PERM_READ | MESH_PERM_WRITE), + + // fromRadio: READ — phone polls this after receiving a fromNum notification + BT_GATT_CHARACTERISTIC(&fromradio_uuid.uuid, BT_GATT_CHRC_READ, MESH_PERM_READ, read_fromradio, NULL, + NULL), + + // toRadio: WRITE — phone sends protobuf packets to the device + BT_GATT_CHARACTERISTIC(&toradio_uuid.uuid, BT_GATT_CHRC_WRITE, MESH_PERM_WRITE, NULL, write_toradio, NULL), + + // logRadio: READ | NOTIFY | INDICATE — log stream to phone when connected + BT_GATT_CHARACTERISTIC(&logradio_uuid.uuid, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE, MESH_PERM_READ, + read_logradio, NULL, NULL), + BT_GATT_CCC(logradio_ccc_changed, MESH_PERM_READ | MESH_PERM_WRITE), ); + +// ── Advertising +// ─────────────────────────────────────────────────────────────── +// +// Use legacy advertising (bt_le_adv_start / HCI 0x2006 path). +// +// History: we previously used bt_le_ext_adv_create (true extended advertising) +// because bt_le_adv_start() with CONFIG_BT_EXT_ADV=y was translated internally +// to the extended HCI path with LEGACY-bit (0x2036), which produced +// non-connectable PDUs on the nRF54L15 SW-LL. The true extended path +// (0x203x, AUX_ADV_IND) was connectable but caused two problems: +// 1. iOS CoreBluetooth does not reliably complete GATT after connecting via +// extended advertising (zero ATT PDUs observed in all test sessions). +// 2. After each connection the controller auto-stops the advertising set, and +// the subsequent bt_le_ext_adv_delete() sends LE Remove Advertising Set +// (0x203c) which times out → kernel oops at hci_core.c:506. +// +// With CONFIG_BT_EXT_ADV=n the host uses pure legacy HCI commands — the same +// path Nordic NCS uses in all nRF54L15 examples (peripheral_uart, +// peripheral_lbs) and which is universally iOS-compatible. The legacy data +// payload is 31 bytes: +// FLAGS (3B) + UUID128 (18B) = 21B in adv; NAME in scan-response (17B). + +static void start_advertising() +{ + // IMPORTANT: BT_DATA_BYTES() uses C99 compound literals that GCC C++ treats + // as temporaries; with -Os the compiler may elide writes, leaving stack + // uninitialized. Use static const arrays for stable data (flags, UUID) + // and a runtime pointer for the dynamic device name. + static const uint8_t adv_flags_val[] = {BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR}; + static const uint8_t adv_uuid128_val[] = {MESH_SVC_UUID_VAL}; + + const char *name = bt_get_name(); + size_t full_name_len = strlen(name); + + // Legacy scan-response payload is 31 bytes total. Each AD entry costs 2 + // bytes (length + type), leaving 29 bytes for the name. With + // CONFIG_BT_DEVICE_NAME_MAX=32 the name can exceed that — truncate and + // mark as SHORTENED so bt_le_adv_start() doesn't reject the payload. + constexpr size_t LEGACY_SCAN_RSP_NAME_MAX = 31 - 2; + bool name_shortened = full_name_len > LEGACY_SCAN_RSP_NAME_MAX; + uint8_t name_len = (uint8_t)(name_shortened ? LEGACY_SCAN_RSP_NAME_MAX : full_name_len); + + // Primary advertising data: FLAGS + Meshtastic service UUID128 (21 bytes + // total) + struct bt_data ad[] = { + {BT_DATA_FLAGS, sizeof(adv_flags_val), adv_flags_val}, + {BT_DATA_UUID128_ALL, sizeof(adv_uuid128_val), adv_uuid128_val}, + }; + // Scan response: device name (discovered after scan request) + struct bt_data sd[] = { + {(uint8_t)(name_shortened ? BT_DATA_NAME_SHORTENED : BT_DATA_NAME_COMPLETE), name_len, (const uint8_t *)name}, + }; + + // BT_LE_ADV_OPT_CONN = connectable legacy ADV_IND + stops after first + // connection (replaces deprecated + // CONNECTABLE|ONE_TIME in Zephyr 4.2.1; + // BT_LE_ADV_OPT_CONN = BIT(0)|BIT(1)) + // BT_LE_ADV_OPT_USE_IDENTITY = use static random identity address (stable + // across reboots) Advertising restart after disconnect is via + // adv_restart_work (system workqueue) so calling bt_le_adv_start() from the + // BT RX thread context is avoided. + int err = bt_le_adv_start(BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_IDENTITY, BT_GAP_ADV_FAST_INT_MIN_2, + BT_GAP_ADV_FAST_INT_MAX_2, NULL), + ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); + + if (err == -EALREADY) { + return; + } + if (err) { + LOG_WARN("BLE adv start failed: %d", err); + } else { + LOG_INFO("BLE advertising as '%s'", bt_get_name()); + } +} + +static void stop_advertising() +{ + bt_le_adv_stop(); +} + +// ── Connection callbacks +// ────────────────────────────────────────────────────── + +static void connected_cb(struct bt_conn *conn, uint8_t err) +{ + if (err) { + LOG_WARN("BLE connection failed, err=0x%02x", err); + return; + } + + k_mutex_lock(&ble_mutex, K_FOREVER); + active_conn = bt_conn_ref(conn); + k_mutex_unlock(&ble_mutex); + + memset(lastToRadio, 0, sizeof(lastToRadio)); + connect_time_ms = k_uptime_get_32(); + last_att_time_ms = connect_time_ms; + + char addr[BT_ADDR_LE_STR_LEN]; + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + LOG_INFO("BLE connected: %s", addr); + + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); + + // nRF54L15-DK has no screen — cannot display a PIN to the user. + // Requesting BT_SECURITY_L2 causes the OS to show a pairing dialog that + // the user dismisses, triggering disconnect + advertising restart failure. + // Skip security negotiation; the Meshtastic app works over plain GATT. + // (Security can be re-enabled once a display or NFC OOB path is available.) +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + LOG_INFO("BLE disconnected, reason=0x%02x", reason); + + k_mutex_lock(&ble_mutex, K_FOREVER); + if (active_conn) { + bt_conn_unref(active_conn); + active_conn = nullptr; + } + k_mutex_unlock(&ble_mutex); + + fromnum_ccc_val = 0; + logradio_ccc_val = 0; + connect_time_ms = 0; + last_att_time_ms = 0; + + if (phoneAPI) { + phoneAPI->close(); + } + memset(lastToRadio, 0, sizeof(lastToRadio)); + + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); + + // Schedule advertising restart via work queue — NOT from this callback + // directly. disconnected_cb runs on the BT RX thread; calling + // bt_le_adv_start() here would deadlock (see adv_restart_work comment above). + if (ble_enabled) { + k_work_submit(&adv_restart_work); + } +} + +#if defined(CONFIG_BT_SMP) +static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) +{ + if (err == BT_SECURITY_ERR_PIN_OR_KEY_MISSING) { + // Phone has a stale bond (device was wiped/reflashed). Unpair the stale + // entry so the phone re-pairs cleanly on the next connection attempt. + LOG_WARN("BLE stale bond detected (key missing) — unpairing"); + bt_unpair(BT_ID_DEFAULT, bt_conn_get_dst(conn)); + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + } else if (err) { + LOG_WARN("BLE security change failed: level=%d err=%d", (int)level, (int)err); + } else { + LOG_INFO("BLE security level %d established", (int)level); + } +} +#endif /* CONFIG_BT_SMP */ + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected_cb, + .disconnected = disconnected_cb, +#if defined(CONFIG_BT_SMP) + .security_changed = security_changed_cb, +#endif +}; + +// ── Pairing / auth callbacks +// ────────────────────────────────────────────────── + +#if defined(CONFIG_BT_SMP) +static uint32_t configuredPasskey; + +static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey) +{ + char passkey_str[7]; + snprintf(passkey_str, sizeof(passkey_str), "%06u", passkey); + configuredPasskey = passkey; + LOG_INFO("BLE pairing PIN: %s", passkey_str); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + + std::string textkey(passkey_str); + meshtastic::BluetoothStatus pairingStatus(textkey); + bluetoothStatus->updateStatus(&pairingStatus); +} + +static void auth_cancel(struct bt_conn *conn) +{ + LOG_WARN("BLE pairing cancelled"); +} + +static struct bt_conn_auth_cb auth_cb = { + .passkey_display = auth_passkey_display, + .passkey_entry = NULL, + .cancel = auth_cancel, +}; + +static void pairing_complete_cb(struct bt_conn *conn, bool bonded) +{ + LOG_INFO("BLE pairing complete, bonded=%d", (int)bonded); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); +} + +static void pairing_failed_cb(struct bt_conn *conn, enum bt_security_err reason) +{ + LOG_WARN("BLE pairing failed, reason=%d", (int)reason); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); +} + +static struct bt_conn_auth_info_cb auth_info_cb = { + .pairing_complete = pairing_complete_cb, + .pairing_failed = pairing_failed_cb, +}; +#endif /* CONFIG_BT_SMP */ + +// ── BluetoothPhoneAPI methods +// ───────────────────────────────────────────────── + +void BluetoothPhoneAPI::onNowHasData(uint32_t fromRadioNum) +{ + PhoneAPI::onNowHasData(fromRadioNum); + fromNumValue = fromRadioNum; + + if (!(fromnum_ccc_val & BT_GATT_CCC_NOTIFY)) + return; + + // active_conn may be torn down on another thread while we're dispatching + // this notify — acquire under ble_mutex so disconnected_cb can't free the + // conn between the null check and bt_conn_ref. + struct bt_conn *conn = acquire_active_conn(); + if (!conn) + return; + bt_gatt_notify(conn, &mesh_svc.attrs[FROMNUM_ATTR_IDX], &fromNumValue, sizeof(fromNumValue)); + bt_conn_unref(conn); +} + +bool BluetoothPhoneAPI::checkIsConnected() +{ + return active_conn != nullptr; +} + +// ── Deferred ToRadio processor + zombie-connection watchdog ────────────────── +// +// write_toradio() runs on the BT RX workqueue thread (CONFIG_BT_RX_STACK_SIZE) +// and cannot execute phoneAPI->handleToRadio() directly: handleStartConfig +// recurses through nanopb encode + state machine init and overflows the RX +// stack. This thread runs on the Meshtastic OSThread scheduler (24 KB stack), +// picks up the pending ToRadio buffer flagged by write_toradio(), and calls +// handleToRadio() with plenty of headroom. +// +// Real-time fromNum notifications are sent synchronously from +// BluetoothPhoneAPI::onNowHasData() (called by PhoneAPI when new data is +// queued). +// +// Zombie-connection detection has two tiers: +// +// (1) Liveness probe. After IDLE_BEFORE_PROBE_MS without ATT traffic, send +// a bt_gatt_notify to fromNum every PROBE_INTERVAL_MS. If the +// controller replies -ENOTCONN the LL link is definitely dead but the +// host didn't forward LE Disconnection Complete → reboot. We avoid +// probing during normal activity so iOS isn't woken up unnecessarily +// (each probe wakes iOS → triggers a zero-byte FromRadio drain). +// +// (2) Hard watchdog. Absolute HARD_WATCHDOG_MS ceiling on ATT idle as a +// fallback if probes somehow don't detect the zombie. +class BleDeferredThread : public concurrency::OSThread +{ + static constexpr uint32_t IDLE_BEFORE_PROBE_MS = 30000; // 30 s: start probing + static constexpr uint32_t PROBE_INTERVAL_MS = 5000; // 5 s: between probes + static constexpr uint32_t HARD_WATCHDOG_MS = 60000; // 1 min: last resort + + uint32_t last_probe_ms = 0; + + public: + BleDeferredThread() : concurrency::OSThread("BleDeferred") {} + + protected: + int32_t runOnce() override + { + // Snapshot the pending ToRadio buffer under the mutex, then release + // the lock before calling into handleToRadio (which can be slow and + // must not block the BT RX thread producer). + uint8_t buf[MAX_TO_FROM_RADIO_SIZE]; + size_t n = 0; + bool have_pending = false; + k_mutex_lock(&pendingToRadioMutex, K_FOREVER); + if (pendingToRadio) { + memcpy(buf, pendingToRadioBuf, pendingToRadioLen); + n = pendingToRadioLen; + pendingToRadio = false; + have_pending = true; + } + k_mutex_unlock(&pendingToRadioMutex); + if (have_pending && phoneAPI) { + phoneAPI->handleToRadio(buf, n); + } + + // Take a reference to active_conn so it can't be freed underneath us + // if disconnected_cb fires on another thread while we're dispatching. + struct bt_conn *conn = acquire_active_conn(); + if (!conn || connect_time_ms == 0) { + if (conn) + bt_conn_unref(conn); + last_probe_ms = 0; + return 100; + } + + uint32_t now = k_uptime_get_32(); + uint32_t att_idle = now - last_att_time_ms; + + // Liveness probe — only when ATT has been quiet for a while. + if (att_idle > IDLE_BEFORE_PROBE_MS && (now - last_probe_ms) >= PROBE_INTERVAL_MS && + (fromnum_ccc_val & BT_GATT_CCC_NOTIFY)) { + last_probe_ms = now; + int err = bt_gatt_notify(conn, &mesh_svc.attrs[FROMNUM_ATTR_IDX], &fromNumValue, sizeof(fromNumValue)); + if (err == -ENOTCONN) { + LOG_WARN("BLE zombie (probe ENOTCONN); rebooting"); + bt_conn_unref(conn); + k_sleep(K_MSEC(50)); // flush log + sys_reboot(SYS_REBOOT_COLD); + } + } + bt_conn_unref(conn); + + // Hard ceiling — last-resort reboot if probes miss the zombie. + if (att_idle > HARD_WATCHDOG_MS && (now - connect_time_ms) > HARD_WATCHDOG_MS) { + LOG_WARN("BLE zombie (hard watchdog %us); rebooting", HARD_WATCHDOG_MS / 1000); + k_sleep(K_MSEC(50)); + sys_reboot(SYS_REBOOT_COLD); + } + return 100; + } +}; + +static BleDeferredThread *bleDeferredThread = nullptr; + +// ── BT stack pre-initializer (call from main thread before OSThreads start) ── +// +// bt_enable() requires substantially more stack than a Meshtastic OSThread +// (PowerFSMThread) provides — calling it there causes a stack overflow. +// Call this from nrf54l15Setup() (main Zephyr thread, CONFIG_MAIN_STACK_SIZE) +// so that by the time NRF54L15Bluetooth::setup() runs from PowerFSMThread, +// bt_initialized is already true and bt_enable() is skipped. + +void nrf54l15_bt_preinit() +{ + if (!bt_initialized) { + int err = bt_enable(NULL); + if (err) { + LOG_ERROR("BLE pre-init failed: %d", err); + return; + } + bt_initialized = true; + LOG_INFO("BLE stack pre-initialized on main thread"); + + // Phase 7: load bonding keys from LittleFS (/lfs/bt_settings). + // LittleFS is already mounted by fsInit() before nrf54l15Setup() runs. + // On first boot the file doesn't exist — settings_load() returns 0 (OK). + // On subsequent boots, previously bonded peers are restored so the + // phone can reconnect without re-pairing. + err = settings_load(); + if (err) { + LOG_WARN("settings_load failed: %d (OK on first boot)", err); + } else { + LOG_INFO("BT settings loaded from /lfs/bt_settings"); + } + } +} + +// ── NRF54L15Bluetooth public methods ───────────────────────────────────────── + +// Shared init: idempotent setup of work item, OSThread, auth callbacks, +// bt_enable, and device name. Leaves advertising control to the caller. +static bool nrf54l15_bt_init_common() +{ + k_work_init(&adv_restart_work, adv_restart_work_fn); + + if (!bleDeferredThread) { + bleDeferredThread = new BleDeferredThread(); + } + + if (!phoneAPI) { + phoneAPI = new BluetoothPhoneAPI(); + } + +#if defined(CONFIG_BT_SMP) + // NO_PIN is unsupported on this platform: the mesh GATT permissions are + // declared with BT_GATT_PERM_*_AUTHEN, prj.conf sets + // CONFIG_BT_SMP_ENFORCE_MITM=y, and the build pulls in the SMP/passkey path. + // If a user requested NO_PIN we'd register no auth callbacks → no display + // path for the passkey → every GATT access returns BT_ATT_ERR_AUTHENTICATION + // and the link is unusable. Fall back to RANDOM_PIN behavior with a warning + // instead of leaving BLE silently broken. + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { + LOG_WARN("BLE: NO_PIN not supported on nRF54L15-DK (MITM-only build); " + "treating as RANDOM_PIN"); + } + + bt_conn_auth_cb_register(&auth_cb); + bt_conn_auth_info_cb_register(&auth_info_cb); + + // FIXED_PIN — register the configured passkey so the mobile app prompts + // the user for that specific number instead of a random display-only PIN. + // RANDOM_PIN (and clamped NO_PIN) keeps the default behavior: Zephyr + // generates a fresh passkey on each pairing attempt and fires + // auth_passkey_display with it. + if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN) { + configuredPasskey = config.bluetooth.fixed_pin; + int rc = bt_passkey_set(configuredPasskey); + if (rc) { + LOG_WARN("bt_passkey_set(%u) failed: %d", configuredPasskey, rc); + } else { + LOG_INFO("BLE fixed PIN: %06u", configuredPasskey); + } + } else { + bt_passkey_set(BT_PASSKEY_INVALID); // random per-pair + } +#endif /* CONFIG_BT_SMP */ + + if (!bt_initialized) { + int err = bt_enable(NULL); + if (err) { + LOG_ERROR("BLE enable failed: %d", err); + return false; + } + bt_initialized = true; + LOG_INFO("BLE stack enabled"); + } + + bt_set_name(getDeviceName()); + return true; +} + +void NRF54L15Bluetooth::setup() +{ + LOG_INFO("NRF54L15Bluetooth::setup()"); + if (!nrf54l15_bt_init_common()) { + return; + } + ble_enabled = true; + start_advertising(); +} + +void NRF54L15Bluetooth::shutdown() +{ + LOG_INFO("NRF54L15Bluetooth::shutdown()"); + ble_enabled = false; + stop_advertising(); + + struct bt_conn *conn = acquire_active_conn(); + if (conn) { + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + } +} + +void NRF54L15Bluetooth::startDisabled() +{ + // Initialize BT stack but leave advertising off until resumeAdvertising(). + if (!nrf54l15_bt_init_common()) { + return; + } + ble_enabled = false; + LOG_INFO("BLE initialized, advertising stopped (startDisabled)"); +} + +void NRF54L15Bluetooth::resumeAdvertising() +{ + ble_enabled = true; + start_advertising(); +} + +void NRF54L15Bluetooth::clearBonds() +{ + LOG_INFO("BLE clear bonds"); + bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY); +} + +bool NRF54L15Bluetooth::isConnected() +{ + return active_conn != nullptr; +} + +int NRF54L15Bluetooth::getRssi() +{ + return 0; // TODO: Zephyr has no direct bt_conn_get_rssi; use HCI RSSI read + // command +} + +void NRF54L15Bluetooth::sendLog(const uint8_t *logMessage, size_t length) +{ + if (length > 512 || logradio_ccc_val == 0) { + return; + } + // Acquire a reference under ble_mutex so disconnected_cb can't free the + // connection between the null check and bt_gatt_notify. + struct bt_conn *conn = acquire_active_conn(); + if (!conn) { + return; + } + // Send as notify regardless of whether client subscribed to NOTIFY or + // INDICATE — bt_gatt_indicate() requires a params struct with a callback; + // notify is simpler and the app accepts both. Change to indicate if + // compatibility issues arise. + bt_gatt_notify(conn, &mesh_svc.attrs[LOGRADIO_ATTR_IDX], logMessage, (uint16_t)length); + bt_conn_unref(conn); +} diff --git a/src/platform/nrf54l15/NRF54L15Bluetooth.h b/src/platform/nrf54l15/NRF54L15Bluetooth.h new file mode 100644 index 000000000..499e91f98 --- /dev/null +++ b/src/platform/nrf54l15/NRF54L15Bluetooth.h @@ -0,0 +1,29 @@ +// NRF54L15Bluetooth.h — Zephyr BLE backend for nRF54L15 +// +// Implements the same interface as NRF52Bluetooth (same method names and +// signatures) so main.cpp and AdminModule can use nrf52Bluetooth pointer +// without knowing the underlying implementation. +// +// GATT profile is identical to the nRF52 implementation: +// Service: MESH_SERVICE_UUID +// toRadio: TORADIO_UUID (WRITE) +// fromRadio: FROMRADIO_UUID (READ) +// fromNum: FROMNUM_UUID (READ | NOTIFY) +// logRadio: LOGRADIO_UUID (READ | NOTIFY | INDICATE) + +#pragma once + +#include "BluetoothCommon.h" + +class NRF54L15Bluetooth : public BluetoothApi +{ + public: + void setup(); + void shutdown(); + void startDisabled(); + void resumeAdvertising(); + void clearBonds(); + bool isConnected(); + int getRssi(); + void sendLog(const uint8_t *logMessage, size_t length); +}; diff --git a/src/platform/nrf54l15/Nrf52SaadcLock.h b/src/platform/nrf54l15/Nrf52SaadcLock.h new file mode 100644 index 000000000..deb43a7a6 --- /dev/null +++ b/src/platform/nrf54l15/Nrf52SaadcLock.h @@ -0,0 +1,17 @@ +// Nrf52SaadcLock.h — stub for nRF54L15/Zephyr +// Power.cpp includes this when ARCH_NRF52 is defined. +// Phase 2: compile-only stub. +#pragma once + +#ifdef ARCH_NRF52 + +#include "concurrency/Lock.h" + +namespace concurrency +{ +/** Shared mutex for SAADC configuration and reads (VDD + battery analog path). + * On nRF54L15 ADC is handled differently; this is a compile-only stub. */ +extern Lock *nrf52SaadcLock; +} // namespace concurrency + +#endif diff --git a/src/platform/nrf54l15/Print.h b/src/platform/nrf54l15/Print.h new file mode 100644 index 000000000..5ff8ed016 --- /dev/null +++ b/src/platform/nrf54l15/Print.h @@ -0,0 +1,4 @@ +// Print.h — shim for nRF54L15/Zephyr +// Meshtastic includes separately; redirect to our Arduino.h shim. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/SPI.h b/src/platform/nrf54l15/SPI.h new file mode 100644 index 000000000..b95890b0e --- /dev/null +++ b/src/platform/nrf54l15/SPI.h @@ -0,0 +1,62 @@ +/** + * SPI.h — Arduino SPI shim for Zephyr/nRF54L15 + * + * Provides the Arduino SPIClass interface backed by Zephyr's SPI API. The + * backing controller is SPIM00 (HP domain, 3.0 V); the implementation in + * nrf54l15_arduino.cpp binds to DEVICE_DT_GET(DT_NODELABEL(spi00)) and the + * bus is configured in zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay. + * RadioLib uses ArduinoHal which calls transfer() byte-by-byte. + * + * CS pin is handled by RadioLib via digitalWrite() — hardware CS is not used. + */ + +#pragma once + +#include "Arduino.h" +#include + +#define SPI_MODE0 0 +#define SPI_MODE1 1 +#define SPI_MODE2 2 +#define SPI_MODE3 3 + +struct SPISettings { + uint32_t clock; + uint8_t bitOrder; + uint8_t dataMode; + + // Arduino API allows `SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0))` — implicit form is intentional. + // cppcheck-suppress noExplicitConstructor + SPISettings(uint32_t clock = 4000000, uint8_t bitOrder = MSBFIRST, uint8_t dataMode = SPI_MODE0) + : clock(clock), bitOrder(bitOrder), dataMode(dataMode) + { + } +}; + +class SPIClass +{ + public: + void begin() {} + void begin(uint8_t sck, uint8_t miso, uint8_t mosi, uint8_t ss = 0xFF) {} + void end() {} + void beginTransaction(SPISettings) {} + void endTransaction() {} + void setBitOrder(uint8_t order) {} + void setDataMode(uint8_t mode) {} + void setClockDivider(uint8_t div) {} + void setFrequency(uint32_t freq) {} + + // Real Zephyr SPI implementation — defined in nrf54l15_arduino.cpp + uint8_t transfer(uint8_t data); + uint16_t transfer16(uint16_t data); + void transfer(void *buf, size_t count); + void transferBytes(const uint8_t *tx, uint8_t *rx, uint32_t count); + uint8_t transfer(uint8_t tx, uint8_t *rx, uint32_t count) + { + transferBytes(&tx, rx, count); + return rx ? rx[0] : 0; + } +}; + +extern SPIClass SPI; +extern SPIClass SPI1; diff --git a/src/platform/nrf54l15/Stream.h b/src/platform/nrf54l15/Stream.h new file mode 100644 index 000000000..a20f241da --- /dev/null +++ b/src/platform/nrf54l15/Stream.h @@ -0,0 +1,5 @@ +// Stream.h — shim for nRF54L15/Zephyr +// StreamAPI.h and other Meshtastic headers include . +// Redirect to our Arduino.h shim which defines the Stream base class. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/Tone.h b/src/platform/nrf54l15/Tone.h new file mode 100644 index 000000000..68dc98b7b --- /dev/null +++ b/src/platform/nrf54l15/Tone.h @@ -0,0 +1,4 @@ +// Tone.h — shim for nRF54L15/Zephyr +// Tone functions are stubbed in Arduino.h; this header satisfies direct includes. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/WProgram.h b/src/platform/nrf54l15/WProgram.h new file mode 100644 index 000000000..e602f1ee6 --- /dev/null +++ b/src/platform/nrf54l15/WProgram.h @@ -0,0 +1,5 @@ +// WProgram.h — shim for nRF54L15/Zephyr +// ArduinoThread (and other legacy Arduino libs) include . +// Redirect to our Arduino.h shim. +#pragma once +#include "Arduino.h" diff --git a/src/platform/nrf54l15/Wire.cpp b/src/platform/nrf54l15/Wire.cpp new file mode 100644 index 000000000..2fc0acab7 --- /dev/null +++ b/src/platform/nrf54l15/Wire.cpp @@ -0,0 +1,219 @@ +// Wire.cpp — Arduino TwoWire backed by Zephyr i2c30 (TWIM30 hardware). +// +// The pinctrl + clock-frequency are configured in +// zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay. Runtime begin()/setClock() +// are best-effort: setClock() goes through i2c_configure() to actually change +// the bus speed; begin() just verifies the device is ready. + +#include "Wire.h" +#include "configuration.h" + +#include +#include +#include +#include + +// Resolve the i2c30 node at compile time. If the overlay has not enabled +// i2c30, this evaluates to a NULL device pointer and every call short-circuits +// to a NACK return code — matching the prior compile-only stub behavior. +#define I2C_NODE DT_NODELABEL(i2c30) + +static const struct device *getI2CDevice() +{ +#if DT_NODE_HAS_STATUS(I2C_NODE, okay) + static const struct device *const dev = DEVICE_DT_GET(I2C_NODE); + return dev; +#else + return nullptr; +#endif +} + +// Wire/Wire1 instances are defined in nrf54l15_arduino.cpp alongside the +// other Arduino singletons (Serial, SPI, …). + +TwoWire::TwoWire() : txAddr(0), txLen(0), txBuf{}, rxLen(0), rxPos(0), rxBuf{} {} + +void TwoWire::begin() +{ + const struct device *dev = getI2CDevice(); + if (dev == nullptr) { + LOG_WARN("Wire.begin(): i2c30 not enabled in DT overlay"); + return; + } + if (!device_is_ready(dev)) { + LOG_WARN("Wire.begin(): i2c30 device not ready"); + return; + } + LOG_INFO("Wire.begin(): i2c30 ready"); +} + +void TwoWire::begin(uint8_t /*sda*/, uint8_t /*scl*/) +{ + // SDA/SCL fixed by overlay pinctrl — pin args ignored. + begin(); +} + +void TwoWire::begin(int /*sda*/, int /*scl*/, uint32_t freq) +{ + begin(); + if (freq) { + setClock(freq); + } +} + +void TwoWire::end() +{ + // No-op: Zephyr i2c devices stay initialized for the lifetime of the + // application. Runtime PM (zephyr,pm-device-runtime-auto in the DT) + // handles low-power transitions when idle. +} + +void TwoWire::setClock(uint32_t freq) +{ + const struct device *dev = getI2CDevice(); + if (dev == nullptr) { + return; + } + uint32_t speed; + if (freq >= 1000000U) { + speed = I2C_SPEED_FAST_PLUS; // 1 MHz + } else if (freq >= 400000U) { + speed = I2C_SPEED_FAST; // 400 kHz + } else { + speed = I2C_SPEED_STANDARD; // 100 kHz + } + uint32_t cfg = I2C_MODE_CONTROLLER | I2C_SPEED_SET(speed); + int rc = i2c_configure(dev, cfg); + if (rc) { + LOG_WARN("Wire.setClock(%u) failed: %d", (unsigned)freq, rc); + } +} + +void TwoWire::beginTransmission(uint8_t addr) +{ + txAddr = addr; + txLen = 0; +} + +size_t TwoWire::write(uint8_t data) +{ + if (txLen >= WIRE_BUFFER_LENGTH) { + return 0; // overflow — endTransmission() will return 1 + } + txBuf[txLen++] = data; + return 1; +} + +size_t TwoWire::write(const uint8_t *data, size_t n) +{ + size_t written = 0; + for (size_t i = 0; i < n; i++) { + if (write(data[i]) == 0) { + break; + } + written++; + } + return written; +} + +uint8_t TwoWire::endTransmission(bool /*stop*/) +{ + // Arduino return codes: + // 0 = success + // 1 = data-too-long (overflow caught in write()) + // 2 = NACK on address + // 3 = NACK on data + // 4 = other error + // 5 = timeout + if (txLen > WIRE_BUFFER_LENGTH) { + return 1; + } + const struct device *dev = getI2CDevice(); + if (dev == nullptr || !device_is_ready(dev)) { + return 4; + } + int rc = i2c_write(dev, txBuf, txLen, txAddr); + txLen = 0; + if (rc == 0) { + return 0; + } + if (rc == -EIO) { + return 2; // address NACK is the most common -EIO source on nrf-twim + } + if (rc == -ETIMEDOUT) { + return 5; + } + return 4; +} + +uint8_t TwoWire::requestFrom(uint8_t addr, uint8_t quantity, bool /*stop*/) +{ + rxLen = 0; + rxPos = 0; + if (quantity == 0) { + return 0; + } + if (quantity > WIRE_BUFFER_LENGTH) { + quantity = WIRE_BUFFER_LENGTH; + } + const struct device *dev = getI2CDevice(); + if (dev == nullptr || !device_is_ready(dev)) { + return 0; + } + + // If there is a pending TX (driver wrote register address via write() + // without an explicit endTransmission()), use i2c_write_read so the + // repeated-start path matches the typical "set register pointer then + // read N bytes" sensor protocol. + int rc; + if (txLen > 0) { + rc = i2c_write_read(dev, addr, txBuf, txLen, rxBuf, quantity); + txLen = 0; + } else { + rc = i2c_read(dev, rxBuf, quantity, addr); + } + if (rc) { + return 0; + } + rxLen = quantity; + return quantity; +} + +int TwoWire::available() +{ + return rxLen - rxPos; +} + +int TwoWire::read() +{ + if (rxPos >= rxLen) { + return -1; + } + return rxBuf[rxPos++]; +} + +int TwoWire::peek() +{ + if (rxPos >= rxLen) { + return -1; + } + return rxBuf[rxPos]; +} + +size_t TwoWire::readBytes(uint8_t *buf, size_t len) +{ + size_t n = 0; + while (n < len) { + int b = read(); + if (b < 0) { + break; + } + buf[n++] = (uint8_t)b; + } + return n; +} + +TwoWire::operator bool() const +{ + return getI2CDevice() != nullptr; +} diff --git a/src/platform/nrf54l15/Wire.h b/src/platform/nrf54l15/Wire.h new file mode 100644 index 000000000..2c8f66dff --- /dev/null +++ b/src/platform/nrf54l15/Wire.h @@ -0,0 +1,83 @@ +/** + * Wire.h — Arduino TwoWire (I2C) shim for Zephyr / nRF54L15. + * + * Bus binding: the Zephyr device tree alias `i2c30` (TWIM30 hardware + * peripheral, HP domain, 3.0 V) is resolved at compile time via + * DEVICE_DT_GET in Wire.cpp. SDA/SCL pins are configured in the board + * overlay via pinctrl — `begin(sda, scl)` overloads are accepted for + * Arduino API compatibility but the pin arguments are ignored. + * + * Buffer sizes are sized for the worst-case I2C consumer we plan to use + * (NXP SE050 secure element, ~256-byte T=1 frames). BMP280 / INA228 / + * SHT40 / INA3221 read in single-digit bytes and fit trivially. + */ + +#pragma once + +#include "Arduino.h" +#include +#include + +#ifndef WIRE_BUFFER_LENGTH +#define WIRE_BUFFER_LENGTH 256 +#endif + +class TwoWire +{ + public: + TwoWire(); + + // ── Bus lifecycle ───────────────────────────────────────────────── + // begin() variants — pin arguments are accepted for API compatibility + // but ignored: SDA/SCL are fixed by the Zephyr overlay pinctrl. freq + // is also fixed by overlay clock-frequency (use setClock() at runtime). + void begin(); + void begin(uint8_t sda, uint8_t scl); + void begin(int sda, int scl, uint32_t freq); + void end(); + + void setClock(uint32_t freq); + void setClockStretchLimit(uint32_t) {} // no-op on TWIM hardware + + // ── Master write ───────────────────────────────────────────────── + void beginTransmission(uint8_t addr); + void beginTransmission(int addr) { beginTransmission((uint8_t)addr); } + // Return codes (Arduino convention): + // 0 = success, 1 = data-too-long, 2 = NACK on addr, 3 = NACK on data, + // 4 = other error, 5 = timeout. + uint8_t endTransmission(bool stop = true); + uint8_t endTransmission(uint8_t stop) { return endTransmission(stop != 0); } + + size_t write(uint8_t data); + size_t write(const uint8_t *data, size_t n); + + // ── Master read ────────────────────────────────────────────────── + uint8_t requestFrom(uint8_t addr, uint8_t quantity, bool stop = true); + uint8_t requestFrom(uint8_t addr, uint8_t quantity, uint8_t stop) { return requestFrom(addr, quantity, stop != 0); } + uint8_t requestFrom(int addr, int quantity, int stop = 1) { return requestFrom((uint8_t)addr, (uint8_t)quantity, stop != 0); } + + int available(); + int read(); + int peek(); + size_t readBytes(uint8_t *buf, size_t len); + size_t readBytes(char *buf, size_t len) { return readBytes((uint8_t *)buf, len); } + void flush() {} + + // Slave callbacks unsupported — peripheral-only stub. + void onReceive(void (*)(int)) {} + void onRequest(void (*)(void)) {} + + operator bool() const; + + private: + uint8_t txAddr; + uint16_t txLen; + uint8_t txBuf[WIRE_BUFFER_LENGTH]; + + uint16_t rxLen; + uint16_t rxPos; + uint8_t rxBuf[WIRE_BUFFER_LENGTH]; +}; + +extern TwoWire Wire; +extern TwoWire Wire1; // alias to Wire — only one I2C bus on this board diff --git a/src/platform/nrf54l15/architecture.h b/src/platform/nrf54l15/architecture.h new file mode 100644 index 000000000..3f2a4c4e0 --- /dev/null +++ b/src/platform/nrf54l15/architecture.h @@ -0,0 +1,79 @@ +#pragma once + +#define ARCH_NRF54L15 + +// +// Feature flags for nRF54L15. +// +// The HAS_* macros below are Meshtastic's compile-time feature gate: every +// optional subsystem (BLE, screen, I2C, GPS, buttons, telemetry, sensors, +// radio, CPU shutdown, ...) is wrapped in `#if HAS_FOO` so a given board +// only pays for the features it actually ships. On memory-tight MCUs this +// is not cosmetic — it's the difference between a binary that fits in +// flash and one that doesn't, and between a build that links and one that +// drags in drivers for hardware the board doesn't have. Defaulting to 0 +// here (rather than inheriting nRF52 defaults) is deliberate: the +// nRF54L15-DK is a bare dev kit with no screen, no I2C sensors, no GPS, +// no user buttons — so every HAS_* flag starts off and gets flipped on +// explicitly by variants that add that hardware. +// +// Feature flags are also the cleanest way to absorb platform divergence +// without sprinkling `#ifdef ARCH_NRF54L15` across shared code. Anywhere +// a subsystem can be conditionally compiled via HAS_*, prefer that over +// per-arch guards: it keeps the core code arch-agnostic, makes it trivial +// to bring up the next board (flip the flags, don't patch call sites), +// and keeps the "does this platform support X?" question answerable by +// reading one file instead of grepping the tree. BLE in particular is +// deferred to Phase 2 on this port — the nRF54L15 uses MPSL/Zephyr BLE +// APIs rather than the Adafruit SoftDevice stack used by nRF52840 — so +// while HAS_BLUETOOTH defaults to 1, the actual implementation lives in +// NRF54L15Bluetooth.cpp behind its own Zephyr Kconfig gates. +// + +#ifndef HAS_BLUETOOTH +#define HAS_BLUETOOTH 1 +#endif +#ifndef HAS_SCREEN +#define HAS_SCREEN 0 +#endif +#ifndef HAS_WIRE +#define HAS_WIRE 0 +#endif +#ifndef HAS_GPS +#define HAS_GPS 0 +#endif +#ifndef HAS_BUTTON +#define HAS_BUTTON 0 +#endif +#ifndef HAS_TELEMETRY +#define HAS_TELEMETRY 0 +#endif +#ifndef HAS_SENSOR +#define HAS_SENSOR 0 +#endif +#ifndef HAS_RADIO +#define HAS_RADIO 1 +#endif +#ifndef HAS_CPU_SHUTDOWN +#define HAS_CPU_SHUTDOWN 0 +#endif + +// ADC reference — nRF54L15 SAADC uses VDD/4 internal ref by default +#ifndef AREF_VOLTAGE +#define AREF_VOLTAGE 3.6 +#endif +#ifndef BATTERY_SENSE_RESOLUTION_BITS +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#endif + +// +// HW_VENDOR — maps build-time define to HardwareModel enum. +// PRIVATE_HW (255): the protobuf HardwareModel enum reserves DK / DIY boards +// without an SKU under this value; the nRF54L15-DK doesn't get a dedicated +// enum number. Variant manifest matches via custom_meshtastic_hw_model = 255. +// +#ifdef NRF54L15_DK +#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW +#else +#define HW_VENDOR meshtastic_HardwareModel_UNSET +#endif diff --git a/src/platform/nrf54l15/bluefruit.h b/src/platform/nrf54l15/bluefruit.h new file mode 100644 index 000000000..a9c0afe71 --- /dev/null +++ b/src/platform/nrf54l15/bluefruit.h @@ -0,0 +1,19 @@ +// bluefruit.h — stub for nRF54L15/Zephyr +// NodeDB.cpp includes this when ARCH_NRF52 is defined. +// Bluetooth is excluded (MESHTASTIC_EXCLUDE_BLUETOOTH=1); this satisfies +// the include chain without pulling in the Adafruit Bluefruit SDK. +#pragma once + +struct BLEPeripheral { + void clearBonds() {} +}; +struct BLECentral { + void clearBonds() {} +}; + +struct BlueFruitClass { + BLEPeripheral Periph; + BLECentral Central; +}; + +extern BlueFruitClass Bluefruit; diff --git a/src/platform/nrf54l15/main-nrf54l15.cpp b/src/platform/nrf54l15/main-nrf54l15.cpp new file mode 100644 index 000000000..5b7729b4d --- /dev/null +++ b/src/platform/nrf54l15/main-nrf54l15.cpp @@ -0,0 +1,210 @@ +/* + * main-nrf54l15.cpp — Platform entry points for Nordic nRF54L15 + * + * Adapted from src/platform/nrf52/main-nrf52.cpp. + * SoftDevice, Adafruit BLE, and nRFCrypto are NOT available on nRF54L15. + * Phase 2 will add proper BLE via Zephyr MPSL APIs. + * + * TODO items are marked with "TODO(nrf54l15):" + */ + +#include "configuration.h" +#include +#include +#include +#include + +#include "NodeDB.h" +#include "PowerMon.h" +#include "Router.h" +#include "error.h" +#include "main.h" +#include "mesh/MeshService.h" +#include "meshUtils.h" +#include "power.h" +#include + +// ── Watchdog ────────────────────────────────────────────────────────────── +// TODO(nrf54l15): nRF54L15 has a WDT peripheral but nrfx_wdt driver support +// may differ depending on the Zephyr SDK version. Enable once confirmed. +#define APP_WATCHDOG_SECS 90 +static bool watchdog_running = false; + +static inline void watchdog_feed() {} // TODO(nrf54l15): replace with real WDT feed + +// ── Weak variant hooks ──────────────────────────────────────────────────── +void variant_shutdown() __attribute__((weak)); +void variant_shutdown() {} + +void variant_nrf54l15LoopHook(void) __attribute__((weak)); +void variant_nrf54l15LoopHook(void) {} + +// ── PowerHAL ───────────────────────────────────────────────────────────── +bool powerHAL_isVBUSConnected() +{ + // TODO(nrf54l15): nRF54L15 has a USB POWER peripheral — read USBREGSTATUS + return false; +} + +bool powerHAL_isPowerLevelSafe() +{ + // TODO(nrf54l15): implement SAADC VDD measurement similar to nRF52 + return true; +} + +void powerHAL_platformInit() +{ + // TODO(nrf54l15): configure POF comparator and analog reference if needed +} + +// ── Utilities ───────────────────────────────────────────────────────────── +bool loopCanSleep() +{ + return !Serial; +} + +void updateBatteryLevel(uint8_t level) +{ + (void)level; +} + +void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) +{ + LOG_ERROR("assert failed %s: %d, %s, test=%s", file, line, func, failedexpr); + NVIC_SystemReset(); +} + +void getMacAddr(uint8_t *dmac) +{ + // TODO(nrf54l15): verify FICR register layout for nRF54L15. + // nRF52840 uses NRF_FICR->DEVICEADDR[0/1]; nRF54L15 Zephyr HAL may differ. +#if defined(NRF_FICR) + const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR; + dmac[5] = src[0]; + dmac[4] = src[1]; + dmac[3] = src[2]; + dmac[2] = src[3]; + dmac[1] = src[4]; + dmac[0] = src[5] | 0xc0; +#else + // Fallback: fixed placeholder until Zephyr FICR path is confirmed + dmac[0] = 0xC2; + dmac[1] = 0xA7; + dmac[2] = 0x54; + dmac[3] = 0x15; + dmac[4] = 0x00; + dmac[5] = 0x01; +#endif +} + +// ── Bluetooth ───────────────────────────────────────────────────────────────── + +void setBluetoothEnable(bool enable) +{ + if (enable) { + static bool initialized = false; + if (!initialized) { + nrf54l15Bluetooth = new NRF54L15Bluetooth(); + nrf54l15Bluetooth->startDisabled(); + initialized = true; + } + if (nrf54l15Bluetooth) { + nrf54l15Bluetooth->resumeAdvertising(); + } + } else { + if (nrf54l15Bluetooth) { + nrf54l15Bluetooth->shutdown(); + } + } +} + +void clearBonds() +{ + if (!nrf54l15Bluetooth) { + nrf54l15Bluetooth = new NRF54L15Bluetooth(); + nrf54l15Bluetooth->setup(); + } + nrf54l15Bluetooth->clearBonds(); +} + +void enterDfuMode() +{ + // TODO(nrf54l15): nRF54L15 uses nRF Connect DFU (MCUboot/SUIT). + // Trigger via Zephyr boot_request_upgrade() or similar. + NVIC_SystemReset(); +} + +// ── printf via RTT ──────────────────────────────────────────────────────── +// TODO(nrf54l15): SEGGER_RTT may not be available with Zephyr; use printk() +// or a USB CDC console instead. Remove this override if it conflicts. +#ifdef SEGGER_RTT_PRINTF +int printf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + auto res = SEGGER_RTT_vprintf(0, fmt, &args); + va_end(args); + return res; +} +#endif + +// ── Deep sleep ──────────────────────────────────────────────────────────── +void cpuDeepSleep(uint32_t msecToWake) +{ +#if HAS_WIRE + Wire.end(); +#endif + SPI.end(); + if (Serial) + Serial.end(); + + variant_shutdown(); + + // TODO(nrf54l15): use Zephyr pm_system_suspend() or WFI for proper low-power + if (msecToWake != portMAX_DELAY) { + delay(msecToWake); + NVIC_SystemReset(); + } else { + // System off equivalent — halt + while (1) { + __WFI(); + } + } +} + +// ── Setup / Loop ────────────────────────────────────────────────────────── +// Forward declaration — defined in NRF54L15Bluetooth.cpp +void nrf54l15_bt_preinit(); + +void nrf54l15Setup() +{ + // nRF54L15 power peripheral layout differs from nRF52; RESETREAS not present here. + // TODO(Phase 3): use zephyr/drivers/hwinfo.h hwinfo_get_reset_cause() + LOG_DEBUG("Reset reason: (nRF54L15 power peripheral differs from nRF52, skipped)"); + + // TODO(nrf54l15): init SAADC, watchdog, and random seed via nrfx or Zephyr + // For now seed with a fixed value; replace with hardware entropy source. +#if defined(NRF_FICR) + randomSeed(analogRead(0) ^ (uint32_t)NRF_FICR->DEVICEADDR[0]); +#else + randomSeed(analogRead(0)); +#endif + + // Pre-initialize BT stack here on the main thread (CONFIG_MAIN_STACK_SIZE=8192). + // bt_enable() overflows the smaller PowerFSMThread stack when called later. + // NRF54L15Bluetooth::setup() checks bt_initialized and skips bt_enable() if true. + nrf54l15_bt_preinit(); +} + +void nrf54l15Loop() +{ + // First-call gate for the future WDT init — body will hold real init code, not just the bookkeeping flag. + // cppcheck-suppress duplicateConditionalAssign + if (!watchdog_running) { + // TODO(nrf54l15): enable WDT here + watchdog_running = true; + } + watchdog_feed(); + + variant_nrf54l15LoopHook(); +} diff --git a/src/platform/nrf54l15/nrf54l15_arduino.cpp b/src/platform/nrf54l15/nrf54l15_arduino.cpp new file mode 100644 index 000000000..43a3c11f8 --- /dev/null +++ b/src/platform/nrf54l15/nrf54l15_arduino.cpp @@ -0,0 +1,557 @@ +/** + * nrf54l15_arduino.cpp — Arduino shim implementations for Zephyr/nRF54L15 + * + * Provides concrete implementations for Print, HardwareSerial, GPIO, SPI, + * and String methods declared in Arduino.h / SPI.h. + * + * Phase 3: real GPIO via Zephyr GPIO API and real SPI via Zephyr SPI API. + * Pin numbering convention: P0.n = n, P1.n = 16+n, P2.n = 32+n. + */ + +#include "Arduino.h" +#include "SPI.h" +#include "Wire.h" +#include +#include +#include +#include +#include +#include +#include +// ── Bluefruit singleton stub (satisfies NodeDB.cpp ARCH_NRF52 path) ────────── +#include "bluefruit.h" +BlueFruitClass Bluefruit; + +// ── _fini stub — ARM newlib's __libc_fini_array references _fini, but ──────── +// Zephyr startup doesn't provide it. Provide a weak no-op so the linker +// is satisfied when C++ global dtors or atexit() pull in __libc_fini_array. +extern "C" void __attribute__((weak)) _fini(void) {} + +// ── SPI / Wire singletons ───────────────────────────────────────────────────── +SPIClass SPI; +SPIClass SPI1; +TwoWire Wire; +TwoWire Wire1; + +// ── HardwareSerial singletons ──────────────────────────────────────────────── +HardwareSerial Serial; +HardwareSerial Serial1; +HardwareSerial Serial2; + +// ── Timing functions — C linkage to match extern "C" declarations ──────────── +extern "C" uint32_t millis(void) +{ + return (uint32_t)k_uptime_get_32(); +} +extern "C" uint32_t micros(void) +{ + return (uint32_t)(k_uptime_get() * 1000ULL); +} +extern "C" void delay(uint32_t ms) +{ + k_sleep(K_MSEC(ms)); +} +extern "C" void delayMicroseconds(uint32_t us) +{ + k_sleep(K_USEC(us)); +} +extern "C" void yield(void) +{ + k_yield(); +} + +// ── NVIC_SystemReset — wraps __NVIC_SystemReset from CMSIS core_cm33.h ─────── +// core_cm33.h has #define NVIC_SystemReset __NVIC_SystemReset, so undef it +// before defining our own implementation to prevent macro expansion collision. +#pragma push_macro("NVIC_SystemReset") +#undef NVIC_SystemReset +extern "C" void NVIC_SystemReset(void) +{ + sys_reboot(SYS_REBOOT_COLD); +} +#pragma pop_macro("NVIC_SystemReset") + +// ── HardwareSerial::write ───────────────────────────────────────────────────── +size_t HardwareSerial::write(uint8_t c) +{ + // TODO(nrf54l15 Phase 3): route through Zephyr UART / USB-CDC console + // For now use printk so we at least get something over RTT/UART0 + printk("%c", (char)c); + return 1; +} + +size_t HardwareSerial::write(const uint8_t *buf, size_t n) +{ + for (size_t i = 0; i < n; i++) + printk("%c", (char)buf[i]); + return n; +} + +// ── Print::printf ───────────────────────────────────────────────────────────── +int Print::printf(const char *fmt, ...) +{ + char buf[256]; + va_list args; + va_start(args, fmt); + int n = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + if (n > 0) + write((const uint8_t *)buf, (size_t)(n < (int)sizeof(buf) ? n : (int)sizeof(buf) - 1)); + return n; +} + +// ── strlcpy — BSD extension not in Zephyr newlib ──────────────────────────── +extern "C" size_t strlcpy(char *dst, const char *src, size_t size) +{ + size_t len = strlen(src); + if (size > 0) { + size_t copy = len < size - 1 ? len : size - 1; + memcpy(dst, src, copy); + dst[copy] = '\0'; + } + return len; +} + +// ── Print numeric helpers ───────────────────────────────────────────────────── +static size_t printNumber(Print &p, unsigned long n, uint8_t base) +{ + if (base == 0) + return p.write((uint8_t)n); + + char buf[8 * sizeof(long) + 1]; + char *end = buf + sizeof(buf) - 1; + *end = '\0'; + if (n == 0) { + *--end = '0'; + } else { + while (n > 0) { + unsigned long remainder = n % base; + *--end = (char)(remainder < 10 ? '0' + remainder : 'A' + remainder - 10); + n /= base; + } + } + return p.write((const uint8_t *)end, strlen(end)); +} + +static size_t printFloat(Print &p, double number, uint8_t digits) +{ + if (isnan(number)) + return p.print("nan"); + if (isinf(number)) + return p.print("inf"); + if (number > 4294967040.0 || number < -4294967040.0) + return p.print("ovf"); + + size_t n = 0; + if (number < 0.0) { + n += p.write('-'); + number = -number; + } + + // Round + double rounding = 0.5; + for (uint8_t i = 0; i < digits; i++) + rounding /= 10.0; + number += rounding; + + unsigned long int_part = (unsigned long)number; + double remainder = number - (double)int_part; + n += printNumber(p, int_part, 10); + if (digits > 0) { + n += p.write('.'); + for (uint8_t i = 0; i < digits; i++) { + remainder *= 10.0; + unsigned int d = (unsigned int)remainder; + n += p.write('0' + d); + remainder -= d; + } + } + return n; +} + +size_t Print::print(unsigned char n, int base) +{ + return printNumber(*this, n, base); +} +size_t Print::print(int n, int base) +{ + if (base == 10 && n < 0) { + size_t r = write('-'); + return r + printNumber(*this, (unsigned long)(-n), base); + } + return printNumber(*this, (unsigned long)n, base); +} +size_t Print::print(long n, int base) +{ + if (base == 10 && n < 0) { + size_t r = write('-'); + return r + printNumber(*this, (unsigned long)(-n), base); + } + return printNumber(*this, (unsigned long)n, base); +} +size_t Print::print(unsigned int n, int base) +{ + return printNumber(*this, n, base); +} +size_t Print::print(unsigned long n, int base) +{ + return printNumber(*this, n, base); +} +size_t Print::print(float n, int d) +{ + return printFloat(*this, n, d); +} +size_t Print::print(double n, int d) +{ + return printFloat(*this, n, d); +} + +// ── String::replace(String, String) ───────────────────────────────────────── +void String::replace(const String &from, const String &to) +{ + if (from.isEmpty() || !_buf) + return; + // Simple O(n²) replace — fine for typical Meshtastic string lengths + String result; + const char *p = _buf; + while (*p) { + if (strncmp(p, from.c_str(), from.length()) == 0) { + result += to; + p += from.length(); + } else { + result += *p++; + } + } + *this = result; +} + +// ═════════════════════════════════════════════════════════════════════════════ +// GPIO — Real Zephyr implementation (Phase 3) +// Pin mapping: P0.n = n (0-15), P1.n = 16+n (16-31), P2.n = 32+n (32-47) +// ═════════════════════════════════════════════════════════════════════════════ + +static const struct device *_gpio_dev_for_pin(uint32_t pin, gpio_pin_t *zpin) +{ + if (pin < 16) { + *zpin = (gpio_pin_t)pin; + return DEVICE_DT_GET(DT_NODELABEL(gpio0)); + } else if (pin < 32) { + *zpin = (gpio_pin_t)(pin - 16); + return DEVICE_DT_GET(DT_NODELABEL(gpio1)); + } else { + *zpin = (gpio_pin_t)(pin - 32); + return DEVICE_DT_GET(DT_NODELABEL(gpio2)); + } +} + +void pinMode(uint32_t pin, uint32_t mode) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) + return; + + gpio_flags_t flags; + switch (mode) { + case OUTPUT: + flags = GPIO_OUTPUT_INACTIVE; + break; + case INPUT_PULLUP: + flags = GPIO_INPUT | GPIO_PULL_UP; + break; + case INPUT_PULLDOWN: + flags = GPIO_INPUT | GPIO_PULL_DOWN; + break; + default: + flags = GPIO_INPUT; + break; + } + gpio_pin_configure(dev, zpin, flags); +} + +// Bring-up diagnostics for the SX1262 wiring path. Off by default — enable by +// adding `-DNRF54L15_GPIO_DEBUG` to platformio.ini build_flags. Useful when +// validating CS/NRESET toggles after a wiring change, diagnosing a "stuck HIGH" +// BUSY before the first NRESET pulse, or tracing BUSY transitions during early +// boot. In normal operation these traces are noise (they bypass LOG level +// controls and print on every GPIO touch), so they are gated at compile time. +#ifdef NRF54L15_GPIO_DEBUG +#define GPIO_LOG_MAX 20 +static uint32_t _gpio_log_count = 0; +#endif + +void digitalWrite(uint32_t pin, uint32_t value) +{ +#ifdef NRF54L15_GPIO_DEBUG + // Before the very first NRESET pulse, snapshot BUSY state. + // If BUSY is already HIGH here, the chip never completed power-on calibration. + if (pin == 32 && value == 0) { + static bool _first_nreset = true; + if (_first_nreset) { + _first_nreset = false; + const struct device *bdev = DEVICE_DT_GET(DT_NODELABEL(gpio2)); + if (device_is_ready(bdev)) { + gpio_pin_configure(bdev, 3, GPIO_INPUT); // P2.03 = BUSY + int busy_before = gpio_pin_get(bdev, 3); + printk("[nrf54l15] BUSY before first NRESET = %d%s\n", busy_before, + busy_before ? " ← STUCK HIGH (chip damaged?)" : " ← LOW (chip OK)"); + } + } + } +#endif + + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) { + // Genuine hardware/DTS misconfiguration — keep this regardless of the + // GPIO_DEBUG gate so it surfaces in production builds too. + printk("[GPIO] pin%u dev NOT READY\n", (unsigned)pin); + return; + } + gpio_pin_set(dev, zpin, (int)value); +#ifdef NRF54L15_GPIO_DEBUG + if ((pin == 37 || pin == 32) && _gpio_log_count < GPIO_LOG_MAX) { + // Read back the pin state to confirm it actually changed + int actual = gpio_pin_get(dev, zpin); + printk("[GPIO] pin%u → %u (read-back=%d)\n", (unsigned)pin, (unsigned)value, actual); + _gpio_log_count++; + } +#endif +} + +int digitalRead(uint32_t pin) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) + return 0; + int v = gpio_pin_get(dev, zpin); +#ifdef NRF54L15_GPIO_DEBUG + // Log BUSY pin (35=P2.03) state changes + periodic updates for 10 seconds + if (pin == 35) { + static uint32_t busy_log_count = 0; + static int last_busy = -1; + static uint32_t first_read_ms = 0; + if (first_read_ms == 0) + first_read_ms = k_uptime_get_32(); + uint32_t elapsed_ms = k_uptime_get_32() - first_read_ms; + + // Always log state changes + if (v != last_busy) { + printk("[BUSY] %ums: state changed %d → %d\n", (unsigned)elapsed_ms, last_busy, v); + last_busy = v; + } + // Also log every 500ms for first 10 seconds so we can see timeline + if (elapsed_ms < 10000 && busy_log_count < 20 && (elapsed_ms / 500) > (busy_log_count)) { + printk("[BUSY] %ums: pin=%d (periodic)\n", (unsigned)elapsed_ms, v); + busy_log_count = (elapsed_ms / 500) + 1; + } + } +#endif + return v; +} + +// ─── attachInterrupt — supports up to NRF54L15_MAX_IRQS pins ──────────────── +#define NRF54L15_MAX_IRQS 8 + +struct _PinIrq { + struct gpio_callback cb; + voidFuncPtr user_cb; + const struct device *dev; + gpio_pin_t zpin; + bool used; +}; + +static _PinIrq _irq_table[NRF54L15_MAX_IRQS]; + +static void _gpio_irq_dispatch(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + _PinIrq *irq = CONTAINER_OF(cb, _PinIrq, cb); + if (irq->user_cb) + irq->user_cb(); +} + +void attachInterrupt(uint32_t pin, voidFuncPtr cb, int mode) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + if (!device_is_ready(dev)) + return; + + // Find a free slot (or reuse existing registration for same pin) + _PinIrq *slot = nullptr; + for (int i = 0; i < NRF54L15_MAX_IRQS; i++) { + if (_irq_table[i].used && _irq_table[i].dev == dev && _irq_table[i].zpin == zpin) { + // Re-register: remove old callback first + gpio_remove_callback(dev, &_irq_table[i].cb); + slot = &_irq_table[i]; + break; + } + if (!slot && !_irq_table[i].used) + slot = &_irq_table[i]; + } + if (!slot) + return; // table full + + gpio_flags_t irq_flags; + switch (mode) { + case RISING: + irq_flags = GPIO_INT_EDGE_RISING; + break; + case FALLING: + irq_flags = GPIO_INT_EDGE_FALLING; + break; + default: + irq_flags = GPIO_INT_EDGE_BOTH; + break; + } + + slot->user_cb = cb; + slot->dev = dev; + slot->zpin = zpin; + slot->used = true; + + gpio_pin_configure(dev, zpin, GPIO_INPUT); + gpio_init_callback(&slot->cb, _gpio_irq_dispatch, BIT(zpin)); + gpio_add_callback(dev, &slot->cb); + gpio_pin_interrupt_configure(dev, zpin, irq_flags); +} + +void detachInterrupt(uint32_t pin) +{ + gpio_pin_t zpin; + const struct device *dev = _gpio_dev_for_pin(pin, &zpin); + for (int i = 0; i < NRF54L15_MAX_IRQS; i++) { + if (_irq_table[i].used && _irq_table[i].dev == dev && _irq_table[i].zpin == zpin) { + gpio_pin_interrupt_configure(dev, zpin, GPIO_INT_DISABLE); + gpio_remove_callback(dev, &_irq_table[i].cb); + _irq_table[i].used = false; + break; + } + } +} + +// ═════════════════════════════════════════════════════════════════════════════ +// SPI — Real Zephyr implementation using SPIM00 (HP domain, 3.0V) +// CS is handled by RadioLib via digitalWrite() — hardware CS not used. +// Mode 0 (CPOL=0, CPHA=0), MSB first. +// ═════════════════════════════════════════════════════════════════════════════ + +// Use SPIM00 (HP domain, 3.0V) — SPIM20 is 1.8V LP domain, incompatible with SX1262. +// Lazy-init: DEVICE_DT_GET in global scope fails when the extern symbol is +// not visible in this translation unit. Use a function-local static instead. +static const struct device *_spi00(void) +{ + static const struct device *dev = nullptr; + if (!dev) { + dev = DEVICE_DT_GET(DT_NODELABEL(spi00)); + if (!device_is_ready(dev)) { + printk("[nrf54l15] spi00 NOT READY\n"); + dev = nullptr; + } else { + printk("[nrf54l15] spi00 ready\n"); + } + } + return dev; +} + +// SPI config: Mode 0, MSB first, no hardware CS (RadioLib does it manually) +// +// SPIM00 base clock = 128 MHz (nRF54L15 default when NRF_CONFIG_CPU_FREQ_MHZ +// is not set; SystemInit() applies 128 MHz). Hardware prescaler must be EVEN +// and in [4, 126] (SPIM00_PRESCALER_DIVISOR_RANGE_MIN/MAX from MDK). +// 1 MHz → prescaler = 128 > 126 → NRFX_ERROR_INVALID_PARAM → -EIO on every +// transfer. Minimum valid frequency is 2 MHz (prescaler = 64). +static const struct spi_config _spi00_cfg = { + .frequency = 2000000U, // 2 MHz — minimum valid for SPIM00 at 128 MHz base + .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB, + .slave = 0, + .cs = {}, // CS = NULL → RadioLib handles CS via GPIO +}; + +// Static DMA buffers — stack-allocated bufs on nRF54L15 may not be reachable +// by SPIM20 EasyDMA. Static placement in .bss/.data is always in Global SRAM. +// rx_byte is pre-filled with 0xAA before every transfer so we can distinguish: +// 0xAA → DMA never wrote (EasyDMA can't reach the buffer) +// 0x00 → MISO actively driven LOW (chip in reset / bus fight) +// 0xFF → MISO floating HIGH +// other → real chip response +static uint8_t _spi_tx_byte __attribute__((aligned(4))); +static uint8_t _spi_rx_byte __attribute__((aligned(4))); + +// Dump the first SPI_DUMP_N byte exchanges so we can see what MISO returns. +#define SPI_DUMP_N 30 +static uint32_t _spi_dump_count = 0; + +uint8_t SPIClass::transfer(uint8_t data) +{ + const struct device *dev = _spi00(); + if (!dev) + return 0xFF; + + _spi_tx_byte = data; + _spi_rx_byte = 0xAA; // sentinel: if DMA doesn't write, we return 0xAA + + struct spi_buf tx_buf = {.buf = &_spi_tx_byte, .len = 1}; + struct spi_buf rx_buf = {.buf = &_spi_rx_byte, .len = 1}; + struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1}; + struct spi_buf_set rx_set = {.buffers = &rx_buf, .count = 1}; + + static uint32_t spi_err_count = 0; + int ret = spi_transceive(dev, &_spi00_cfg, &tx_set, &rx_set); + if (ret != 0 && spi_err_count++ < 3) + printk("[SPI] err=%d tx=0x%02x\n", ret, data); + + if (_spi_dump_count < SPI_DUMP_N) { + printk("[SPI] #%u tx=0x%02x rx=0x%02x\n", (unsigned)_spi_dump_count, data, _spi_rx_byte); + _spi_dump_count++; + } + + return _spi_rx_byte; +} + +// Static DMA-safe buffers for transfer16 — same EasyDMA reachability concern +// applies as for the byte path: stack buffers from a caller thread may sit in +// per-thread RAM regions that EasyDMA cannot reach. +static uint8_t _spi_tx16[2] __attribute__((aligned(4))); +static uint8_t _spi_rx16[2] __attribute__((aligned(4))); + +uint16_t SPIClass::transfer16(uint16_t data) +{ + const struct device *dev = _spi00(); + if (!dev) + return 0xFFFF; + + _spi_tx16[0] = (uint8_t)(data >> 8); + _spi_tx16[1] = (uint8_t)(data & 0xFF); + _spi_rx16[0] = 0xAA; + _spi_rx16[1] = 0xAA; + struct spi_buf tx_buf = {.buf = _spi_tx16, .len = 2}; + struct spi_buf rx_buf = {.buf = _spi_rx16, .len = 2}; + struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1}; + struct spi_buf_set rx_set = {.buffers = &rx_buf, .count = 1}; + spi_transceive(dev, &_spi00_cfg, &tx_set, &rx_set); + return ((uint16_t)_spi_rx16[0] << 8) | _spi_rx16[1]; +} + +void SPIClass::transferBytes(const uint8_t *tx, uint8_t *rx, uint32_t count) +{ + if (!count) + return; + const struct device *dev = _spi00(); + if (!dev) + return; + // Zephyr requires non-const buf pointer; cast is safe for tx-only direction + struct spi_buf tx_buf = {.buf = const_cast(tx), .len = count}; + struct spi_buf rx_buf = {.buf = rx, .len = count}; + struct spi_buf_set tx_set = {.buffers = &tx_buf, .count = 1}; + struct spi_buf_set rx_set = {.buffers = rx_buf.buf ? &rx_buf : nullptr, .count = rx_buf.buf ? 1U : 0U}; + spi_transceive(dev, &_spi00_cfg, &tx_set, rx ? &rx_set : nullptr); +} + +void SPIClass::transfer(void *buf, size_t count) +{ + if (!count || !buf) + return; + transferBytes(reinterpret_cast(buf), reinterpret_cast(buf), (uint32_t)count); +} diff --git a/src/platform/nrf54l15/nrf54l15_main.cpp b/src/platform/nrf54l15/nrf54l15_main.cpp new file mode 100644 index 000000000..4b633f0df --- /dev/null +++ b/src/platform/nrf54l15/nrf54l15_main.cpp @@ -0,0 +1,121 @@ +/* + * nrf54l15_main.cpp — Zephyr entry point for Meshtastic nRF54L15 port + * + * Zephyr calls main() instead of Arduino's setup()/loop(). + * This file provides the main() that bootstraps the Arduino-style + * Meshtastic application loop. + */ + +#include +#include +#include +#include + +// Forward declarations from src/main.cpp +void setup(); +void loop(); + +// ── Crash info saved to noinit RAM (survives soft reset) ───────────────────── +// Zephyr's arch_esf does not expose the faulting SP directly; we capture PSP +// at entry to the fatal handler (the exception-basic frame lives there) and +// store xPSR alongside PC/LR for context. +struct crash_info { + uint32_t magic; + uint32_t reason; + uint32_t pc; + uint32_t psp; // stack pointer captured at fault entry + uint32_t xpsr; // saved program status (flags + exception number) + uint32_t lr; + uint32_t cfsr; // Configurable Fault Status Register +}; +static struct crash_info saved_crash __attribute__((section(".noinit"))); +#define CRASH_MAGIC 0xDEADBEEF + +// Override Zephyr's weak fatal handler to save crash info, then cold-reboot so +// main() can report the saved record on the next boot. We don't rely on +// CONFIG_RESET_ON_FATAL_ERROR (default off → k_fatal_halt would spin forever) +// — we issue sys_reboot() ourselves after flushing logs. +extern "C" void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *esf) +{ + saved_crash.magic = CRASH_MAGIC; + saved_crash.reason = reason; + // Capture the faulting thread's stack pointer before we start using the + // handler's own stack for logging. + uint32_t psp_at_entry; + __asm__ volatile("mrs %0, psp" : "=r"(psp_at_entry)); + saved_crash.psp = psp_at_entry; + if (esf) { + saved_crash.pc = esf->basic.pc; + saved_crash.xpsr = esf->basic.xpsr; + saved_crash.lr = esf->basic.lr; + } + // Read Cortex-M33 SCB CFSR + saved_crash.cfsr = *((volatile uint32_t *)0xE000ED28U); + printk("[nrf54l15] FATAL reason=%u pc=0x%08x lr=0x%08x cfsr=0x%08x\n", reason, saved_crash.pc, saved_crash.lr, + saved_crash.cfsr); + + // Walk the failing thread's stack and print any word that looks like a + // Thumb code address (0x1000 — flash end, with the Thumb-mode low bit set). + // The Cortex-M exception frame at PSP holds r0,r1,r2,r3,r12,lr,pc,xpsr + // (8 words); deeper words are the caller's saved frame, which gives a + // crude but useful poor-man's backtrace when CONFIG_DEBUG_COREDUMP is off. + // Found the BLE-init bad_alloc → abort() chain (heap exhaustion under + // CONFIG_BT_BUF_ACL_RX_SIZE=251) when the fault dump alone showed only + // abort itself. Cheap (~150 B of code) and silent until a fault. + uint32_t psp; + __asm__ volatile("mrs %0, psp" : "=r"(psp)); + printk("[nrf54l15] PSP=0x%08x — stack walk:\n", psp); + // Validate PSP before dereferencing. Real faults frequently leave PSP + // pointing at corrupted/unmapped memory, and walking it blindly triggers a + // second fault inside this handler. Restrict to nRF54L15 SRAM (256 KB at + // 0x20000000) with 4-byte alignment, and clamp the walk so we never read + // past the end of RAM. + const uintptr_t SRAM_START = 0x20000000UL; + const uintptr_t SRAM_END = 0x20040000UL; + if (psp < SRAM_START || psp >= SRAM_END || (psp & 0x3U) != 0) { + printk("[nrf54l15] PSP out of SRAM range or unaligned, skipping walk\n"); + } else { + const uint32_t *sp = (const uint32_t *)psp; + int max_words = (int)((SRAM_END - psp) / sizeof(uint32_t)); + if (max_words > 96) + max_words = 96; + for (int i = 0; i < max_words; i++) { + uint32_t v = sp[i]; + if (v >= 0x00001000 && v < 0x00080000 && (v & 1)) { + printk("[nrf54l15] sp[%d]=0x%08x (code)\n", i, v); + } + } + } + + // Give the RTT/printk backend a chance to drain before we reset, otherwise + // the crash log line above is lost and the next boot's "Prev crash" line is + // the only forensic evidence we get. + k_busy_wait(50000); // 50 ms + sys_reboot(SYS_REBOOT_COLD); + // Unreachable; k_fatal_halt as a defensive backstop in case sys_reboot + // returns (it shouldn't). + k_fatal_halt(reason); +} + +int main(void) +{ + uint32_t reset_cause = 0; + hwinfo_get_reset_cause(&reset_cause); + hwinfo_clear_reset_cause(); + printk("[nrf54l15] Reset cause: 0x%08x\n", reset_cause); + + if (saved_crash.magic == CRASH_MAGIC) { + printk("[nrf54l15] Prev crash: reason=%u pc=0x%08x lr=0x%08x psp=0x%08x xpsr=0x%08x cfsr=0x%08x\n", saved_crash.reason, + saved_crash.pc, saved_crash.lr, saved_crash.psp, saved_crash.xpsr, saved_crash.cfsr); + saved_crash.magic = 0; + } + + printk("[nrf54l15] A: main() entry\n"); + printk("[nrf54l15] B: calling setup()\n"); + setup(); + printk("[nrf54l15] C: setup() returned\n"); + while (true) { + loop(); + } + return 0; +} diff --git a/src/platform/nrf54l15/utility/bonding.h b/src/platform/nrf54l15/utility/bonding.h new file mode 100644 index 000000000..c55625bb6 --- /dev/null +++ b/src/platform/nrf54l15/utility/bonding.h @@ -0,0 +1,11 @@ +// utility/bonding.h — stub for nRF54L15/Zephyr +// NodeDB.cpp includes this when ARCH_NRF52 is defined. +// Bluetooth is excluded; this stub satisfies the include chain. +#pragma once + +// BLE role constants (from Bluefruit SDK) +#define BLE_GAP_ROLE_PERIPH 0x01 +#define BLE_GAP_ROLE_CENTRAL 0x02 + +// Stub for bond_print_list() +static inline void bond_print_list(uint8_t) {} diff --git a/src/power.h b/src/power.h index 4b5ef609d..993163c39 100644 --- a/src/power.h +++ b/src/power.h @@ -4,8 +4,10 @@ #include "configuration.h" #ifdef ARCH_ESP32 -// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h -#include +// #include +#include +#include +#include #include #endif @@ -28,11 +30,6 @@ #define NUM_CELLS 1 #endif -#ifdef BAT_MEASURE_ADC_UNIT -extern RTC_NOINIT_ATTR uint64_t RTC_reg_b; -#include "soc/sens_reg.h" // needed for adc pin reset -#endif - #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "modules/Telemetry/Sensor/nullSensor.h" #if __has_include() diff --git a/src/sleep.cpp b/src/sleep.cpp index 792781f6d..626fc4e11 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -18,8 +18,6 @@ #include "target_specific.h" #ifdef ARCH_ESP32 -// "esp_pm_config_esp32_t is deprecated, please include esp_pm.h and use esp_pm_config_t instead" -#include "esp32/pm.h" #include "esp_pm.h" #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" @@ -146,15 +144,31 @@ void initDeepSleep() // If we booted because our timer ran out or the user pressed reset, send those as fake events RESET_REASON hwReason = rtc_get_reset_reason(0); +#ifdef CONFIG_IDF_TARGET_ESP32P4 + if (hwReason == BROWN_OUT_RESET) + reason = "brownout"; + else if (hwReason == HP_CORE_HP_WDT_RESET) + reason = "taskWatchdog"; + else if (hwReason == HP_CORE_LP_WDT_RESET) + reason = "intWatchdog"; + else if (hwReason == CHIP_LP_WDT_RESET) + reason = "chipWatchdog"; + else if (hwReason == SUPER_WDT_RESET) + reason = "superWatchdog"; + else if (hwReason == HP_SYS_HP_WDT_RESET) + reason = "systemWatchdog"; + else if (hwReason == HP_SYS_LP_WDT_RESET) + reason = "systemLowPowerWatchdog"; +#else if (hwReason == RTCWDT_BROWN_OUT_RESET) reason = "brownout"; - - if (hwReason == TG0WDT_SYS_RESET) + else if (hwReason == RTCWDT_RTC_RESET) + reason = "rtcWatchdog"; + else if (hwReason == TG0WDT_SYS_RESET) reason = "taskWatchdog"; - - if (hwReason == TG1WDT_SYS_RESET) + else if (hwReason == TG1WDT_SYS_RESET) reason = "intWatchdog"; - +#endif LOG_INFO("Booted, wake cause %d (boot count %d), reset_reason=%s", wakeCause, bootCount, reason); #endif @@ -397,8 +411,10 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r // NOTE! ESP docs say we must disable bluetooth and wifi before light sleep +#if SOC_PM_SUPPORT_RTC_PERIPH_PD // We want RTC peripherals to stay on esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); +#endif #if defined(BUTTON_PIN) && defined(BUTTON_NEED_PULLUP) gpio_pullup_en((gpio_num_t)BUTTON_PIN); @@ -532,11 +548,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r */ void enableModemSleep() { -#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0) static esp_pm_config_t esp32_config; // filled with zeros because bss -#else - static esp_pm_config_esp32_t esp32_config; // filled with zeros because bss -#endif #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32S2 @@ -545,6 +557,12 @@ void enableModemSleep() esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; #elif CONFIG_IDF_TARGET_ESP32C3 esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32P4 +#if CONFIG_ESP32P4_REV_MIN_FULL < 300 + esp32_config.max_freq_mhz = 360; +#else + esp32_config.max_freq_mhz = 400; +#endif #else esp32_config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; #endif @@ -562,8 +580,8 @@ bool shouldLoraWake(uint32_t msecToWake) void enableLoraInterrupt() { - esp_err_t res; #if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC) + esp_err_t res; res = gpio_pulldown_en((gpio_num_t)LORA_DIO1); if (res != ESP_OK) { LOG_ERROR("gpio_pulldown_en(LORA_DIO1) result %d", res); diff --git a/userPrefs.jsonc b/userPrefs.jsonc index a8201bab3..57ede8bbf 100644 --- a/userPrefs.jsonc +++ b/userPrefs.jsonc @@ -34,6 +34,8 @@ // "USERPREFS_CONFIG_GPS_UPDATE_INTERVAL": "600", // "USERPREFS_CONFIG_POSITION_BROADCAST_INTERVAL": "1800", // "USERPREFS_CONFIG_DEVICE_TELEM_UPDATE_INTERVAL": "900", // Device telemetry update interval in seconds + // "USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL": "900", // Environment telemetry update interval in seconds + // "USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED": "1", // Force BMP280/sensor reads + LoRa broadcast on first boot // "USERPREFS_LORACONFIG_CHANNEL_NUM": "31", // "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST", // "USERPREFS_USE_ADMIN_KEY_0": "{ 0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c }", diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index e91e4dcef..9241ed6f2 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -70,7 +70,7 @@ // Battery #define BATTERY_PIN 34 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO34_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_ATTENUATION \ ADC_ATTEN_DB_2_5 // 2_5-> 100mv-1250mv, 11-> 150mv-3100mv for ESP32 // ESP32-S2/C3/S3 are different diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h index 1f84fffa1..6435e29b2 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h @@ -30,7 +30,7 @@ // Battery sense #define BATTERY_PIN 35 #define ADC_MULTIPLIER 2.01 // 100k + 100k, and add 1% tolerance -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION // SPI diff --git a/variants/esp32/diy/dr-dev/variant.h b/variants/esp32/diy/dr-dev/variant.h index 35b18ee74..e47b4c41c 100644 --- a/variants/esp32/diy/dr-dev/variant.h +++ b/variants/esp32/diy/dr-dev/variant.h @@ -3,7 +3,7 @@ #define I2C_SDA 4 #define I2C_SCL 5 #define BATTERY_PIN 34 -#define ADC_CHANNEL ADC1_GPIO34_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 // GPS #undef GPS_RX_PIN diff --git a/variants/esp32/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h index 68194f869..917962b5f 100644 --- a/variants/esp32/diy/hydra/variant.h +++ b/variants/esp32/diy/hydra/variant.h @@ -11,7 +11,7 @@ // Note: On the ESP32 base version, gpio34-39 are input-only, and do not have internal pull-ups. // If 39 is not being used for a button, it is suggested to remove the #define. #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). diff --git a/variants/esp32/diy/v1/variant.h b/variants/esp32/diy/v1/variant.h index 862969af0..98a9eb9c3 100644 --- a/variants/esp32/diy/v1/variant.h +++ b/variants/esp32/diy/v1/variant.h @@ -11,7 +11,7 @@ #define BUTTON_PIN 39 // The middle button GPIO on the T-Beam #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index d06a6aa6f..3e13f0227 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -4,8 +4,9 @@ extends = arduino_base custom_esp32_kind = custom_mtjson_part = platform = - # renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32 - platformio/espressif32@6.13.0 + # TODO renovate + https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip + ; https://github.com/pioarduino/platform-espressif32.git#develop platform_packages = # renovate: datasource=custom.pio depName=platformio/tool-mklittlefs packageName=platformio/tool/tool-mklittlefs platformio/tool-mklittlefs@1.203.210628 @@ -16,7 +17,7 @@ extra_scripts = extra_scripts/esp32_extra.py build_src_filter = - ${arduino_base.build_src_filter} - - - - - + ${arduino_base.build_src_filter} - - - - - - upload_speed = 921600 debug_init_break = tbreak setup @@ -35,20 +36,18 @@ build_unflags = -std=gnu++11 build_flags = ${arduino_base.build_flags} - -flto -Wall -Wextra -Isrc/platform/esp32 -std=gnu++17 -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + # DO NOT INCREASE THIS TO DEBUG. It appears to trigger a bug in ESP-IDF + # Bluetooth stack with Pixel 8 Android devices: + # https://github.com/espressif/esp-idf/issues/18126#issuecomment-4286197744 + # Once the bug is resolved, we can remove this warning. + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL -DAXP_DEBUG_PORT=Serial - -DCONFIG_BT_NIMBLE_ENABLED - -DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3 - -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 - -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 - -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING -DSERIAL_BUFFER_SIZE=4096 -DSERIAL_HAS_ON_RECEIVE @@ -62,16 +61,13 @@ build_flags = lib_deps = ${arduino_base.lib_deps} ${networking_base.lib_deps} - ${networking_extra.lib_deps} ${environmental_base.lib_deps} ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master https://github.com/meshtastic/esp32_https_server/archive/0c71f380390ad483ff134ad938e07f6cf1226c5b.zip - # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino - h2zero/NimBLE-Arduino@1.4.3 - # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master - https://github.com/dbinfrago/libpax/archive/df424747f9acb86ab07c5a206ded1e8e3650726a.zip + # TODO renovate + https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib lewisxhe/XPowersLib@0.3.3 # renovate: datasource=git-refs depName=meshtastic/Crypto packageName=https://github.com/meshtastic/Crypto gitBranch=master @@ -79,7 +75,30 @@ lib_deps = lib_ignore = segger_rtt - ESP32 BLE Arduino + ; ESP32 BLE Arduino + ; BLE + ; Ignore select Bluetooth libs + NimBLE-Arduino + BluetoothSerial + SimpleBLE + WiFiProv + ArduinoOTA + ; Ignore pioarduino esp32 libs we don't use + ESP_I2S + ESP_NOW + ESP_SR + Insights + Matter + OpenThread + RainMaker + ;SPIFFS + USB + ;NetworkClientSecure + Zigbee + Micro-RTSP + ; testing below + mqtt + esp-mqtt ; leave this commented out to avoid breaking Windows ;upload_port = /dev/ttyUSB0 @@ -92,3 +111,226 @@ lib_ignore = ; customize the partition table ; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables board_build.partitions = partition-table.csv + +custom_component_remove = + espressif/esp_hosted + espressif/esp_wifi_remote + espressif/esp_modem + espressif/esp-dsp + espressif/esp32-camera + espressif/libsodium + espressif/esp-modbus + espressif/qrcode + espressif/esp_insights + espressif/esp_diag_data_store + espressif/esp_diagnostics + espressif/esp_rainmaker + espressif/rmaker_common + espressif/network_provisioning + chmorgan/esp-libhelix-mp3 + espressif/esp-tflite-micro + espressif/esp-sr + espressif/esp_matter + espressif/esp-zboss-lib + espressif/esp-zigbee-lib + espressif/mqtt + +custom_sdkconfig = + ; CONFIG_LOG_DEFAULT_LEVEL=4 + ; CONFIG_LOG_MAXIMUM_LEVEL=4 + '# CONFIG_BT_NIMBLE_LOG_LEVEL_INFO is not set' + CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR=y + CONFIG_LOG_COLORS=y + CONFIG_ARDUHAL_LOG_COLORS=y + CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y + CONFIG_IDF_EXPERIMENTAL_FEATURES=y + ; + ; Compiler options + ; + CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=n + CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y + CONFIG_COMPILER_CXX_EXCEPTIONS=n + CONFIG_COMPILER_STACK_CHECK_MODE_NORM=n + CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y + CONFIG_COMPILER_DISABLE_GCC12_WARNINGS=y + CONFIG_COMPILER_DISABLE_GCC13_WARNINGS=y + CONFIG_COMPILER_DISABLE_GCC14_WARNINGS=y + CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=n + CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE=y + CONFIG_ESP_GDBSTUB_ENABLED=n + CONFIG_ESP_TASK_WDT_INIT=n + CONFIG_NEWLIB_NANO_FORMAT=y + ; LIBC_NEWLIB_NANO_FORMAT is enabled via 'esp32_extra.py' to allow float support + CONFIG_ESP_COREDUMP_ENABLE=n + CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n + CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y + CONFIG_ESP32_ENABLE_COREDUMP=n + CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=n + CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=n + CONFIG_FREERTOS_USE_TRACE_FACILITY=n + CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y + CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y + CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y + CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM=y + CONFIG_ESP_WIFI_IRAM_OPT=n + CONFIG_ESP32_WIFI_RX_IRAM_OPT=n + CONFIG_SPIRAM_CACHE_LIBCHAR_IN_IRAM=n + CONFIG_SPIRAM_CACHE_LIBSTR_IN_IRAM=n + CONFIG_SPIRAM_CACHE_LIBMISC_IN_IRAM=n + CONFIG_SPIRAM_CACHE_LIBTIME_IN_IRAM=n + CONFIG_UNITY_ENABLE_FLOAT=n + CONFIG_UNITY_ENABLE_DOUBLE=n + CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n + CONFIG_SUPPORT_TERMIOS=n + CONFIG_VFS_SUPPORT_TERMIOS=n + CONFIG_VFS_SUPPORT_SELECT=n + CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=n + CONFIG_WS_TRANSPORT=n + CONFIG_PPP_SUPPORT=n + CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI=n + ; CONFIG_LWIP_DHCPS=n + CONFIG_LWIP_PPP_SUPPORT=n + CONFIG_LWIP_IP_FORWARD=n + CONFIG_LWIP_IPV4_NAPT=n + ; + ; MBEDTLS + ; + CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=8192 + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y + ; Switch to custom CA bundle (for Meshtastic MQTT/etc) in the future + ; https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/kconfig-reference.html#certificate-bundle-configuration + ; CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y + ; CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="" + CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=1 + ; #shame + CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION=y + CONFIG_MBEDTLS_SSL_RENEGOTIATION=n + CONFIG_MBEDTLS_SSL_PROTO_DTLS=n + CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=n + CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=n + CONFIG_MBEDTLS_PKCS7_C=n + CONFIG_MBEDTLS_CAMELLIA_C=n + CONFIG_MBEDTLS_CCM_C=n + CONFIG_MBEDTLS_CMAC_C=n + CONFIG_MBEDTLS_SHA512_C=n + CONFIG_MBEDTLS_X509_CRL_PARSE_C=n + CONFIG_MBEDTLS_X509_CSR_PARSE_C=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_PSK=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=n + CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=n + CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=n + CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=n + CONFIG_MDNS_ENABLE_CONSOLE_CLI=n + CONFIG_MDNS_PREDEF_NETIF_AP=n + ; CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n + CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n + CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n + CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=n + CONFIG_ESP_WIFI_ENABLE_SAE_H2E=n + CONFIG_OPENTHREAD_ENABLED=n + CONFIG_ZB_ENABLED=n + CONFIG_IEEE802154_ENABLED=n + CONFIG_ESP_INSIGHTS_ENABLED=n + CONFIG_HTTPD_WS_SUPPORT=n + CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=n + CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=n + ; using esp-idf5-https_server library instead, for now? + CONFIG_ESP_HTTPS_SERVER_ENABLE=n + ; + ; Builtin ESP-MQTT, we don't use this (yet?) + ; + CONFIG_MQTT_TRANSPORT_SSL=n + CONFIG_MQTT_TRANSPORT_WEBSOCKET=n + CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=n + CONFIG_MQTT_PROTOCOL_311=n + ; + ; Ethernet + ; Disabled in esp32_common, re-enable on variants that need it + ; + CONFIG_ETH_ENABLED=n + CONFIG_ARDUINO_SELECTIVE_Ethernet=n + ; + ; Bluetooth + ; + CONFIG_BLE_MESH=n + CONFIG_BT_ENABLED=y + CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y + CONFIG_BT_BLUEDROID_ENABLED=n + CONFIG_BT_NIMBLE_ENABLED=y + CONFIG_BT_NIMBLE_NVS_PERSIST=y + CONFIG_BT_NIMBLE_ROLE_CENTRAL=n + CONFIG_BT_NIMBLE_ROLE_OBSERVER=n + CONFIG_BT_CONTROLLER_ENABLED=y + CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 + CONFIG_BT_NIMBLE_MAX_CCCDS=20 + CONFIG_BT_NIMBLE_MAX_BONDS=6 + CONFIG_BT_NIMBLE_ENABLE_PERIODIC_SYNC=n + CONFIG_BT_NIMBLE_ENABLE_PERIODIC_ADV=n + CONFIG_BT_NIMBLE_EXT_SCAN=n + CONFIG_BT_NIMBLE_EXT_ADV=n + CONFIG_BT_NIMBLE_PERIODIC_ADV_SYNC_TRANSFER=n + CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=n + CONFIG_BT_NIMBLE_BLUFI_ENABLE=n + CONFIG_BT_NIMBLE_PROX_SERVICE=n + CONFIG_BT_NIMBLE_ANS_SERVICE=n + CONFIG_BT_NIMBLE_CTS_SERVICE=n + CONFIG_BT_NIMBLE_HTP_SERVICE=n + CONFIG_BT_NIMBLE_IPSS_SERVICE=n + CONFIG_BT_NIMBLE_TPS_SERVICE=n + CONFIG_BT_NIMBLE_IAS_SERVICE=n + CONFIG_BT_NIMBLE_LLS_SERVICE=n + CONFIG_BT_NIMBLE_SPS_SERVICE=y + CONFIG_BT_NIMBLE_HR_SERVICE=n + CONFIG_BT_NIMBLE_HID_SERVICE=n + CONFIG_BT_NIMBLE_BAS_SERVICE=n + CONFIG_BT_NIMBLE_DIS_SERVICE=n + ; + ; Arduino selective compilation + ; Disable unused Arduino libraries to save space + ; + CONFIG_ARDUINO_SELECTIVE_COMPILATION=y + CONFIG_ARDUINO_SELECTIVE_SPI=y + CONFIG_ARDUINO_SELECTIVE_Wire=y + CONFIG_ARDUINO_SELECTIVE_ESP_SR=n + CONFIG_ARDUINO_SELECTIVE_EEPROM=y + CONFIG_ARDUINO_SELECTIVE_Preferences=y + CONFIG_ARDUINO_SELECTIVE_Ticker=y + CONFIG_ARDUINO_SELECTIVE_Update=y + CONFIG_ARDUINO_SELECTIVE_Zigbee=n + CONFIG_ARDUINO_SELECTIVE_FS=y + CONFIG_ARDUINO_SELECTIVE_SD=y + CONFIG_ARDUINO_SELECTIVE_SD_MMC=y + CONFIG_ARDUINO_SELECTIVE_SPIFFS=y + CONFIG_ARDUINO_SELECTIVE_FFat=n + CONFIG_ARDUINO_SELECTIVE_LittleFS=y + CONFIG_ARDUINO_SELECTIVE_Network=y + ; CONFIG_ARDUINO_SELECTIVE_Ethernet=n + CONFIG_ARDUINO_SELECTIVE_PPP=n + CONFIG_ARDUINO_SELECTIVE_Hash=y + CONFIG_ARDUINO_SELECTIVE_ArduinoOTA=n + CONFIG_ARDUINO_SELECTIVE_AsyncUDP=y + CONFIG_ARDUINO_SELECTIVE_DNSServer=n + CONFIG_ARDUINO_SELECTIVE_ESPmDNS=y + CONFIG_ARDUINO_SELECTIVE_HTTPClient=n + CONFIG_ARDUINO_SELECTIVE_Matter=n + CONFIG_ARDUINO_SELECTIVE_NetBIOS=n + CONFIG_ARDUINO_SELECTIVE_WebServer=n + CONFIG_ARDUINO_SELECTIVE_WiFi=y + CONFIG_ARDUINO_SELECTIVE_NetworkClientSecure=y + CONFIG_ARDUINO_SELECTIVE_WiFiProv=n + CONFIG_ARDUINO_SELECTIVE_BLE=y + CONFIG_ARDUINO_SELECTIVE_BluetoothSerial=n + CONFIG_ARDUINO_SELECTIVE_SimpleBLE=n + CONFIG_ARDUINO_SELECTIVE_RainMaker=n + CONFIG_ARDUINO_SELECTIVE_OpenThread=n + CONFIG_ARDUINO_SELECTIVE_Insights=n diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index f40f1d064..50b475a6c 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -11,12 +11,39 @@ build_src_filter = build_flags = ${esp32_common.build_flags} + -mtext-section-literals + -D ESP32_FORCE_IRAM_MEMSET + -Wl,--wrap=memset + -Wl,--wrap=memcpy -DMESHTASTIC_EXCLUDE_AUDIO=1 -DMESHTASTIC_EXCLUDE_ACCELEROMETER=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DMESHTASTIC_EXCLUDE_WEBSERVER=1 -DMESHTASTIC_EXCLUDE_RANGETEST=1 + ; HACK HACK HACK this is just a proof of concept ESP32+NimBLE but these + ; includes need to be done properly somehow + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/porting/nimble/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/port/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/porting/npl/freertos/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/transport/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/services/gap/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/esp-hci/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/services/gatt/include + -I${platformio.packages_dir}/framework-espidf/components/bt/host/nimble/nimble/nimble/host/util/include +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y + '# CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY is not set' + '# CONFIG_BTDM_CTRL_MODE_BTDM is not set' + '# CONFIG_BT_BLUEDROID_ENABLED is not set' + CONFIG_BT_NIMBLE_ENABLED=y + CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y + +; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra +; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets lib_deps = ${arduino_base.lib_deps} ${networking_base.lib_deps} @@ -24,8 +51,8 @@ lib_deps = ${radiolib_base.lib_deps} ${environmental_base.lib_deps} ${environmental_extra_no_bsec.lib_deps} - # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino - h2zero/NimBLE-Arduino@1.4.3 + # TODO renovate + https://github.com/mverch67/libpax/archive/6f52ee989301cdabaeef00bcbf93bff55708ce2f.zip # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib lewisxhe/XPowersLib@0.3.3 # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto diff --git a/variants/esp32/heltec_wsl_v2.1/variant.h b/variants/esp32/heltec_wsl_v2.1/variant.h index db374afb6..c0afeac82 100644 --- a/variants/esp32/heltec_wsl_v2.1/variant.h +++ b/variants/esp32/heltec_wsl_v2.1/variant.h @@ -12,7 +12,7 @@ #define ADC_CTRL 21 #define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 37 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_CHANNEL_1 +#define ADC_CHANNEL ADC_CHANNEL_1 // ratio of voltage divider = 3.20 (R1=100k, R2=220k) #define ADC_MULTIPLIER 3.2 diff --git a/variants/esp32/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h index 84a1e1966..ede008bab 100644 --- a/variants/esp32/m5stack_coreink/variant.h +++ b/variants/esp32/m5stack_coreink/variant.h @@ -92,7 +92,7 @@ #define PIN_EINK_MOSI 23 // EPD_MOSI #define BATTERY_PIN 35 -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 // https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/schematic/Core/m5paper/M5_PAPER_SCH.pdf // https://github.com/m5stack/M5Core-Ink/blob/master/examples/Basics/FactoryTest/FactoryTest.ino#L58 // VBAT diff --git a/variants/esp32/nano-g1-explorer/variant.h b/variants/esp32/nano-g1-explorer/variant.h index f3640241a..48d064710 100644 --- a/variants/esp32/nano-g1-explorer/variant.h +++ b/variants/esp32/nano-g1-explorer/variant.h @@ -33,7 +33,7 @@ #endif #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. #define ADC_MULTIPLIER 2 diff --git a/variants/esp32/radiomaster_900_bandit/platformio.ini b/variants/esp32/radiomaster_900_bandit/platformio.ini index 6729235ed..fdc483fe3 100644 --- a/variants/esp32/radiomaster_900_bandit/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit/platformio.ini @@ -7,7 +7,7 @@ build_flags = -DVTABLES_IN_FLASH=1 -DCONFIG_DISABLE_HAL_LOCKS=1 -DHAS_STK8XXX=1 - -O2 + ; -O2 -I variants/esp32/radiomaster_900_bandit board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/rak11200/variant.h b/variants/esp32/rak11200/variant.h index a38ac83b7..feb38a8e1 100644 --- a/variants/esp32/rak11200/variant.h +++ b/variants/esp32/rak11200/variant.h @@ -47,7 +47,7 @@ static const uint8_t SCK = 33; #define PIN_VBAT WB_A0 #define BATTERY_PIN PIN_VBAT -#define ADC_CHANNEL ADC1_GPIO36_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 // https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13300/ diff --git a/variants/esp32/station-g1/variant.h b/variants/esp32/station-g1/variant.h index 6c3a39261..29f3cc1d8 100644 --- a/variants/esp32/station-g1/variant.h +++ b/variants/esp32/station-g1/variant.h @@ -32,7 +32,7 @@ #endif #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_SAMPLES 30 // Set the number of samples, It has an effect of increasing sensitivity. #define ADC_MULTIPLIER 6.45 #define CELL_TYPE_LION // same curve for liion/lipo diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index 96e9879ce..b1367df87 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -16,6 +16,7 @@ board_check = true build_flags = ${esp32_base.build_flags} -D TBEAM_V10 -I variants/esp32/tbeam + upload_speed = 921600 [env:tbeam-displayshield] diff --git a/variants/esp32/tbeam_v07/variant.h b/variants/esp32/tbeam_v07/variant.h index 898705ce2..1fdb295bf 100644 --- a/variants/esp32/tbeam_v07/variant.h +++ b/variants/esp32/tbeam_v07/variant.h @@ -6,7 +6,7 @@ #define BUTTON_PIN 39 #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define USE_RF95 #define LORA_DIO0 26 // a No connect on the SX1262 module diff --git a/variants/esp32/tlora_v1_3/variant.h b/variants/esp32/tlora_v1_3/variant.h index 2b0395d8a..ab783e57d 100644 --- a/variants/esp32/tlora_v1_3/variant.h +++ b/variants/esp32/tlora_v1_3/variant.h @@ -1,5 +1,5 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 diff --git a/variants/esp32/tlora_v2/variant.h b/variants/esp32/tlora_v2/variant.h index 099fdc2ee..cd714b8fe 100644 --- a/variants/esp32/tlora_v2/variant.h +++ b/variants/esp32/tlora_v2/variant.h @@ -1,5 +1,5 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 diff --git a/variants/esp32/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h index 5488fddf4..27d2fb5ab 100644 --- a/variants/esp32/tlora_v2_1_16/variant.h +++ b/variants/esp32/tlora_v2_1_16/variant.h @@ -1,5 +1,5 @@ #define BATTERY_PIN 35 -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define BATTERY_SENSE_SAMPLES 30 // ratio of voltage divider = 2.0 (R42=100k, R43=100k) diff --git a/variants/esp32/tlora_v2_1_18/variant.h b/variants/esp32/tlora_v2_1_18/variant.h index 1ab08c364..75578e1ca 100644 --- a/variants/esp32/tlora_v2_1_18/variant.h +++ b/variants/esp32/tlora_v2_1_18/variant.h @@ -1,7 +1,7 @@ #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (R42=100k, R43=100k) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 diff --git a/variants/esp32/trackerd/variant.h b/variants/esp32/trackerd/variant.h index 8071ba99d..8ba32c776 100644 --- a/variants/esp32/trackerd/variant.h +++ b/variants/esp32/trackerd/variant.h @@ -22,7 +22,7 @@ #undef BAT_MEASURE_ADC_UNIT #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage #define ADC_MULTIPLIER 1.34 // tracked resistance divider is 100k+470k, so it can not fillfull well on esp32 adc -#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // lower dB for high resistance voltage divider #undef GPS_RX_PIN diff --git a/variants/esp32c3/esp32c3.ini b/variants/esp32c3/esp32c3.ini index e5f117ad7..02042047f 100644 --- a/variants/esp32c3/esp32c3.ini +++ b/variants/esp32c3/esp32c3.ini @@ -5,6 +5,19 @@ custom_esp32_kind = esp32c3 monitor_speed = 115200 monitor_filters = esp32_c3_exception_decoder +build_flags = + ${esp32_common.build_flags} + ; Linker script to align text.handler_execute section to 4 bytes + -Wl,-Tsrc/platform/esp32/align-text.handler_execute-4.ld + +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + ; ESP32c3 doesn't support SD_MMC + CONFIG_ARDUINO_SELECTIVE_SD_MMC=n + ; CONFIG_BT_NIMBLE_EXT_ADV=y + ; CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=257 + ; CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + lib_deps = ${esp32_common.lib_deps} # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master diff --git a/variants/esp32c6/esp32c6.ini b/variants/esp32c6/esp32c6.ini index 552c4ee24..ae91e6e81 100644 --- a/variants/esp32c6/esp32c6.ini +++ b/variants/esp32c6/esp32c6.ini @@ -1,50 +1,34 @@ [esp32c6_base] extends = esp32_common -platform = - # Do not renovate until we have switched to pioarduino tagged builds - https://github.com/Jason2866/platform-espressif32/archive/22faa566df8c789000f8136cd8d0aca49617af55.zip -platform_packages = - # HACK: This release was automatically removed upstream - framework-arduinoespressif32 @ https://github.com/vidplace7/platform-espressif32/releases/download/meshtastic-esp32c6/framework-arduinoespressif32-all-release_v5.1-124d64e.zip -build_flags = - ${arduino_base.build_flags} - -Wall - -Wextra - -Isrc/platform/esp32 - -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING - -DSERIAL_BUFFER_SIZE=4096 - -DLIBPAX_ARDUINO - -DLIBPAX_WIFI - -DLIBPAX_BLE - -DMESHTASTIC_EXCLUDE_WEBSERVER - ;-DDEBUG_HEAP - ; TEMP - -DHAS_BLUETOOTH=0 - -DMESHTASTIC_EXCLUDE_PAXCOUNTER - -DMESHTASTIC_EXCLUDE_BLUETOOTH +custom_esp32_kind = esp32c6 -lib_deps = - ${arduino_base.lib_deps} - ${networking_base.lib_deps} - ${environmental_base.lib_deps} - ${environmental_extra.lib_deps} - ${radiolib_base.lib_deps} - # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@0.3.3 - # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master - https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip - # renovate: datasource=git-refs depName=meshtastic-Crypto packageName=https://github.com/meshtastic/Crypto gitBranch=master - https://github.com/meshtastic/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip +build_flags = + ${esp32_common.build_flags} + ; Linker script to align text.handler_execute section to 4 bytes + -Wl,-Tsrc/platform/esp32/align-text.handler_execute-4.ld + ; Exclude Paxcounter, it uses 'esp_vhci_host_send_packet' whch is not available on ESP32-C6 + ; https://github.com/espressif/arduino-esp32/issues/11716 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER build_src_filter = - ${esp32_common.build_src_filter} - + ${esp32_common.build_src_filter} monitor_speed = 460800 monitor_filters = esp32_c3_exception_decoder +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + ; ESP32c6 doesn't support SD_MMC + CONFIG_ARDUINO_SELECTIVE_SD_MMC=n + ; CONFIG_BT_NIMBLE_EXT_ADV=y + ; CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=257 + ; CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + lib_ignore = ${esp32_common.lib_ignore} - NonBlockingRTTTL - NimBLE-Arduino libpax - + +lib_deps = + ${esp32_common.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master + https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index bf605ca61..59eca377b 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -17,28 +17,12 @@ board_build.partitions = default_16MB.csv ;Normal method upload_protocol = esptool ;upload_port = /dev/ttyACM2 -build_unflags = - -D HAS_BLUETOOTH - -D MESHTASTIC_EXCLUDE_BLUETOOTH - -D HAS_WIFI -lib_deps = - ${esp32c6_base.lib_deps} - # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino - h2zero/NimBLE-Arduino@2.3.7 build_flags = ${esp32c6_base.build_flags} -D M5STACK_UNITC6L -I variants/esp32c6/m5stack_unitc6l - -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 - -D HAS_BLUETOOTH=1 - -DCONFIG_BT_NIMBLE_EXT_ADV=1 - -DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 - -D NIMBLE_TWO monitor_speed=115200 -lib_ignore = - NonBlockingRTTTL - libpax build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> diff --git a/variants/esp32c6/m5stack_unitc6l/variant.cpp b/variants/esp32c6/m5stack_unitc6l/variant.cpp index 8e26b4ab7..7dc5785f6 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.cpp +++ b/variants/esp32c6/m5stack_unitc6l/variant.cpp @@ -52,23 +52,25 @@ void c6l_init() vTaskDelay(10 / portTICK_PERIOD_MS); i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data); vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11100000); // P5,P6,P7 as outputs vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00011100); // High-Impedance vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11100011); // pull up/down select, 0 down, 1 up vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11100011); // pull up/down enable, 0 disable, 1 enable vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断 + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 default to high level; button press triggers the interrupt vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 interrupts enabled (0 = enable, 1 = disable) vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0 + i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // default output is 0 + vTaskDelay(10 / portTICK_PERIOD_MS); + i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // read IRQ_STA to clear the flag vTaskDelay(10 / portTICK_PERIOD_MS); - i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志 i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data); - setbit(in_data, 6); // HIGH + setbit(in_data, 6); // enable SX_ANT_SW + setbit(in_data, 5); // enable SX_LNA_EN i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data); } diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h index 1654ee590..576d4e114 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.h +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -48,6 +48,9 @@ void c6l_init(); #define SSD1306_RESET 15 // #define OLED_DG 1 #endif +// Tiny OLED panel — opts into compile-time layout/font/feature substitutions +// gated on OLED_TINY across the graphics stack. +#define OLED_TINY #define SCREEN_TRANSITION_FRAMERATE 10 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness diff --git a/variants/esp32c6/tlora_c6/platformio.ini b/variants/esp32c6/tlora_c6/platformio.ini index 6b402d7c5..07585e79f 100644 --- a/variants/esp32c6/tlora_c6/platformio.ini +++ b/variants/esp32c6/tlora_c6/platformio.ini @@ -8,3 +8,11 @@ build_flags = -I variants/esp32c6/tlora_c6 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + ; Disable screen + audio. + ; ESP32-C6 with 4MB flash is TIGHT on space. + -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_AUDIO=1 + +lib_ignore = + ${esp32c6_base.lib_ignore} + ESP32 Codec2 diff --git a/variants/esp32p4/esp32p4.ini b/variants/esp32p4/esp32p4.ini new file mode 100644 index 000000000..44094ee04 --- /dev/null +++ b/variants/esp32p4/esp32p4.ini @@ -0,0 +1,110 @@ +[esp32p4_base] +extends = esp32_common +custom_esp32_kind = esp32p4 +board_build.mcu = esp32p4 + +build_unflags= + -DHAS_UDP_MULTICAST=1 + +build_flags = + ${esp32_common.build_flags} + -DMESHTASTIC_EXCLUDE_WIFI=1 ; TODO + -DMESHTASTIC_EXCLUDE_MQTT=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + +build_src_filter = + ${esp32_common.build_src_filter} - - - - - - - + +extra_scripts = + ${esp32_common.extra_scripts} + extra_scripts/ld_response_file.py + +; Override esp32_common component pruning: keep esp_hosted + esp_wifi_remote for P4 hosted BT +custom_component_remove = + espressif/esp_modem + espressif/esp-dsp + espressif/esp32-camera + espressif/libsodium + espressif/esp-modbus + espressif/qrcode + espressif/esp_insights + espressif/esp_diag_data_store + espressif/esp_diagnostics + espressif/esp_rainmaker + espressif/rmaker_common + espressif/network_provisioning + chmorgan/esp-libhelix-mp3 + espressif/esp-tflite-micro + espressif/esp-sr + espressif/esp_matter + espressif/esp-zboss-lib + espressif/esp-zigbee-lib + espressif/mqtt + +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + CONFIG_ARDUINO_SELECTIVE_SD_MMC=y + CONFIG_BT_CONTROLLER_DISABLED=y + CONFIG_ESP_WIFI_REMOTE_ENABLED=y + # esp_hosted core + CONFIG_ESP_HOSTED_ENABLED=y + # Board: custom (not Espressif EV board) + CONFIG_ESP_HOSTED_P4_DEV_BOARD_NONE=y + # Delay after C6 reset to allow boot (e.g. old v2.3.0 may be slow) + CONFIG_ESP_HOSTED_SDIO_RESET_DELAY_MS=1500 + CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y + CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y + CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET="esp32c6" + CONFIG_ESP_HOSTED_CP_TARGET_ESP32C6=y + CONFIG_ESP_HOSTED_CP_TARGET_ESP32H2=n + CONFIG_ESP_HOSTED_PRIV_SDIO_OPTION=y + CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE=n + CONFIG_ESP_HOSTED_TRANSPORT_SDIO=y + CONFIG_ESP_HOSTED_ENABLE_NIMBLE=y + CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y + CONFIG_ESP_HOSTED_NIMBLE_HCI_VHCI=y + CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER=y + CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS=3 + # OTA method: LittleFS + CONFIG_OTA_METHOD_LITTLEFS=y + + # Skip version check — we force OTA regardless + # CONFIG_OTA_VERSION_CHECK_HOST_SLAVE is not set + # CONFIG_OTA_VERSION_CHECK_SLAVEFW_SLAVE is not set + + # RX streaming mode + CONFIG_ESP_HOSTED_SLAVE_RESET_ON_EVERY_HOST_BOOTUP=y + ;CONFIG_ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARY=y + # SOC_LCD (MUI / lovyanGFX) + CONFIG_SOC_LCD_I80_SUPPORTED=y + CONFIG_SOC_LCD_RGB_SUPPORTED=y + CONFIG_SOC_MIPI_DSI_SUPPORTED=y + # stack dump + CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y ; remove for production version + ;CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y; for production version + ;CONFIG_ESP_SYSTEM_PANIC_GDBSTUB=y ; for target debugging + # Logger: verbose for experiment + CONFIG_LOG_DEFAULT_LEVEL_INFO=y + CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y + + +lib_ignore = + ${esp32_common.lib_ignore} + libpax + esp8266-oled-ssd1306 + bsec2 + esp32_idf5_https_server + esp_driver_cam + esp_http_server + +; Override lib_deps to exclude environmental_extras +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${networking_extra.lib_deps} + ${environmental_base.lib_deps} + ${radiolib_base.lib_deps} + # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto + rweather/Crypto@0.4.0 + # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master + https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip diff --git a/variants/esp32s2/esp32s2.ini b/variants/esp32s2/esp32s2.ini index 836e31d8d..189f51898 100644 --- a/variants/esp32s2/esp32s2.ini +++ b/variants/esp32s2/esp32s2.ini @@ -12,13 +12,18 @@ build_flags = -DHAS_BLUETOOTH=0 -DMESHTASTIC_EXCLUDE_PAXCOUNTER -DMESHTASTIC_EXCLUDE_BLUETOOTH + -mtext-section-literals -lib_deps = - ${esp32_common.lib_deps} - # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master - https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} lib_ignore = ${esp32_common.lib_ignore} NimBLE-Arduino libpax + +lib_deps = + ${esp32_common.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master + https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip + \ No newline at end of file diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h index 5decc7eb2..628ebcf8b 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h @@ -7,7 +7,7 @@ #define BUTTON_PIN 0 // BOOT button #define BATTERY_PIN 1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_MULTIPLIER 103.0 // Calibrated value #define ADC_ATTENUATION ADC_ATTEN_DB_0 #define ADC_CTRL 37 diff --git a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h index 85321cbe0..0991afd53 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h @@ -18,7 +18,7 @@ // Battery voltage monitoring - TODO: test, currently untested, copied from T3S3 variant #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_MULTIPLIER \ 2.11 // ratio of voltage divider = 2.0 (R10=1M, R13=1M), plus some undervoltage correction - TODO: this was carried over from // the T3S3, test to see if the undervoltage correction is needed. diff --git a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.cpp deleted file mode 100644 index c6ff6b8d8..000000000 --- a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "mesh/NodeDB.h" - -void variantDefaultConfig() -{ - config.network.eth_enabled = true; -} diff --git a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h index c5afd574a..316763499 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-G3/variant.h @@ -34,3 +34,5 @@ #define ETH_INT_PIN 10 #define ETH_RST_PIN 9 // #define ETH_ADDR 1 + +#define USE_ETHERNET_DEFAULT 1 \ No newline at end of file diff --git a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h index c8e56426f..668208586 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h @@ -40,8 +40,8 @@ // Battery // #define BATTERY_PIN 2 #define BATTERY_PIN 17 -// #define ADC_CHANNEL ADC1_GPIO2_CHANNEL -#define ADC_CHANNEL ADC2_GPIO17_CHANNEL +// #define ADC_CHANNEL ADC_CHANNEL_1 +#define ADC_CHANNEL ADC_CHANNEL_6 #define BATTERY_SENSE_RESOLUTION_BITS 12 #define BATTERY_SENSE_RESOLUTION 4096.0 #undef AREF_VOLTAGE diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 2d02c7f27..55bcd0dc7 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -16,7 +16,7 @@ // USB_CHECK #define EXT_PWR_DETECT 12 #define BATTERY_PIN 8 -#define ADC_CHANNEL ADC1_GPIO8_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_7 #define ADC_MULTIPLIER 2.0 // 2.0 + 10% for correction of display undervoltage. diff --git a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp deleted file mode 100644 index c6ff6b8d8..000000000 --- a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "mesh/NodeDB.h" - -void variantDefaultConfig() -{ - config.network.eth_enabled = true; -} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h index 9724b20fa..da73bcd1e 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M7/variant.h @@ -41,3 +41,5 @@ #define ETH_CS_PIN 21 #define ETH_INT_PIN 45 // #define ETH_ADDR 1 + +#define USE_ETHERNET_DEFAULT 1 \ No newline at end of file diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h index c9200b96b..f8a0e28c8 100644 --- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h @@ -17,7 +17,7 @@ // #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to // measure battery voltage ratio of voltage divider = 2.0 (assumption) // #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -// #define ADC_CHANNEL ADC1_GPIO1_CHANNEL +// #define ADC_CHANNEL ADC_CHANNEL_0 #define I2C_SDA SDA // 21 #define I2C_SCL SCL // 15 diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index 71116279c..f43f104d7 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -26,3 +26,4 @@ build_flags = -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue -DARDUINO_USB_MODE=0 + -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini index 60b030d42..b90325f51 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini @@ -8,13 +8,9 @@ board_build.f_cpu = 240000000L upload_protocol = esptool ;upload_port = /dev/ttyACM0 upload_speed = 921600 -build_unflags = - ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D PRIVATE_HW -I variants/esp32s3/diy/my_esp32s3_diy_oled -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue - -DARDUINO_USB_MODE=0 diff --git a/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini b/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini index 681ee6c45..9d977e45d 100644 --- a/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini +++ b/variants/esp32s3/diy/t-energy-s3_e22/platformio.ini @@ -6,13 +6,10 @@ board_build.partitions = default_16MB.csv board_level = extra board_upload.flash_size = 16MB ;Specify the FLASH capacity as 16MB board_build.arduino.memory_type = qio_opi ;Enable internal PSRAM -build_unflags = - ${esp32s3_base.build_unflags} - -D ARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D EBYTE_ESP32_S3 -D BOARD_HAS_PSRAM - -D ARDUINO_USB_MODE=0 + -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 -I variants/esp32s3/diy/t-energy-s3_e22 diff --git a/variants/esp32s3/diy/t-energy-s3_e22/variant.h b/variants/esp32s3/diy/t-energy-s3_e22/variant.h index 6933d7715..10a0fc20d 100644 --- a/variants/esp32s3/diy/t-energy-s3_e22/variant.h +++ b/variants/esp32s3/diy/t-energy-s3_e22/variant.h @@ -4,7 +4,7 @@ // Battery #define BATTERY_PIN 3 #define ADC_MULTIPLIER 2.0 -#define ADC_CHANNEL ADC1_GPIO3_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_2 // Button on NanoVHF PCB #define BUTTON_PIN 39 diff --git a/variants/esp32s3/elecrow_panel/pins_arduino.h b/variants/esp32s3/elecrow_panel/pins_arduino.h index 81c9e0a2c..75b5d561a 100644 --- a/variants/esp32s3/elecrow_panel/pins_arduino.h +++ b/variants/esp32s3/elecrow_panel/pins_arduino.h @@ -15,9 +15,9 @@ static const uint8_t MOSI = 48; static const uint8_t MISO = 47; static const uint8_t SCK = 41; -static const uint8_t SPI_MOSI = 6; -static const uint8_t SPI_SCK = 5; -static const uint8_t SPI_MISO = 4; +// static const uint8_t SPI_MOSI = 6; +// static const uint8_t SPI_SCK = 5; +// static const uint8_t SPI_MISO = 4; static const uint8_t A0 = 1; static const uint8_t A1 = 2; diff --git a/variants/esp32s3/elecrow_panel/platformio.ini b/variants/esp32s3/elecrow_panel/platformio.ini index 1b91a02bb..a707a832e 100644 --- a/variants/esp32s3/elecrow_panel/platformio.ini +++ b/variants/esp32s3/elecrow_panel/platformio.ini @@ -38,6 +38,9 @@ build_flags = ${esp32s3_base.build_flags} -Os -D USE_PACKET_API -D HAS_SDCARD -D SD_SPI_FREQUENCY=75000000 + -D SPI_MISO=4 + -D SPI_MOSI=6 + -D SPI_SCK=5 lib_deps = ${esp32s3_base.lib_deps} ${device-ui_base.lib_deps} @@ -47,7 +50,9 @@ lib_deps = ${esp32s3_base.lib_deps} earlephilhower/ESP8266SAM@1.1.0 # renovate: datasource=custom.pio depName=TCA9534 packageName=hideakitai/library/TCA9534 hideakitai/TCA9534@0.1.1 - lovyan03/LovyanGFX@1.2.0 ; note: v1.2.7 breaks the elecrow 7" display functionality + # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX + lovyan03/LovyanGFX@1.2.19 + ; REVISIT note: v1.2.7 breaks the elecrow 7" display functionality [crowpanel_small_esp32s3_base] ; 2.4, 2.8, 3.5 inch extends = crowpanel_base diff --git a/variants/esp32s3/esp32-s3-pico/variant.h b/variants/esp32s3/esp32-s3-pico/variant.h index 65732171a..1629ebebe 100644 --- a/variants/esp32s3/esp32-s3-pico/variant.h +++ b/variants/esp32s3/esp32-s3-pico/variant.h @@ -21,8 +21,8 @@ // https://www.waveshare.com/img/devkit/ESP32-S3-Pico/ESP32-S3-Pico-details-inter-1.jpg // digram is incorrect labeled as battery pin is getting readings on GPIO7_CH1? #define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL -// #define ADC_CHANNEL ADC1_GPIO6_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 +// #define ADC_CHANNEL ADC_CHANNEL_5 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) #define ADC_MULTIPLIER 3.1 // 3.0 + a bit for being optimistic diff --git a/variants/esp32s3/esp32s3.ini b/variants/esp32s3/esp32s3.ini index 299415442..17b451d42 100644 --- a/variants/esp32s3/esp32s3.ini +++ b/variants/esp32s3/esp32s3.ini @@ -4,6 +4,16 @@ custom_esp32_kind = esp32s3 monitor_speed = 115200 +build_flags = + ${esp32_common.build_flags} + -mtext-section-literals + +custom_sdkconfig = + ${esp32_common.custom_sdkconfig} + ; CONFIG_BT_NIMBLE_EXT_ADV=y + ; CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=257 + ; CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2 + lib_deps = ${esp32_common.lib_deps} # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master diff --git a/variants/esp32s3/hackaday-communicator/platformio.ini b/variants/esp32s3/hackaday-communicator/platformio.ini index 8fd275c0e..cee393e48 100644 --- a/variants/esp32s3/hackaday-communicator/platformio.ini +++ b/variants/esp32s3/hackaday-communicator/platformio.ini @@ -16,5 +16,5 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/hackaday-communicator lib_deps = ${esp32s3_base.lib_deps} - # renovate: datasource=git-refs depName=meshtastic-Arduino_GFX packageName=https://github.com/meshtastic/Arduino_GFX gitBranch=master - https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip \ No newline at end of file + # renovate: datasource=git-refs depName=moononournation-Arduino_GFX packageName=https://github.com/moononournation/Arduino_GFX gitBranch=master + https://github.com/moononournation/Arduino_GFX/archive/refs/tags/v1.6.5.zip \ No newline at end of file diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h index f689b20a8..71c5f0743 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h +++ b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h @@ -8,7 +8,7 @@ #define BUTTON_ACTIVE_PULLUP false #define BATTERY_PIN 7 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER (4.9 * 1.045) #define ADC_CTRL 36 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/heltec_sensor_hub/variant.h b/variants/esp32s3/heltec_sensor_hub/variant.h index 64255c038..64450e0ea 100644 --- a/variants/esp32s3/heltec_sensor_hub/variant.h +++ b/variants/esp32s3/heltec_sensor_hub/variant.h @@ -6,7 +6,7 @@ #define BUTTON_ACTIVE_PULLUP false #define BATTERY_PIN 7 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER (4.9 * 1.045) #define ADC_CTRL 34 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/heltec_v3/variant.h b/variants/esp32s3/heltec_v3/variant.h index d2d904d9c..e4a2004e5 100644 --- a/variants/esp32s3/heltec_v3/variant.h +++ b/variants/esp32s3/heltec_v3/variant.h @@ -16,7 +16,7 @@ #define ADC_CTRL 37 #define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h index 72f55d09f..5a806653e 100644 --- a/variants/esp32s3/heltec_v4/variant.h +++ b/variants/esp32s3/heltec_v4/variant.h @@ -4,7 +4,7 @@ #define ADC_CTRL 37 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/heltec_v4_r8/variant.h b/variants/esp32s3/heltec_v4_r8/variant.h index 1f638f24c..d59f0ae2c 100644 --- a/variants/esp32s3/heltec_v4_r8/variant.h +++ b/variants/esp32s3/heltec_v4_r8/variant.h @@ -3,7 +3,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.035 diff --git a/variants/esp32s3/heltec_vision_master_e213/platformio.ini b/variants/esp32s3/heltec_vision_master_e213/platformio.ini index bd1b73d2b..fb97fd983 100644 --- a/variants/esp32s3/heltec_vision_master_e213/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e213/platformio.ini @@ -16,6 +16,7 @@ board = heltec_vision_master_e213 board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} + -DBOARD_HAS_PSRAM -Ivariants/esp32s3/heltec_vision_master_e213 -DHELTEC_VISION_MASTER_E213 -DUSE_EINK @@ -44,6 +45,7 @@ build_src_filter = build_flags = ${esp32s3_base.build_flags} ${inkhud.build_flags} + -D BOARD_HAS_PSRAM -I variants/esp32s3/heltec_vision_master_e213 -D HELTEC_VISION_MASTER_E213 lib_deps = diff --git a/variants/esp32s3/heltec_vision_master_e213/variant.h b/variants/esp32s3/heltec_vision_master_e213/variant.h index c9aaa2ee8..931a2d7bb 100644 --- a/variants/esp32s3/heltec_vision_master_e213/variant.h +++ b/variants/esp32s3/heltec_vision_master_e213/variant.h @@ -28,7 +28,7 @@ #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_MULTIPLIER 4.9 * 1.03 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 diff --git a/variants/esp32s3/heltec_vision_master_e290/variant.h b/variants/esp32s3/heltec_vision_master_e290/variant.h index b32715e39..daeac0a51 100644 --- a/variants/esp32s3/heltec_vision_master_e290/variant.h +++ b/variants/esp32s3/heltec_vision_master_e290/variant.h @@ -27,7 +27,7 @@ #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 7 -#define ADC_CHANNEL ADC1_GPIO7_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_MULTIPLIER 4.9 * 1.03 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 diff --git a/variants/esp32s3/heltec_vision_master_t190/variant.h b/variants/esp32s3/heltec_vision_master_t190/variant.h index a6a809207..037460c2f 100644 --- a/variants/esp32s3/heltec_vision_master_t190/variant.h +++ b/variants/esp32s3/heltec_vision_master_t190/variant.h @@ -45,7 +45,7 @@ #define ADC_CTRL 46 #define ADC_CTRL_ENABLED HIGH #define BATTERY_PIN 6 -#define ADC_CHANNEL ADC1_GPIO6_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_5 #define ADC_MULTIPLIER 4.9 * 1.03 // Voltage divider is roughly 1:1 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // Voltage divider output is quite high diff --git a/variants/esp32s3/heltec_wireless_paper/variant.h b/variants/esp32s3/heltec_wireless_paper/variant.h index 7f57bb67f..c18fa40c1 100644 --- a/variants/esp32s3/heltec_wireless_paper/variant.h +++ b/variants/esp32s3/heltec_wireless_paper/variant.h @@ -24,7 +24,7 @@ #define VEXT_ON_VALUE LOW #define ADC_CTRL 19 #define BATTERY_PIN 20 -#define ADC_CHANNEL ADC2_GPIO20_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_9 #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high diff --git a/variants/esp32s3/heltec_wireless_paper_v1/variant.h b/variants/esp32s3/heltec_wireless_paper_v1/variant.h index 59dd485f6..62615fa46 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/variant.h +++ b/variants/esp32s3/heltec_wireless_paper_v1/variant.h @@ -25,7 +25,7 @@ #define VEXT_ON_VALUE LOW #define ADC_CTRL 19 #define BATTERY_PIN 20 -#define ADC_CHANNEL ADC2_GPIO20_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_9 #define ADC_MULTIPLIER 2 // Voltage divider is roughly 1:1 #define BAT_MEASURE_ADC_UNIT 2 // Use ADC2 #define ADC_ATTENUATION ADC_ATTEN_DB_12 // Voltage divider output is quite high diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini index 1450bb45c..3f11bf7e2 100644 --- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini @@ -15,10 +15,12 @@ board = heltec_wireless_tracker board_build.partitions = default_8MB.csv upload_protocol = esptool -build_flags = +build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_wireless_tracker -D HELTEC_TRACKER_V1_1 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output lib_deps = diff --git a/variants/esp32s3/heltec_wireless_tracker/variant.h b/variants/esp32s3/heltec_wireless_tracker/variant.h index b40e40011..e2049bcf6 100644 --- a/variants/esp32s3/heltec_wireless_tracker/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker/variant.h @@ -39,7 +39,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h index e7d3f93c1..16afbee81 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h @@ -35,7 +35,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h index 7c797a503..a3d52fd3c 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -34,7 +34,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. diff --git a/variants/esp32s3/heltec_wsl_v3/variant.h b/variants/esp32s3/heltec_wsl_v3/variant.h index c81f45d3b..26276d227 100644 --- a/variants/esp32s3/heltec_wsl_v3/variant.h +++ b/variants/esp32s3/heltec_wsl_v3/variant.h @@ -10,7 +10,7 @@ #define ADC_CTRL 37 #define ADC_CTRL_ENABLED LOW #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 * 1.045 diff --git a/variants/esp32s3/icarus/platformio.ini b/variants/esp32s3/icarus/platformio.ini index 0aaff52f5..de29cc2cf 100644 --- a/variants/esp32s3/icarus/platformio.ini +++ b/variants/esp32s3/icarus/platformio.ini @@ -7,12 +7,10 @@ board_build.mcu = esp32s3 board_build.partitions = default_8MB.csv upload_protocol = esptool upload_speed = 921600 -build_unflags = - ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D PRIVATE_HW -I variants/esp32s3/icarus -DBOARD_HAS_PSRAM + -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=0 diff --git a/variants/esp32s3/m5stack_cardputer_adv/variant.h b/variants/esp32s3/m5stack_cardputer_adv/variant.h index 48437cd13..7b25a0193 100644 --- a/variants/esp32s3/m5stack_cardputer_adv/variant.h +++ b/variants/esp32s3/m5stack_cardputer_adv/variant.h @@ -84,7 +84,7 @@ #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) #define BATTERY_PIN 10 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO10_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_9 #define ADC_MULTIPLIER 2 * 1.02 // 100k + 100k, and add 2% to kick the voltage over the max voltage to show charging. // BMI270 6-axis IMU on internal I2C bus diff --git a/variants/esp32s3/mesh-tab/variant.h b/variants/esp32s3/mesh-tab/variant.h index 30042b90f..5b450aa6c 100644 --- a/variants/esp32s3/mesh-tab/variant.h +++ b/variants/esp32s3/mesh-tab/variant.h @@ -10,7 +10,7 @@ #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider (100k, 220k) #define ADC_MULTIPLIER 1.6 // 1.45 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 // LED #define LED_POWER 21 diff --git a/variants/esp32s3/mini-epaper-s3/variant.h b/variants/esp32s3/mini-epaper-s3/variant.h index 0b640f9cf..1ac0a1e74 100644 --- a/variants/esp32s3/mini-epaper-s3/variant.h +++ b/variants/esp32s3/mini-epaper-s3/variant.h @@ -18,7 +18,7 @@ #define BATTERY_PIN 2 // A battery voltage measurement pin, voltage divider connected here to // measure battery voltage ratio of voltage divider = 2.0 (assumption) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO2_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_1 // Display (E-Ink) #define PIN_EINK_EN 42 diff --git a/variants/esp32s3/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h index 60afac002..10adff3b6 100644 --- a/variants/esp32s3/picomputer-s3/variant.h +++ b/variants/esp32s3/picomputer-s3/variant.h @@ -11,7 +11,7 @@ // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 3.0 (R11=200k, R7=100k) #define ADC_MULTIPLIER 3.1 // 3.0 with correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO2_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_1 #define USE_RF95 // RFM95/SX127x diff --git a/variants/esp32s3/rak3312/platformio.ini b/variants/esp32s3/rak3312/platformio.ini index 87e5b63ff..dcdb2974c 100644 --- a/variants/esp32s3/rak3312/platformio.ini +++ b/variants/esp32s3/rak3312/platformio.ini @@ -29,7 +29,7 @@ board_level = extra upload_protocol = esptool build_flags = - ${esp32_base.build_flags} + ${esp32s3_base.build_flags} -D RAK3312 -D _VARIANT_RAK3112_ -I variants/esp32s3/rak3312 diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index ee0fff524..381506866 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -30,7 +30,7 @@ #define LED_STATE_ON 1 // State when LED is litted #define BATTERY_PIN 1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #ifdef _VARIANT_RAK3112_ // Modular variant (stamp) #define ADC_MULTIPLIER 2.11 @@ -55,4 +55,4 @@ #define GPS_TX_PIN 43 #define GPS_RX_PIN 44 -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index 90cb12053..f8672edac 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -51,7 +51,7 @@ #define USE_VIRTUAL_KEYBOARD 1 #define BATTERY_PIN 1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_MULTIPLIER 1.667 #define PIN_BUZZER 38 diff --git a/variants/esp32s3/seeed-sensecap-indicator/platformio.ini b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini index bb52f801b..9b01033df 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/platformio.ini +++ b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini @@ -13,7 +13,8 @@ custom_meshtastic_has_mui = true extends = esp32s3_base platform_packages = - platformio/framework-arduinoespressif32 @ https://github.com/mverch67/arduino-esp32/archive/aef7fef6de3329ed6f75512d46d63bba12b09bb5.zip ; add_tca9535 (based on 2.0.16) + ; Version needs to match the pioarduino version used in esp32_common.ini + platformio/framework-arduinoespressif32 @ https://github.com/mverch67/arduino-esp32#82aee17619ea2940de03b0db4d082d5def657771 board = seeed-sensecap-indicator board_check = true @@ -23,7 +24,7 @@ upload_protocol = esptool build_flags = ${esp32s3_base.build_flags} -Ivariants/esp32s3/seeed-sensecap-indicator -DSENSECAP_INDICATOR - -DCONFIG_ARDUHAL_LOG_COLORS + -DUSE_ARDUINO_HAL_GPIO ; patch for lovyanGFX to use digitalWrite instead of direct register access for GPIO control, which is necessary for the IO expander to work correctly -DRADIOLIB_DEBUG_SPI=0 -DRADIOLIB_DEBUG_PROTOCOL=0 -DRADIOLIB_DEBUG_BASIC=0 @@ -32,19 +33,48 @@ build_flags = ${esp32s3_base.build_flags} -DIO_EXPANDER=0x40 -DIO_EXPANDER_IRQ=42 ;-DIO_EXPANDER_DEBUG - -DUSE_ARDUINO_HAL_GPIO lib_deps = ${esp32s3_base.lib_deps} - ; TODO switch back to official LovyanGFX - https://github.com/mverch67/LovyanGFX/archive/4c76238c1344162a234ae917b27651af146d6fb2.zip + https://github.com/mverch67/LovyanGFX/archive/9abe502add013f392a1898d7dc48d65ddc112754.zip # renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM earlephilhower/ESP8266SAM@1.1.0 +custom_sdkconfig = + ${esp32s3_base.custom_sdkconfig} + CONFIG_AUTOSTART_ARDUINO=y + CONFIG_LOG_DEFAULT_LEVEL_INFO=y + CONFIG_LOG_MAXIMUM_LEVEL_DEBUG=y + CONFIG_SPIRAM_MODE_OCT=y +; CONFIG_SPIRAM_SPEED_120M=y +; CONFIG_SPIRAM_SPEED=120 + CONFIG_SPIRAM_SPEED_80M=y + CONFIG_SPIRAM_SPEED=80 + CONFIG_SPIRAM_XIP_FROM_PSRAM=y + CONFIG_LCD_RGB_ISR_IRAM_SAFE=y + CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y + CONFIG_I2S_ISR_IRAM_SAFE=y + CONFIG_GDMA_ISR_IRAM_SAFE=y + CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y + CONFIG_ESP32S3_DATA_CACHE_64KB=y + CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y + CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK=y + CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=0 + CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y + CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +; CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +; CONFIG_ESPTOOLPY_FLASHFREQ="120m" + '# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set' + CONFIG_ESPTOOLPY_FLASHFREQ_80M=y + CONFIG_ESPTOOLPY_FLASHFREQ="80m" + CONFIG_COMPILER_OPTIMIZATION_PERF=y + CONFIG_BOOTLOADER_LOG_LEVEL_NONE=n + CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y + [env:seeed-sensecap-indicator-tft] extends = env:seeed-sensecap-indicator -board_level = pr +board_check = true upload_speed = 460800 build_flags = @@ -54,10 +84,10 @@ build_flags = -D HAS_SCREEN=1 -D HAS_TFT=1 -D DISPLAY_SET_RESOLUTION - -D RAM_SIZE=4096 - -D LV_LVGL_H_INCLUDE_SIMPLE - -D LV_CONF_INCLUDE_SIMPLE - -D LV_COMP_CONF_INCLUDE_SIMPLE + -D RAM_SIZE=3072 + -D LV_LVGL_H_INCLUDE_SIMPLE + -D LV_CONF_INCLUDE_SIMPLE + -D LV_COMP_CONF_INCLUDE_SIMPLE -D LV_USE_SYSMON=0 -D LV_USE_PROFILER=0 -D LV_USE_PERF_MONITOR=0 @@ -77,5 +107,6 @@ build_flags = lib_deps = ${env:seeed-sensecap-indicator.lib_deps} ${device-ui_base.lib_deps} - ; TODO switch back to official bb_captouch + ;# renovate: datasource=github-tags depName=bb_captouch packageName=bitbank2/bb_captouch + ;https://github.com/bitbank2/bb_captouch/archive/refs/tags/1.3.1.zip ; not working https://github.com/mverch67/bb_captouch/archive/8626412fe650d808a267791c0eae6e5860c85a5d.zip ; alternative touch library supporting FT6x36 diff --git a/variants/esp32s3/seeed-sensecap-indicator/variant.h b/variants/esp32s3/seeed-sensecap-indicator/variant.h index 8fa9e2393..db0054348 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/variant.h +++ b/variants/esp32s3/seeed-sensecap-indicator/variant.h @@ -4,7 +4,7 @@ // This board has a serial coprocessor for sensor readings #define SENSOR_RP2040_TXD 19 #define SENSOR_RP2040_RXD 20 -#define SENSOR_PORT_NUM 2 +#define SENSOR_PORT_NUM UART_NUM_2 #define SENSOR_BAUD_RATE 115200 #define BUTTON_PIN 38 @@ -14,7 +14,7 @@ // #define BUTTON_NEED_PULLUP // #define BATTERY_PIN 27 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -// #define ADC_CHANNEL ADC1_GPIO27_CHANNEL +// #define ADC_CHANNEL ADC2_GPIO27_CHANNEL // #define ADC_MULTIPLIER 2 // ST7701 TFT LCD diff --git a/variants/esp32s3/seeed_xiao_s3/platformio.ini b/variants/esp32s3/seeed_xiao_s3/platformio.ini index b0e66241b..ee457eaf0 100644 --- a/variants/esp32s3/seeed_xiao_s3/platformio.ini +++ b/variants/esp32s3/seeed_xiao_s3/platformio.ini @@ -17,12 +17,8 @@ board_check = true board_build.partitions = default_8MB.csv upload_protocol = esptool upload_speed = 921600 -build_unflags = - ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 build_flags = ${esp32s3_base.build_flags} -D SEEED_XIAO_S3 -I variants/esp32s3/seeed_xiao_s3 -DBOARD_HAS_PSRAM - -DARDUINO_USB_MODE=0 diff --git a/variants/esp32s3/seeed_xiao_s3/variant.h b/variants/esp32s3/seeed_xiao_s3/variant.h index cbdbf8eb8..7eee372fa 100644 --- a/variants/esp32s3/seeed_xiao_s3/variant.h +++ b/variants/esp32s3/seeed_xiao_s3/variant.h @@ -37,7 +37,7 @@ L76K GPS Module Information : https://www.seeedstudio.com/L76K-GNSS-Module-for-S #define BUTTON_NEED_PULLUP #define BATTERY_PIN -1 -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define BATTERY_SENSE_RESOLUTION_BITS 12 /*Warning: diff --git a/variants/esp32s3/station-g2/variant.h b/variants/esp32s3/station-g2/variant.h index a343823c7..9e844588f 100755 --- a/variants/esp32s3/station-g2/variant.h +++ b/variants/esp32s3/station-g2/variant.h @@ -20,7 +20,7 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2 /* #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 #define ADC_MULTIPLIER 4 #define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity. #define BAT_FULLVOLT 8400 diff --git a/variants/esp32s3/t-beam-1w/variant.h b/variants/esp32s3/t-beam-1w/variant.h index 52e99320e..673b7c4c0 100644 --- a/variants/esp32s3/t-beam-1w/variant.h +++ b/variants/esp32s3/t-beam-1w/variant.h @@ -72,7 +72,7 @@ // Battery ADC #define BATTERY_PIN 4 -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 #define BATTERY_SENSE_SAMPLES 30 #define ADC_MULTIPLIER 2.9333 diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index eb1bbdfef..1e6d7b97d 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -53,7 +53,7 @@ #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (RD2=100k, RD3=100k) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_3 // keyboard #define I2C_SDA 18 // I2C pins for this board diff --git a/variants/esp32s3/t-eth-elite/platformio.ini b/variants/esp32s3/t-eth-elite/platformio.ini index 5ed67e977..5b1f01db7 100644 --- a/variants/esp32s3/t-eth-elite/platformio.ini +++ b/variants/esp32s3/t-eth-elite/platformio.ini @@ -10,10 +10,15 @@ build_flags = -D HAS_UDP_MULTICAST=1 -I variants/esp32s3/t-eth-elite -lib_ignore = - Ethernet +custom_sdkconfig = + ${esp32s3_base.custom_sdkconfig} + CONFIG_ETH_ENABLED=y + CONFIG_ARDUINO_SELECTIVE_Ethernet=y -lib_deps = - ${esp32s3_base.lib_deps} - # renovate: datasource=github-tags depName=ETHClass2 packageName=meshtastic/ETHClass2 - https://github.com/meshtastic/ETHClass2/archive/v1.0.0.zip +; lib_ignore = +; Ethernet + +; lib_deps = +; ${esp32s3_base.lib_deps} +; # renovate: datasource=github-tags depName=ETHClass2 packageName=meshtastic/ETHClass2 +; https://github.com/meshtastic/ETHClass2/archive/v1.0.0.zip diff --git a/variants/esp32s3/t5s3_epaper/variant.h b/variants/esp32s3/t5s3_epaper/variant.h index 49579a39c..fdf8589da 100644 --- a/variants/esp32s3/t5s3_epaper/variant.h +++ b/variants/esp32s3/t5s3_epaper/variant.h @@ -1,3 +1,4 @@ +#include "pins_arduino.h" // Display (E-Ink) ED047TC1 - 8bit parallel #define EPD_WIDTH 960 diff --git a/variants/esp32s3/tlora_t3s3_epaper/variant.h b/variants/esp32s3/tlora_t3s3_epaper/variant.h index 0f4875fc4..77dccb616 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/variant.h +++ b/variants/esp32s3/tlora_t3s3_epaper/variant.h @@ -12,7 +12,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to // measure battery voltage ratio of voltage divider = 2.0 (assumption) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define I2C_SDA SDA #define I2C_SCL SCL diff --git a/variants/esp32s3/tlora_t3s3_v1/variant.h b/variants/esp32s3/tlora_t3s3_v1/variant.h index 02e2a0e42..7c6df9be9 100644 --- a/variants/esp32s3/tlora_t3s3_v1/variant.h +++ b/variants/esp32s3/tlora_t3s3_v1/variant.h @@ -6,7 +6,7 @@ #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (R42=100k, R43=100k) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define I2C_SDA 18 // I2C pins for this board #define I2C_SCL 17 diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index b2822c24b..c015be280 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -35,7 +35,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index 6c32ff279..38df4d3ab 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -59,7 +59,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h index 72762c7af..f85e845f3 100644 --- a/variants/esp32s3/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -36,7 +36,7 @@ #define BUTTON_PIN 0 #define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_CHANNEL ADC_CHANNEL_0 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider #define ADC_MULTIPLIER 4.9 #define ADC_CTRL 2 // active HIGH, powers the voltage divider. Only on 1.1 diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 56838b1fc..8c551cf41 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -21,7 +21,6 @@ build_flags = ${esp32s3_base.build_flags} -D UNPHONE -I variants/esp32s3/unphone - -D ARDUINO_USB_MODE=0 -D UNPHONE_ACCEL=0 -D UNPHONE_TOUCHS=0 -D UNPHONE_SDCARD=0 diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 19568dc67..aa2f5bb7c 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -9,8 +9,9 @@ build_src_filter = ${env.build_src_filter} - - - - - - + - + - + - - - - @@ -29,7 +30,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.21 ; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip + https://github.com/pine64/libch341-spi-userspace/archive/2e5ff751d0c39667993df672cb683740ed5c9394.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index 1af319ff0..01dd6352c 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -40,7 +40,7 @@ build_unflags = -std=gnu++11 build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - lib_deps= ${arduino_base.lib_deps} diff --git a/variants/nrf52840/t-echo-card/platformio.ini b/variants/nrf52840/t-echo-card/platformio.ini new file mode 100644 index 000000000..bc012d6e1 --- /dev/null +++ b/variants/nrf52840/t-echo-card/platformio.ini @@ -0,0 +1,12 @@ +[env:t-echo-card] +extends = nrf52840_base +board = t-echo +board_level = extra +debug_tool = jlink + +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/t-echo-card + -D PRIVATE_HW + -D T_ECHO_CARD + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo-card> diff --git a/variants/nrf52840/t-echo-card/variant.cpp b/variants/nrf52840/t-echo-card/variant.cpp new file mode 100644 index 000000000..e82a63f8e --- /dev/null +++ b/variants/nrf52840/t-echo-card/variant.cpp @@ -0,0 +1,66 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "Arduino.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // No plain GPIO LEDs on this board (only WS2812 addressable LEDs, not driven here). +} + +// Reproduces the vendor firmware's boot sequence from +// examples/original_test/original_test.ino. Runs before Meshtastic touches +// PIN_POWER_EN, so the RT9080 LDO gets a clean reset pulse and peripherals +// whose EN pins must be LOW at boot (GPS_EN, GPS_RF_EN, BUZZER) aren't left +// floating while the 3V3 rail is ramping. +void earlyInitVariant() +{ + // 3.3V rail: toggle RT9080_EN HIGH → LOW → HIGH with 100 ms dwell so the + // LDO enters enable from a known state. The single-shot HIGH in main.cpp + // is not enough on this hardware — if the chip was in a half-enabled + // state from a previous reset, the rail brown-outs once LoRa TX fires. + pinMode(PIN_POWER_EN, OUTPUT); + digitalWrite(PIN_POWER_EN, HIGH); + delay(100); + digitalWrite(PIN_POWER_EN, LOW); + delay(100); + digitalWrite(PIN_POWER_EN, HIGH); + delay(100); + + // Park peripherals with active-high enables LOW so they don't sink + // current while the rest of setup() runs. + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, LOW); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, LOW); + pinMode(PIN_BUZZER, OUTPUT); + digitalWrite(PIN_BUZZER, LOW); +} diff --git a/variants/nrf52840/t-echo-card/variant.h b/variants/nrf52840/t-echo-card/variant.h new file mode 100644 index 000000000..37fa28d89 --- /dev/null +++ b/variants/nrf52840/t-echo-card/variant.h @@ -0,0 +1,202 @@ +// Variant definition for LilyGo T-Echo-Card (nRF52840) + +#ifndef _VARIANT_T_ECHO_CARD_ +#define _VARIANT_T_ECHO_CARD_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32kHz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs - board only exposes 3x WS2812 addressable LEDs. No plain GPIO LEDs. +// Intentionally do not define PIN_LED1 on this variant, so nRF52 platform +// code does not auto-enable a nonexistent GPIO power/status LED. +#define LED_STATE_ON 1 + +// Three independent WS2812 data lines (one LED per line, not a daisy chain). +// Each is driven as a 1-pixel NeoPixel by StatusLEDModule / ExternalNotification, +// assigns LED_POWER (red) and LED_NOTIFICATION (green). +#define WS2812_DATA_1 (32 + 7) // P1.7 - charge/heartbeat (red) +#define WS2812_DATA_2 (32 + 12) // P1.12 - external notification (green) +#define WS2812_DATA_3 (0 + 28) // P0.28 - BLE pairing (blue) + +// Wire each WS2812 to a status role. Colour defaults are scaled to 25% +// brightness (0x40) — the bare-die WS2812s on this board are very bright at +// full intensity in a close-range enclosure. +#define NEOPIXEL_STATUS_POWER_PIN WS2812_DATA_1 +#define NEOPIXEL_STATUS_NOTIFICATION_PIN WS2812_DATA_2 +#define NEOPIXEL_STATUS_PAIRING_PIN WS2812_DATA_3 +#define NEOPIXEL_STATUS_POWER_COLOR 0x400000 // red @ 25% +#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x004000 // green @ 25% +#define NEOPIXEL_STATUS_PAIRING_COLOR 0x000040 // blue @ 25% + +// The charger IC does not blink on its own; let StatusLEDModule do the +// software blink while charging +// If left defined: hardware would be expected to handle the charging pulse. +// #define POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING + +// Buttons +#define PIN_BUTTON1 (32 + 10) // KEY_1: P1.10 + +#define BUTTON_CLICK_MS 400 + +// Analog pins +#define PIN_A0 (0 + 2) // Battery ADC (BATTERY_ADC_DATA) + +#define BATTERY_PIN PIN_A0 + +static const uint8_t A0 = PIN_A0; + +#define ADC_RESOLUTION 14 + +// BATTERY_MEASUREMENT_CONTROL - enable divider for battery reading +#define ADC_CTRL (0 + 31) +#define ADC_CTRL_ENABLED HIGH + +// NFC placeholders, not used +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +// Wire Interfaces (IIC_1 on the vendor header) +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) // IIC_1_SDA: P1.4 +#define PIN_WIRE_SCL (32 + 2) // IIC_1_SCL: P1.2 + +// External serial flash ZD25WQ32CEIGR +// QSPI Pins +#define PIN_QSPI_SCK (0 + 4) +#define PIN_QSPI_CS (0 + 12) +#define PIN_QSPI_IO0 (0 + 6) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (0 + 8) // MISO if using two bit interface +#define PIN_QSPI_IO2 (32 + 9) // WP +#define PIN_QSPI_IO3 (0 + 26) // HOLD + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR +#define EXTERNAL_FLASH_USE_QSPI + +// Lora S62F (SX1262) +#define USE_SX1262 +#define SX126X_CS (0 + 11) +#define SX126X_DIO1 (32 + 8) +#define SX126X_DIO2 (0 + 5) +#define SX126X_BUSY (0 + 14) +#define SX126X_RESET (0 + 7) +#define SX126X_RXEN (32 + 1) // SX1262_RF_VC2 +#define SX126X_TXEN (0 + 27) // SX1262_RF_VC1 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// ─────────────────────────────────────────────────────────────────────────── +// OLED display: SSD1315 on I2C @ 0x3C (IIC_1). SSD1315 is register-compatible +// with SSD1306, so USE_SSD1306 initializes the controller correctly. +// +// Viewport: the physical panel is 72×40, mapped into the SSD1315's 128×64 +// GDDRAM at columns 28..99, pages 3..7 (rows 24..63). The firmware handles +// this by: +// * asking the library for GEOMETRY_72_40, which sets the framebuffer to +// 72×40 and emits the right SETMULTIPLEX (39) / SETCOMPINS at init; +// * relying on SSD1306Wire's built-in horizontal auto-centering +// ((128 - width) / 2 = 28), so no horizontal shim is needed; +// * calling SSD1306Wire::setYOffset(3) in Screen.cpp when +// OLED_Y_OFFSET_PAGES is defined — this shifts every PAGEADDR write by +// three pages (24 rows) so data lands on the visible rows. +// ─────────────────────────────────────────────────────────────────────────── +#define HAS_SCREEN 1 +#define USE_SSD1306 +#define OLED_GEOMETRY_OVERRIDE GEOMETRY_72_40 +#define OLED_Y_OFFSET_PAGES 3 +#define OLED_TINY + +// Controls power 3V3 for all peripherals (GPS + LoRa + Sensor) +#define PIN_POWER_EN (0 + 30) // RT9080_EN + +// SPI1 is unused (no external SPI display). Keep declarations for the core. +#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MOSI (-1) +#define PIN_SPI1_SCK (-1) + +// GPS pins +#define GPS_L76K +#define GPS_BAUDRATE 9600 +#define HAS_GPS 1 + +#define PIN_GPS_EN (32 + 15) // GPS_EN: P1.15 - GPS power enable +#define GPS_EN_ACTIVE 1 +#define PIN_GPS_STANDBY (0 + 25) // GPS_WAKE_UP: P0.25 - wakeup pin +#define PIN_GPS_PPS (0 + 23) // GPS_1PPS: P0.23 +#define GPS_RX_PIN (0 + 19) // MCU RX ← GPS's TX (vendor GPS_UART_TX / P0.19) +#define GPS_TX_PIN (0 + 21) // MCU TX → GPS's RX (vendor GPS_UART_RX / P0.21) +#define PIN_GPS_RESET (0 + 29) // GPS_RF_EN: GPS RF enable / reset + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_RX_PIN +#define PIN_SERIAL1_TX GPS_TX_PIN + +// SPI Interfaces (LoRa on SPI0) +#define SPI_INTERFACES_COUNT 2 + +// For LORA, SPI 0 +#define PIN_SPI_MISO (0 + 17) +#define PIN_SPI_MOSI (0 + 15) +#define PIN_SPI_SCK (0 + 13) + +// Battery +// The battery sense is hooked to PIN_A0 (P0.2) via a divider controlled by ADC_CTRL. +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (2.0F) + +// Buzzer (PWM output, passive piezo) +#define PIN_BUZZER (32 + 6) // BUZZER_DATA: P1.6 + +// ─────────────────────────────────────────────────────────────────────────── +// I²S speaker (MAX98357 Class-D amp). Stereo I²S data path. +// Not supported on nrf52. These defines exist for out-of-tree code only. +// ─────────────────────────────────────────────────────────────────────────── +#define SPEAKER_EN (32 + 11) // P1.11 - amp main enable +#define SPEAKER_EN_2 (0 + 3) // P0.3 - secondary enable (vendor firmware toggles both) +#define SPEAKER_BCLK (0 + 16) // P0.16 - I2S bit clock +#define SPEAKER_DATA (0 + 20) // P0.20 - I2S data (SDOUT) +#define SPEAKER_WS_LRCK (0 + 22) // P0.22 - I2S word select / LRCK + +// ─────────────────────────────────────────────────────────────────────────── +// PDM microphone (ST MP34DT05). +// TODO to enable a mic path: +// Use Adafruit nRF52 core's built-in PDM.h wrapper (Arduino-compatible +// API exists on nRF52840). Clock on MIC_SCLK, data on MIC_DATA. +// ─────────────────────────────────────────────────────────────────────────── +#define MIC_SCLK (32 + 3) // P1.3 - PDM clock (MIC_SCLK on vendor header) +#define MIC_DATA (32 + 5) // P1.5 - PDM data (MIC_DATA on vendor header) + +#define SERIAL_PRINT_PORT 0 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/nrf54l15/nrf54l15.ini b/variants/nrf54l15/nrf54l15.ini new file mode 100644 index 000000000..4e6fa3f01 --- /dev/null +++ b/variants/nrf54l15/nrf54l15.ini @@ -0,0 +1,74 @@ +[nrf54l15_base] +platform = https://github.com/Seeed-Studio/platform-seeedboards.git +framework = zephyr +extends = arduino_base + +build_type = release +build_flags = + ${arduino_base.build_flags} + -Isrc/platform/nrf54l15 + -DMESHTASTIC_EXCLUDE_AUDIO=1 + -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_MQTT=1 + -DHAS_WIRE=1 + -DHAS_SENSOR=1 + -DHAS_BUTTON=0 + -DHAS_TELEMETRY=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DARDUINO=100 + -DMESHTASTIC_EXCLUDE_ACCELEROMETER=1 + -DMAX_NUM_NODES=40 + -fpermissive + # Libraries that Zephyr LDF misses; add include paths explicitly + -I.pio/libdeps/${PIOENV}/Crypto + -I.pio/libdeps/${PIOENV}/ArduinoThread + -I".pio/libdeps/${PIOENV}/ESP8266 and ESP32 OLED driver for SSD1306 displays/src" + -I.pio/libdeps/${PIOENV}/OneButton/src + -I.pio/libdeps/${PIOENV}/arduino-fsm + -I.pio/libdeps/${PIOENV}/TinyGPSPlus/src + -I.pio/libdeps/${PIOENV}/ErriezCRC32/src + -I.pio/libdeps/${PIOENV}/NonBlockingRTTTL/src + -I.pio/libdeps/${PIOENV}/RadioLib/src + +build_src_filter = + ${arduino_base.build_src_filter} + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + +lib_compat_mode = off + +lib_deps = + ${arduino_base.lib_deps} + ${radiolib_base.lib_deps} + rweather/Crypto@0.4.0 + ; Cherry-picked sensor libs from environmental_base. The full + ; environmental_base pulls Adafruit_SSD1306 / GFX which need Arduino + ; pin macros (digitalPinToPort / portOutputRegister) that the Zephyr + ; Arduino shim does not implement. + https://github.com/adafruit/Adafruit_BusIO/archive/refs/tags/1.17.4.zip + https://github.com/adafruit/Adafruit_Sensor/archive/refs/tags/1.1.15.zip + https://github.com/adafruit/Adafruit_BMP280_Library/archive/refs/tags/3.0.0.zip + https://github.com/adafruit/Adafruit_BME280_Library/archive/refs/tags/2.3.0.zip + https://github.com/adafruit/Adafruit_INA260/archive/refs/tags/1.5.3.zip + https://github.com/adafruit/Adafruit_INA219/archive/refs/tags/1.2.3.zip + https://github.com/RobTillaart/INA3221_RT/archive/refs/tags/0.4.2.zip + https://github.com/RobTillaart/INA226/archive/refs/tags/0.6.6.zip + https://github.com/adafruit/Adafruit_SHT4X/archive/refs/tags/1.0.5.zip + +lib_ignore = + BluetoothOTA + lvgl + Adafruit_nRFCrypto diff --git a/variants/nrf54l15/nrf54l15dk/README.md b/variants/nrf54l15/nrf54l15dk/README.md new file mode 100644 index 000000000..bede3a04a --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/README.md @@ -0,0 +1,108 @@ +# nRF54L15-DK — EBYTE E22-900M30S Wiring Guide + +Board: **Nordic nRF54L15-DK (PCA10156)** +Radio: **EBYTE E22-900M30S** (SX1262, 30 dBm, 868/915 MHz) + +--- + +## Why P2 (HP domain) and not P1 + +The nRF54L15 splits its GPIOs across three supply domains: + +- **P0** — Main domain, **3.0 V** — usable +- **P1** — LP domain, **1.8 V** — **not compatible** with the SX1262 +- **P2** — HP domain, **3.0 V** — usable + +The SX1262 requires VIH ≥ 0.7 × VDD (≈ 2.31 V at VDD = 3.3 V). P1's 1.8 V output +leaves the chip stuck in reset with `BUSY` never going LOW. All E22 signals +therefore live on **P2** and are driven by **SPIM00**. + +> `P2.09` is normally wired to LED0 on the DK; we ignore the LED and use +> SPIM00's default MISO pin. The on-board MX25R64 NOR flash also sat on SPIM00 +> — it is deleted in the device-tree overlay to free the bus. + +--- + +## Connections — J2 header, P2 bank + +| E22-900M30S | GPIO | DK pin | Function | +| ----------- | ----- | ------ | ---------------------------------------------- | +| MISO | P2.04 | 36 | SPIM00 data in | +| NSS / CS | P2.05 | 37 | SPI chip-select (driven by RadioLib as a GPIO) | +| DIO1 | P2.06 | 38 | IRQ — modem interrupt (routed via gpiote30) | +| BUSY | P2.03 | 35 | Module busy (GPIO input) | +| NRESET | P2.00 | 32 | Module reset (GPIO output, active LOW) | +| RXEN | P2.07 | 39 | LNA enable — held HIGH via `SX126X_ANT_SW` | +| MOSI | P2.02 | 34 | SPIM00 data out | +| SCK | P2.01 | 33 | SPIM00 clock | +| GND | — | GND | Common ground | +| VCC | — | VDD | 3.3 V | + +> **Numbering convention**: `P0.n = n`, `P1.n = 16+n`, `P2.n = 32+n`. +> Example: `P2.04` → 32 + 4 = **36**. + +--- + +## DIO2 → TXEN bridge (required) + +The E22-900M30S does **not** connect DIO2 to TXEN internally. A physical bridge on the module is required: + +1. Locate the `DIO2` and `TXEN` pads on the underside of the E22 module. +2. Solder a wire bridge or a 0 Ω resistor between the two pads. +3. With this bridge, the SX1262 drives the PA automatically via `SX126X_DIO2_AS_RF_SWITCH`. + +Without this bridge the module **will not transmit** (PA is never enabled). + +--- + +## RXEN — LNA always on + +`RXEN` (P2.07) is held HIGH permanently via `SX126X_ANT_SW 39` in `variant.h`. +**Do not use** `SX126X_RXEN` — RadioLib would drive it LOW in IDLE state and +the LNA would stay disabled (radio deaf in RX). + +--- + +## Reserved DK pins — do not reuse + +| Pins | Reserved function | +| ----------- | -------------------------------------------------------- | +| P0.00–P0.03 | IMCU debug UART (uart30, J-Link VCOM — used by RTT host) | +| P0.04 | BTN3 | +| P1.00–P1.01 | 32 kHz crystal | +| P1.02–P1.03 | NFC antenna | +| P1.10 | LED1 (status LED — kept) | +| P1.13 | BTN0 (only remaining user button) | +| P1.14 | LED3 | +| P2.01–P2.05 | SPIM00 / E22 (see connection table above) | +| P2.08–P2.10 | Trace ETM / LED2 (avoid) | + +--- + +## Build and flash + +```bash +# Build +pio run -e nrf54l15dk + +# Flash (requires a J-Link connected via the DK's on-board IMCU) +pio run -e nrf54l15dk -t upload + +# Monitor RTT (channel 1 = Meshtastic logs) +JLinkRTTLogger -device nRF54L15_xxAA -if SWD -speed 4000 -RTTChannel 1 boot.log +``` + +Expected boot log: + +```text +*** Booting Zephyr OS build zephyr-v40201 *** +[nrf54l15] Reset cause: ... +[nrf54l15] B: calling setup() +INFO | ... SX1262 +INFO | ... lora.begin() = 0 ← RADIOLIB_ERR_NONE +[nrf54l15] C: setup() returned +``` + +If you see `Record critical error 3` (`NO_RADIO`), check: DIO2→TXEN bridge, +supply voltages (the E22 must see 3.0–3.3 V on P2, not 1.8 V), and SPI wiring +continuity. diff --git a/variants/nrf54l15/nrf54l15dk/platformio.ini b/variants/nrf54l15/nrf54l15dk/platformio.ini new file mode 100644 index 000000000..71bbe252b --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/platformio.ini @@ -0,0 +1,24 @@ +[env:nrf54l15dk] +# PRIVATE_HW (255) — the protobuf HardwareModel enum reserves DK / DIY boards +# without an SKU under PRIVATE_HW; no dedicated enum value will be assigned. +custom_meshtastic_hw_model = 255 +custom_meshtastic_hw_model_slug = NRF54L15_DK +custom_meshtastic_architecture = nrf54l15 +custom_meshtastic_actively_supported = false +custom_meshtastic_support_level = 3 +custom_meshtastic_display_name = Nordic nRF54L15-DK + +extends = nrf54l15_base +board = nrf54l15dk +board_level = extra +debug_tool = jlink +upload_protocol = jlink +board_runner_args_jlink = --device nRF54L15_xxAA --speed 4000 + +build_flags = ${nrf54l15_base.build_flags} + -Ivariants/nrf54l15/nrf54l15dk + -DNRF54L15_DK + -DMESHTASTIC_EXCLUDE_FILES_MANIFEST=1 + +build_src_filter = ${nrf54l15_base.build_src_filter} + +<../variants/nrf54l15/nrf54l15dk> diff --git a/variants/nrf54l15/nrf54l15dk/variant.cpp b/variants/nrf54l15/nrf54l15dk/variant.cpp new file mode 100644 index 000000000..b2982e7c5 --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/variant.cpp @@ -0,0 +1,9 @@ +#include "variant.h" + +void initVariant() +{ + // Minimal board init for nRF54L15-DK. + // GPIO/SPI peripheral setup is handled by the Zephyr device tree overlay + // (zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay). + // Add any board-level power sequencing here if needed. +} diff --git a/variants/nrf54l15/nrf54l15dk/variant.h b/variants/nrf54l15/nrf54l15dk/variant.h new file mode 100644 index 000000000..b837ed7cf --- /dev/null +++ b/variants/nrf54l15/nrf54l15dk/variant.h @@ -0,0 +1,86 @@ +#pragma once + +/* + * Nordic nRF54L15-DK (PCA10156) — Meshtastic variant + * + * ── GPIO voltage domains ───────────────────────────────────────────────────── + * P0 (gpio0 @ 0x10A000) Main domain 3.0 V ← usable + * P1 (gpio1 @ 0xd8200 ) LP domain 1.8 V ← NOT compatible with E22 + * P2 (gpio2 @ 0x50400 ) HP domain 3.0 V ← usable + * + * The SX1262 needs VIH ≥ 0.7 × VDD = 2.31 V (VDD = 3.3 V). + * P1 outputs only 1.8 V → chip stays in reset, BUSY never goes LOW. + * All E22 signals are therefore on P2 (3.0 V), driven by SPIM00. + * + * EBYTE E22-900M30S (SX1262) wiring — J2 header, all P2: + * + * E22 pin GPIO pin# Notes + * ───────────────────────────────────────────────────────────────────── + * MISO → P2.04 36 SPIM00 data in + * NSS/CS → P2.05 37 SPI chip-select (RadioLib GPIO) + * DIO1 → P2.06 38 IRQ — interrupt via gpiote30 + * BUSY → P2.03 35 GPIO input + * NRESET → P2.00 32 GPIO output + * RXEN → P2.07 39 Held HIGH via ANT_SW (LNA always active) + * MOSI → P2.02 34 SPIM00 data out + * SCK → P2.01 33 SPIM00 clock + * + * DIO2 → TXEN bridge required on E22 module (solder bridge / wire). + * DIO3 drives TCXO reference (1.8 V). + * + * Pin numbering convention: P0.n = n, P1.n = 16+n, P2.n = 32+n. + * + * Reserved / do-not-use DK pins: + * P0.00-P0.02 IMCU VCOM TX/RX/RTS pads (uart30 disabled; pads idle) + * P0.03 I2C SDA (TWIM30) — sensor bus + * P0.04 I2C SCL (TWIM30) — sensor bus; SW3 button on this pad, + * DO NOT press SW3 while I2C is active + * P1.00-P1.01 32 kHz crystal + * P1.02-P1.03 NFC antenna + * P1.10 LED1 (status LED — keep) + * P1.13 BTN0 — main user button + * P1.14 LED3 + * P2.01-P2.05 SPIM00 / E22 (see above) + * P2.08-P2.10 Trace pins (avoid) + */ + +#ifndef NRF54L15_DK +#define NRF54L15_DK +#endif + +// ── SX1262 / E22-900M30S — all P2, HP domain (3.0 V) ──────────────────────── +#define USE_SX1262 +#define SX126X_CS 37 // P2.05 — chip-select +#define SX126X_DIO1 38 // P2.06 — IRQ (gpiote30 capable) +#define SX126X_BUSY 35 // P2.03 — BUSY +#define SX126X_RESET 32 // P2.00 — NRESET + +// RXEN (P2.07) held HIGH permanently — LNA always active. +// RadioLib must NOT toggle it; ANT_SW drives it HIGH before lora.begin(). +#define SX126X_ANT_SW 39 // P2.07 — RXEN driven HIGH at init + +// DIO2 controls TXEN via bridge on E22 module. +// DIO3 provides 1.8 V TCXO reference. +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8f + +// ── LEDs (active HIGH) ─────────────────────────────────────────────────────── +#define PIN_LED1 26 // P1.10 — LED1 (status LED, LP domain — output only, OK) +#define PIN_LED2 41 // P2.09 — LED0 on DK (remapped; P2.07 now used for RXEN) +#define LED_STATE_ON 1 + +// ── Buttons (active LOW, internal pull-up) ─────────────────────────────────── +// BTN1 (P1.09), BTN2 (P1.08) and BTN3 (P0.04) deleted from DTS — only BTN0 +// remains. BTN3's pad (P0.04) is now I2C SCL. +#define PIN_BUTTON1 29 // P1.13 — BTN0 +#define BUTTON_NEED_PULLUP + +// ── I2C bus (TWIM30, HP domain, 3.0 V) ────────────────────────────────────── +// SDA=P0.03, SCL=P0.04. Pinctrl + clock-frequency live in the board overlay. +// External 4.7 kΩ pull-ups required on both lines. Meshtastic's Arduino +// TwoWire layer (src/platform/nrf54l15/Wire.cpp) resolves the device at +// compile time via DT_NODELABEL(i2c30); these PIN_WIRE_* defines are kept +// for parity with the Arduino convention used by other variants. +#define PIN_WIRE_SDA 3 // P0.03 +#define PIN_WIRE_SCL 4 // P0.04 +#define WIRE_INTERFACES_COUNT 1 diff --git a/variants/rp2040/rp2040.ini b/variants/rp2040/rp2040.ini index 346d6eaca..2b7002397 100644 --- a/variants/rp2040/rp2040.ini +++ b/variants/rp2040/rp2040.ini @@ -20,7 +20,7 @@ build_flags = -D__FREERTOS=1 # -D _POSIX_THREADS build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - lib_ignore = BluetoothOTA diff --git a/variants/rp2350/rp2350.ini b/variants/rp2350/rp2350.ini index 7e2da4fa7..2931fd7b6 100644 --- a/variants/rp2350/rp2350.ini +++ b/variants/rp2350/rp2350.ini @@ -16,8 +16,8 @@ build_flags = -Isrc/platform/rp2xx0 -D__PLAT_RP2350__ -D__FREERTOS=1 -build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - +build_src_filter = + ${arduino_base.build_src_filter} - - - - - - - - - - - - lib_ignore = BluetoothOTA diff --git a/variants/stm32/stm32.ini b/variants/stm32/stm32.ini index 94f97b837..da90b83e5 100644 --- a/variants/stm32/stm32.ini +++ b/variants/stm32/stm32.ini @@ -44,7 +44,7 @@ build_flags = -Wl,--wrap=_tzset_unlocked_r build_src_filter = - ${arduino_base.build_src_filter} - - - - - - - - - - - - - - + ${arduino_base.build_src_filter} - - - - - - - - - - - - - - - board_upload.offset_address = 0x08000000 upload_protocol = stlink diff --git a/zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 000000000..4c861312c --- /dev/null +++ b/zephyr/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,117 @@ +#include + +/* + * Zephyr device tree overlay — Nordic nRF54L15-DK + EBYTE E22-900M30S (SX1262) + * + * P1 GPIO bank runs at 1.8V VDDIO (LP domain) — NOT compatible with SX1262 + * which needs VIH ≥ 2.31V (0.7 × 3.3V). All E22 signals therefore use P2, + * which is in the HP domain (3.0V VDDIO), via SPIM00. + * + * SPIM00 (HP domain, 3.0V) replaces SPIM20 (LP domain, 1.8V). + * Default pinctrl from cpuapp_common.dtsi already maps SPIM00 to P2 pins: + * MISO = P2.04, MOSI = P2.02, SCK = P2.01 + * + * The on-board MX25R64 NOR flash was attached to SPIM00; it is not used by + * Meshtastic (LittleFS lives in internal RRAM), so we delete its DTS node to + * free the bus and P2.05 (its CS pin) for our use. + * + * Physical wiring (all P2, HP domain): + * E22 MISO → P2.04 (SPIM00 MISO) + * E22 NSS → P2.05 (freed from MX25R64 CS, RadioLib GPIO) + * E22 DIO1 → P2.06 (IRQ — interrupt capable via gpiote30) + * E22 BUSY → P2.03 (GPIO input) + * E22 RST → P2.00 (GPIO output) + * E22 RXEN → P2.07 (held HIGH via ANT_SW; replaces LED2 on DK) + * E22 MOSI → P2.02 (SPIM00 MOSI) + * E22 SCK → P2.01 (SPIM00 SCK) + * + * Pin numbering convention: P0.n = n, P1.n = 16+n, P2.n = 32+n. + */ + +/* Disable uart20 — no longer needed for LoRa (P1 domain abandoned). */ +&uart20 { + status = "disabled"; +}; + +/* Disable uart30 to free P0.00–P0.03 from the uart30_default pinctrl (which + * also reserves P0.03 as UART30 CTS). The peripheral instance 30 is shared + * with i2c30/spi30 — only one of {UARTE30, TWIM30, SPIM30} can be enabled at + * a time. We pick TWIM30 (i2c30) for sensors (SHT40 / INA3221 / SE050 on the + * custom PCB; BMP280 / INA228 on the DK for bring-up). Console stays on RTT + * via CONFIG_RTT_CONSOLE=y. */ +&uart30 { + status = "disabled"; +}; + +/* I2C bus via TWIM30 (HP domain, 3.0 V), SDA=P0.03 / SCL=P0.04. + * P0.03 was the UART30 CTS; freed by disabling uart30 above. + * P0.04 was button SW3 on the DK; deleted below. The pad still routes to + * the SW3 button on the board — DO NOT press SW3 during I2C use, it will + * short SCL to GND mid-transaction. + * 400 kHz is the highest standard rate supported by all three target sensors + * (BMP280, INA228, SE050). External 4.7 kΩ pull-ups required on both lines. */ +&pinctrl { + i2c30_default: i2c30_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c30_sleep: i2c30_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&i2c30 { + status = "okay"; + clock-frequency = ; /* 400 kHz */ + pinctrl-0 = <&i2c30_default>; + pinctrl-1 = <&i2c30_sleep>; + pinctrl-names = "default", "sleep"; +}; + +/ { + buttons { + /* button1 (P1.09) and button2 (P1.08) no longer repurposed — + * E22 now uses P2. button3 (P0.04) is now I2C SCL — its node + * must be removed before the i2c30 pinctrl can claim the pad. */ + /delete-node/ button_1; + /delete-node/ button_2; + /delete-node/ button_3; + }; + + aliases { + /delete-property/ sw1; + /delete-property/ sw2; + /delete-property/ sw3; + }; +}; + +/* + * Override SPIM00 to remove the MX25R64 NOR flash child. + * The SPIM00 peripheral itself stays enabled (status = "okay" from + * cpuapp_common.dtsi) with its existing pinctrl (P2.01/P2.02/P2.04). + * RadioLib drives CS (P2.05) as a plain GPIO — no hardware CS needed. + */ +&spi00 { + /delete-node/ mx25r6435f@0; +}; + +/* + * Grow storage_partition from 36 KB to 700 KB by reclaiming slot1_partition. + * slot1 (image-1) is the MCUboot secondary slot for dual-bank OTA, which we + * don't use (flashing is direct via J-Link). With only 9 blocks (36 KB / 4 KB) + * LittleFS ran out of space during COW writes of config.proto. + * New layout: storage_partition spans 0xb6000..0x165000 (700 KB, ~175 blocks). + */ +/delete-node/ &slot1_partition; + +&storage_partition { + reg = <0xb6000 0xaf000>; +}; diff --git a/zephyr/prj.conf b/zephyr/prj.conf new file mode 100644 index 000000000..0723d0cd6 --- /dev/null +++ b/zephyr/prj.conf @@ -0,0 +1,299 @@ +# Zephyr project configuration for nRF54L15 Meshtastic port +# +# NOTE: this prj.conf is shared by ALL Zephyr PlatformIO environments +# in this project. Keep it compatible with any future Zephyr targets. + +# ── C++ support (required by Meshtastic) ────────────────────────────────────── +CONFIG_CPP=y +CONFIG_STD_CPP17=y +# Full libstdc++ — provides , , , , etc. +# Works with either newlib or picolibc (Zephyr auto-selects based on board). +CONFIG_REQUIRES_FULL_LIBCPP=y +# Disable C++ exceptions — not needed by Meshtastic and saves RAM/ROM +CONFIG_CPP_EXCEPTIONS=n + +# ── Peripheral subsystems ───────────────────────────────────────────────────── +CONFIG_SPI=y +CONFIG_I2C=y +CONFIG_GPIO=y + +# sys_reboot() used by BLE zombie-connection watchdog (BleDeferredThread). +# nRF54L15 SW-LL occasionally drops the BLE link without forwarding +# LE Disconnection Complete to the host; cold reboot is the only reliable +# recovery path. +CONFIG_REBOOT=y + +# ── ATT Prepare/Execute Write (LONG WRITE) ─────────────────────────────────── +# iOS CoreBluetooth automatically fragments writes >MTU-3 via ATT Prepare Write +# (opcode 0x16). Default CONFIG_BT_ATT_PREPARE_COUNT=0 makes Zephyr reject with +# "Request Not Supported" (0x06), which iOS surfaces as a write error → +# disconnect. With MTU=65 any ToRadio write >62 bytes triggers this path. +# Enabling this allocates N prep_pool buffers (each BT_ATT_BUF_SIZE = 65 bytes) +# and reassembles fragments into a single write_toradio() call on execute. +# 4 × 65 = 260 B max assembled value — enough for typical iOS NodeInfo/admin +# writes after config stream completes. +CONFIG_BT_ATT_PREPARE_COUNT=4 + +# ── Filesystem — LittleFS on storage_partition (RRAM) ─────────────────────── +# Size is set by the board overlay (the nRF54L15-DK overlay reclaims slot1 to +# expand storage_partition to ~700 KB). Capacity is reported at runtime via +# FIXED_PARTITION_SIZE(storage_partition) in InternalFileSystem::totalBytes(). +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MKFS=y +# Disable SPI NOR flash driver — MX25R64 node deleted from DTS, SPIM00 used +# exclusively by RadioLib (SX1262). Without this, the spi_nor driver claims +# SPIM00 at boot and tries to read MX25R64 ID (gets garbage since the chip is +# not wired), producing "Device id a8 a8 a8 does not match config c2 28 17". +CONFIG_SPI_NOR=n +# Disable runtime PM — keeps SPI initialization path simple; avoids any +# interaction between PM auto-suspend/resume cycles and the SPIM00 clock +# request mechanism (CONFIG_CLOCK_CONTROL_NRF_HSFLL_GLOBAL). +CONFIG_PM_DEVICE_RUNTIME=n +# Suppress Zephyr FS subsystem's internal error/warning logs (ENOENT on +# missing files and EEXIST on duplicate mkdir are expected and handled). +CONFIG_FS_LOG_LEVEL_OFF=y + +# ── Console / logging ───────────────────────────────────────────────────────── +# Use SEGGER RTT for console — does not require COM3 (CDC UART), reads via SWD +CONFIG_UART_CONSOLE=n +CONFIG_USE_SEGGER_RTT=y +CONFIG_RTT_CONSOLE=y +CONFIG_LOG=y +CONFIG_LOG_BACKEND_RTT=y +CONFIG_LOG_DEFAULT_LEVEL=2 +# Immediate mode: log writes go directly to RTT backend without a separate thread. +# Deferred mode requires the log thread to run (lowest priority — never gets CPU +# in heavy setup()/loop() workloads), leaving the RTT buffer empty indefinitely. +CONFIG_LOG_MODE_IMMEDIATE=y +# Force RTT control block re-init on every boot — prevents stale/corrupted CB after crash +CONFIG_SEGGER_RTT_INIT_MODE_ALWAYS=y +# Use RTT channel 1 for the LOG backend, channel 0 (Terminal) for direct printk. +# Sharing channel 0 forces LOG_PRINTK=y (deferred) to avoid corruption. +CONFIG_LOG_BACKEND_RTT_BUFFER=1 +# Buffer sizes shrunk from 24576 → 4096 to free ~40 KB of BSS for newlib heap. +# At 24576 the BSS pushed _end up so far that newlib heap was only ~25 KB, +# and BUF_ACL_RX_SIZE=152 + BLE/PhoneAPI lazy init ran out of malloc space. +# 4 KB still gives several seconds of log retention before host attaches. +CONFIG_LOG_BACKEND_RTT_BUFFER_SIZE=4096 +# Overwrite oldest data if buffer fills — never stalls +CONFIG_LOG_BACKEND_RTT_MODE_BLOCK=n +CONFIG_LOG_BACKEND_RTT_MODE_OVERWRITE=y +CONFIG_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE=256 + +# ── LFXO clock source — use RC oscillator to avoid ~2s crystal stabilization +# disrupting the GRTC timer and hanging k_sleep +CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y + +# ── Stack sizes — Meshtastic setup() is heavy (RadioLib, NodeDB, printf) ────── +# bt_enable() called from nrf54l15Setup() needs >8KB. +# Phase 7: CONFIG_BT_SETTINGS=y causes bt_set_name() → settings_save_one() → +# settings_file_save() → LittleFS I/O. The I/O chain needs ~3 KB of stack +# headroom beyond what the BT init alone requires. Increase main stack to 24KB +# and system workqueue to 8KB to cover both the cooperative-OSThread call path +# (which runs on the main thread) and any async flash work items. +CONFIG_MAIN_STACK_SIZE=24576 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192 +# Log processing thread stack — default 768 overflows when processing RTT fault dump +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 + +# ── Fault/exception diagnostics — identify ~2000ms crash ───────────────────── +CONFIG_FAULT_DUMP=2 +CONFIG_EXCEPTION_DEBUG=y +CONFIG_STACK_SENTINEL=y +# Thread names + extra exception info — fault dumps then identify the failing +# thread (otherwise "Current thread: 0x... (unknown)") and include r4-r11 + psp +# so the custom k_sys_fatal_error_handler can walk the stack to show the caller +# chain. Cheap (~32 B/thread for names, no perf hit) and very useful when a +# crash recurs in the field. +CONFIG_THREAD_NAME=y +CONFIG_EXTRA_EXCEPTION_INFO=y +# SEGGER RTT buffer — keep modest (4 KB) to leave RAM for newlib heap. +CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096 +# Report reset reason from previous crash +CONFIG_HWINFO=y + +# ── Bluetooth ───────────────────────────────────────────────────────────────── +# Zephyr BT host + LL SW controller (MPSL) — peripheral role only +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y +# SMP / LE Encryption enabled so the Meshtastic app pairs with a PIN before +# accessing the GATT service. +CONFIG_BT_SMP=y +# Enforce MITM so clients must complete passkey exchange — without this Just +# Works pairings complete silently without prompting the user for a PIN. +CONFIG_BT_SMP_ENFORCE_MITM=y +# Fixed-passkey path so the device (no display) can advertise a known PIN via +# bt_passkey_set() when config.bluetooth.mode == FIXED_PIN. +CONFIG_BT_FIXED_PASSKEY=y +# Allow legacy pairing as fallback. SC_PAIR_ONLY=y has been observed to cause +# some clients to abort pairing with reason 0x01 within 150 ms of the pairing +# request, before any PIN dialog appears. Accepting legacy lets the same +# clients fall through to Passkey Entry successfully. +CONFIG_BT_SMP_SC_PAIR_ONLY=n +# BT_LL_SW_SPLIT is auto-selected from DT (zephyr,bt-hci-ll-sw-split node in nRF54L15 DTS) +# Do NOT set CONFIG_BT_CTLR=y (deprecated — radio silently non-functional) +# Dynamic device name so bt_set_name() can embed the node short ID at runtime +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_DEVICE_NAME_MAX=32 +# Only need one simultaneous central connection +CONFIG_BT_MAX_CONN=1 +# BT subsystem logging — INF for connection/service diagnostics +CONFIG_BT_LOG_LEVEL_INF=y +# BT thread stacks — defaults are too small for nRF54L15 SW-LL init. +# prio_recv_thread overflows at 2048; bump all BT stacks to safe sizes. +# BT RX thread runs ALL our GATT write callbacks (rx_work_handler → hci_acl → +# bt_conn_recv → bt_l2cap_recv → bt_att_recv → write_toradio_cb → +# PhoneAPI::handleStartConfig → getFiles("/", 10) recursion + nanopb encode). +# 4096 overflows on "Client wants config" → abort() / kernel panic (reason 4). +CONFIG_BT_RX_STACK_SIZE=4096 +CONFIG_BT_HCI_TX_STACK_SIZE=1024 +# bt_long_wq runs bt_pub_key_gen (ECC P256 keygen) on this thread. +# Defaults (prio=10, stack=1400) leave it starved by Meshtastic app threads +# at boot: pub_key gen never completes, so smp_public_key() defers +# indefinitely waiting for sc_public_key, and every SC pairing attempt +# stalls right after exchanging public keys (no PIN prompt on iOS, every +# AUTHEN-gated char rejects with ATT error 0x05). +# Prio 0 = highest preemptible, ties with main; stack 4096 clears P256M +# driver frames with margin. +CONFIG_BT_LONG_WQ_PRIO=0 +CONFIG_BT_LONG_WQ_STACK_SIZE=4096 +# Use legacy advertising (bt_le_adv_start / HCI 0x2006 path). +# With CONFIG_BT_EXT_ADV=y, bt_le_adv_start() is internally translated to the +# extended HCI path with LEGACY-bit (0x2036), which produces non-connectable PDUs +# on the nRF54L15 SW-LL. With CONFIG_BT_EXT_ADV=n the host uses pure legacy HCI +# commands (0x2006/0x2008/0x200a) — the same path Nordic uses in all nRF54L15 +# NCS examples (peripheral_uart, peripheral_lbs), which is iOS-compatible and +# avoids the LE Remove Advertising Set (0x203c) controller timeout crash. +CONFIG_BT_EXT_ADV=n + +# ── Phase 7: BT bond persistence ────────────────────────────────────────────── +# CONFIG_BT_SETTINGS enables the BT host settings integration: the stack +# automatically calls settings_save_subtree("bt/keys") after pairing, and +# settings_load() on boot restores previously bonded peers. +# +# Backend: SETTINGS_FILE stores all key-value pairs in a single flat file in +# LittleFS. No new partition needed — the existing storage_partition (mounted +# at /lfs, size set by the board overlay) is used. File path: /lfs/bt_settings. +# +# Ordering guarantee: LittleFS is mounted by fsInit() BEFORE nrf54l15Setup() +# calls nrf54l15_bt_preinit(), so the file backend is always available when +# settings_load() is called after bt_enable(). +CONFIG_BT_SETTINGS=y +CONFIG_SETTINGS=y +CONFIG_SETTINGS_FILE=y +CONFIG_SETTINGS_FILE_PATH="/lfs/bt_settings" +# BT_MAX_PAIRED default is 1 — first bond (e.g. iOS) blocks every subsequent +# peer's SMP pairing request with "Unable to get keys" because there is no free +# bt_keys slot to allocate. Raise to 4 so the device can simultaneously hold +# iOS, Windows, Linux, and one spare bond. Add OVERWRITE_OLDEST so that when +# the table fills, the LRU peer is evicted instead of rejecting the new pair. +CONFIG_BT_MAX_PAIRED=4 +CONFIG_BT_KEYS_OVERWRITE_OLDEST=y +# Disable GATT database caching and Service Changed characteristic. +# CONFIG_BT_GATT_CACHING (default y with BT_SETTINGS) marks every new client as +# "not change-aware" and returns ATT_ERR_DB_OUT_OF_SYNC (0x12) on every GATT +# request until the client reads the DB-hash characteristic. The Meshtastic app +# does not implement GATT caching and silently aborts service discovery on 0x12, +# causing the connection to stall with zero GATT activity. +# CONFIG_BT_GATT_SERVICE_CHANGED (default y) adds the Generic Attribute Profile +# service; disabling it is required before BT_GATT_CACHING can be disabled. +CONFIG_BT_GATT_SERVICE_CHANGED=n +CONFIG_BT_GATT_CACHING=n +# Disable automatic PHY update (1M→2M) after connection. +# The nRF54L15 SW-LL fails the LL_PHY_REQ/RSP exchange and disconnects +# exactly 1.786s after connection — before any ATT/GATT operations. +CONFIG_BT_AUTO_PHY_UPDATE=n +# ATT/GATT/L2CAP debug logging — see exactly what happens after connection +CONFIG_BT_ATT_LOG_LEVEL_DBG=y +CONFIG_BT_GATT_LOG_LEVEL_DBG=y +CONFIG_BT_SMP_LOG_LEVEL_DBG=y +# L2CAP DBG: shows recv on fixed ATT channel — confirms whether iOS sends any data +CONFIG_BT_L2CAP_LOG_LEVEL_DBG=y +# Keep bt_conn at INF — DBG floods RTT buffer every ~150µs (tx_processor loop), +# overwriting all ATT/GATT messages before they can be read. +# Connection events (connected/disconnected) are logged at INF level. +CONFIG_BT_CONN_LOG_LEVEL_INF=y +# Keep HCI logs at INF to save RAM (log thread processing buffers, etc.). +# (Earlier DBG was used to diagnose the hci_acl → L2CAP stall — fix applied.) +CONFIG_BT_HCI_CORE_LOG_LEVEL_INF=y +CONFIG_BT_HCI_DRIVER_LOG_LEVEL_INF=y +# Fix: ACL packets reach hci_acl() but never reach bt_l2cap_recv(). +# Root cause: bt_conn_recv() calls bt_conn_tx_notify(conn, true) which submits +# tx_complete_work to k_sys_work_q and blocks on k_work_flush(). The BT rx +# workqueue (bt_workq) is stuck in k_work_flush waiting for the system +# workqueue, which is busy with LittleFS I/O / other work → dead stall until +# iOS supervision timeout fires (5s) and disconnects with reason 0x13. +# Solution: dedicate a separate workqueue for TX notify processing so it is +# independent from the system workqueue. +CONFIG_BT_CONN_TX_NOTIFY_WQ=y +# Dedicated workqueue only runs tx_notify_process() (iterates tx_complete list, +# calls short callbacks). Default 8192 is overkill and eats malloc heap needed +# by PowerFSM init → realloc() returns NULL → bus fault during FSM::add_transition. +CONFIG_BT_CONN_TX_NOTIFY_WQ_STACK_SIZE=2048 + +# ── ATT/L2CAP MTU — larger payloads for Meshtastic packets ─────────────────── +# TX side: controller sends up to L2CAP_TX_MTU bytes per ATT operation. +# RX side: server ATT MTU is min(BT_L2CAP_TX_MTU, BT_BUF_ACL_RX_SIZE - 4). +# Both set to 247 / 251 → ATT MTU = 247 in each direction, matching Zephyr's +# samples/bluetooth/mtu_update reference. This means typical iOS ToRadio +# writes (NodeInfo, channel settings, common admin packets) fit in a single +# ATT_WRITE_REQ and avoid the ATT Prepare/Execute Write path entirely. +# CONFIG_BT_ATT_PREPARE_COUNT=4 (above) still backstops oversized writes. +# +# Heap dependency: bumping BUF_ACL_RX_SIZE > default (~69) grows the BT host +# net_buf pools in BSS, which proportionally shrinks the newlib heap arena +# (MAX_HEAP_SIZE = SRAM_SIZE - (_end - SRAM_BASE), so any BSS growth steals +# from the heap directly). Empirically the lazy BLE init path +# (setBluetoothEnable → startDisabled → bt_set_name → settings_save → +# LittleFS) needs ~12 KB of newlib heap to run without bad_alloc. At +# BUF_ACL_RX_SIZE=251 with the previous 24 KB RTT buffers (LOG_BACKEND_RTT + +# SEGGER_RTT_BUFFER_SIZE_UP), the heap collapsed to ~4 KB free at +# transition time → `new char[]` in RedirectablePrint::log returned NULL → +# libstdc++ called abort() from main thread. Shrinking both RTT buffers to +# 4 KB (above) frees ~40 KB of BSS for the heap and resolves it. +# +# DLE stays off (BT_DATA_LEN_UPDATE=n below): the LLCP remote table at +# ull_llcp_remote.c:878 is guarded by #ifdef CONFIG_BT_CTLR_DATA_LENGTH, so +# the controller answers iOS's LL_LENGTH_REQ with LL_UNKNOWN_RSP and falls +# back to 27-byte LL PDUs. The host reassembles LL PDUs into L2CAP frames +# up to BT_BUF_ACL_RX_SIZE before dispatching to ATT. +CONFIG_BT_L2CAP_TX_MTU=247 +# Server ATT MTU = BUF_ACL_RX_SIZE - 4 = 247 (matches L2CAP_TX_MTU) +CONFIG_BT_BUF_ACL_RX_SIZE=251 + +# ── Fix: LL Feature Exchange collision (ROOT CAUSE of iOS GATT hang) ───────── +# On connection, Zephyr host calls bt_hci_le_read_remote_features() because +# BT_CTLR_PER_INIT_FEAT_XCHG=y makes can_initiate_feature_exchange() return +# true for peripheral role. This makes the controller send LL_PER_INIT_FEAT_XCHG +# to iOS right after connecting. +# iOS (as central) simultaneously sends LL_FEATURE_REQ to the peripheral. +# The nRF54L15 SW-LL mishandles this COLLISION: iOS waits for LL_FEATURE_RSP +# to its LL_FEATURE_REQ, never gets it, and stalls — sending zero L2CAP bytes. +# BT_CTLR_PER_INIT_FEAT_XCHG=n: host does NOT send HCI_LE_Read_Remote_Features +# as peripheral → no LL_PER_INIT_FEAT_XCHG sent → no collision → iOS feature +# exchange completes → iOS proceeds to L2CAP/ATT. +CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG=n +# Fix: LL Connection Parameter Request handling. +# BT_CTLR_CONN_PARAM_REQ=n was ineffective: the LLCP remote decode table in +# ull_llcp_remote.c hardcodes PDU_DATA_LLCTRL_TYPE_CONN_PARAM_REQ → PROC_CONN_PARAM_REQ +# regardless of Kconfig. With =n the handler is compiled out → controller asserts / +# enters broken state when iOS sends LL_CONN_PARAM_REQ (which is optional from Central). +# Fix: =y so the procedure is actually handled. To avoid host/peripheral vs Central +# collision at 5 s (deferred_work → send_conn_le_param_update), disable auto-update +# below so the host never initiates HCI_LE_Connection_Update. +CONFIG_BT_CTLR_CONN_PARAM_REQ=y +# Prevent Zephyr host from initiating connection parameter update 5 s after connect. +# With CONN_PARAM_REQ=y, if iOS (Central) already issued LL_CONN_PARAM_REQ and the +# SW-LL is mid-procedure, a simultaneous host-initiated HCI_LE_Connection_Update +# creates an LL collision. Disabling the auto-update avoids the collision entirely. +CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n +# Disable optional LL procedures (belt-and-suspenders while debugging): +# BT_PHY_UPDATE=n + BT_CTLR_PHY_2M=n: no PHY update procedure → iOS doesn't attempt LL_PHY_REQ +# BT_DATA_LEN_UPDATE=n: no DLE → controller sends LL_UNKNOWN_RSP to LL_LENGTH_REQ +CONFIG_BT_CTLR_PHY_2M=n +CONFIG_BT_PHY_UPDATE=n +CONFIG_BT_DATA_LEN_UPDATE=n