From 43e9db58230ce2a512820454aa158c8d4a871566 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 19 Sep 2026 09:52:37 -0700 Subject: [PATCH] add heltec wifi lora32 v4-r8 board + anchor the esp32 light-sleep allowlist The V4-R8 is an ESP32-S3R8 with 8 MB octal PSRAM, where V4 and V4.3 are S3R2 with 2 MB quad. Octal PSRAM claims GPIO33-37, which moves Vext 36->40, the LED 35->46 and VGNSS_Ctrl 34->42, and drops ADC_Ctrl and GNSS_RST. CTX binds as lna-bypass-gpios rather than tx-enable-gpios, so the board is a fourth one where set radio.fem.rxgain works; the CLI reference and the register-patch help text say so. The allowlist change is needed for the board to be added correctly. CMake MATCHES is a substring search, so the existing heltec_wifi_lora32_v4 entry also matches heltec_wifi_lora32_v4_r8 and the new board would get CONFIG_PM=y without anyone deciding it should, on a path pm_esp32.conf marks as not yet validated on hardware. Anchoring preserves the five boards that were intended to match, including heltec_wireless_tracker_v2, which previously qualified only by substring and is now listed explicitly. Co-Authored-By: Claude Opus 5 (1M context) --- docs/Repeater_CLI_commands.md | 2 +- docs/supported_boards.md | 34 ++ zephcore/CMakeLists.txt | 24 +- zephcore/Kconfig | 2 +- zephcore/boards/common/pm_esp32.conf | 8 + .../esp32/heltec_wifi_lora32_v4_r8/Kconfig | 3 + .../Kconfig.heltec_wifi_lora32_v4_r8 | 3 + .../heltec_wifi_lora32_v4_r8/board.cmake | 7 + .../esp32/heltec_wifi_lora32_v4_r8/board.conf | 51 +++ .../heltec_wifi_lora32_v4_r8/board.overlay | 189 +++++++++++ .../esp32/heltec_wifi_lora32_v4_r8/board.yml | 6 + .../heltec_wifi_lora32_v4_r8-pinctrl.dtsi | 56 ++++ .../heltec_wifi_lora32_v4_r8_procpu.dts | 315 ++++++++++++++++++ .../heltec_wifi_lora32_v4_r8_procpu_defconfig | 12 + .../partitions.overlay | 56 ++++ 15 files changed, 762 insertions(+), 6 deletions(-) create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig.heltec_wifi_lora32_v4_r8 create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.cmake create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.conf create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.overlay create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.yml create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8-pinctrl.dtsi create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu.dts create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu_defconfig create mode 100644 zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/partitions.overlay diff --git a/docs/Repeater_CLI_commands.md b/docs/Repeater_CLI_commands.md index 6e51bab..036b4f8 100644 --- a/docs/Repeater_CLI_commands.md +++ b/docs/Repeater_CLI_commands.md @@ -346,7 +346,7 @@ four radio parameters together, since they are one interop-critical set. | `set path.hash.mode ` | 0, 1, or 2 | Path hashing algorithm | | `set loop.detect ` | `off`, `minimal`, `moderate`, `strict` | Loop detection sensitivity | | `set radio.rxgain <0\|1\|on\|off>` | | RX gain boost, applied live. Replies `Error: unsupported` on radios without RX boost (SX127x); the pref is still saved. | -| `set radio.fem.rxgain <0\|1\|on\|off>` | default **1** | Routes receive through the external FEM's LNA (`1`) or around it via the FEM's bypass path (`0`), applied live. Sensitivity for battery life — `0` costs roughly 17 dB and saves the LNA's supply current. Transmit, and the driver's idle/sleep gating of the FEM, are unaffected either way. Supported only where the FEM's receive path is software-selectable and that select line is wired to the radio node as `lna-bypass-gpios` — today the three KCT8103L boards, `heltec_t096`, `heltec_wireless_tracker_v2` and `heltec_wifi_lora32_v43`. Every other board reports `Error: unsupported`: `heltec_wifi_lora32_v4`'s GC1109 has no receive-path select (its CPS is don't-care in RX, same as MeshCore); `station_g2`, `gat562_30s`, `ikoka_nano_30dbm` and `promicro_sx1262` have only the DIO2/TXEN/RXEN transmit-receive switch; `rak3401_1watt`'s SKY66122 is enabled by a standalone always-on regulator outside the radio node; and non-SX126x radios (LR1110, LR2021, SX127x) never implement it. The pref is still saved when unsupported. **Do not expect the FEM's chip-enable to be the knob** — deasserting `antenna-enable-gpios` in RX shuts the part down and takes the through path with it (~69 dB measured on a V4.3), which is what 1.17.2 did before this moved to `lna-bypass-gpios`. | +| `set radio.fem.rxgain <0\|1\|on\|off>` | default **1** | Routes receive through the external FEM's LNA (`1`) or around it via the FEM's bypass path (`0`), applied live. Sensitivity for battery life — `0` costs roughly 17 dB and saves the LNA's supply current. Transmit, and the driver's idle/sleep gating of the FEM, are unaffected either way. Supported only where the FEM's receive path is software-selectable and that select line is wired to the radio node as `lna-bypass-gpios` — today the KCT8103L boards, `heltec_t096`, `heltec_wireless_tracker_v2`, `heltec_wifi_lora32_v43` and `heltec_wifi_lora32_v4_r8`. Every other board reports `Error: unsupported`: `heltec_wifi_lora32_v4`'s GC1109 has no receive-path select (its CPS is don't-care in RX, same as MeshCore); `station_g2`, `gat562_30s`, `ikoka_nano_30dbm` and `promicro_sx1262` have only the DIO2/TXEN/RXEN transmit-receive switch; `rak3401_1watt`'s SKY66122 is enabled by a standalone always-on regulator outside the radio node; and non-SX126x radios (LR1110, LR2021, SX127x) never implement it. The pref is still saved when unsupported. **Do not expect the FEM's chip-enable to be the knob** — deasserting `antenna-enable-gpios` in RX shuts the part down and takes the through path with it (~69 dB measured on a V4.3), which is what 1.17.2 did before this moved to `lna-bypass-gpios`. | | `set rxduty <0\|1\|on\|off>` | | RX duty cycle mode *(reboot required)*. Window timing auto-sized per SF/BW/preamble from the SX126x datasheet constraints (boot log line `rxduty:` shows the result). Zero-loss guarantee assumes senders on preamble-32 firmware (current MeshCore at SF≤8); legacy preamble-16 senders are only caught ~50% worst-phase — keep off until the local mesh has converted. Presets with 16-symbol preambles (SF≥9) fall back to continuous RX automatically. | | `set display.rotate <0\|1\|on\|off>` | default **0** | Rotate the display 180 degrees, for cases and upgrade kits that mount the screen upside down (e.g. the Meshnology N37E for the Wio Tracker L1). Applied live — the driver flips the panel's `SEGMENT_MAP` and `COM_OUTPUT_SCAN`, two bytes on the wire, and the next frame comes out rotated with no redraw and no per-frame cost. **Only full-height SSD1306 and SH1106 panels support this** (`rak4631`, `gat562_30s`, `heltec_wifi_lora32_v4`/`v43`, `lilygo_t3s3`, `station_g2`, `wio_tracker_l1`); every other panel replies `Error: this panel cannot rotate` and the pref is **not** saved, so a stored value can never disagree with what the screen shows. `lilygo_timpulse_plus` is excluded despite being an SSD1306: its 64x32 glass is windowed into a 128x64 controller at `page-offset 4`, and the COM-scan reversal flips the controller's whole range, which would move the image off the bonded region. E-paper (SSD16xx) is excluded on purpose: its driver accepts a 180-degree orientation but implements it by flipping the RAM entry mode only, which reverses byte order without reversing bit order inside each byte — it would report success and render wrong. | | `set input.rotate <0\|1\|on\|off>` | default **0** | Swap the joystick/D-pad axes — up/down and left/right — to match an upside-down mount. Applied live. Deliberately **separate** from `display.rotate`: a case can flip the screen without moving the stick, and boards whose panel cannot rotate can still need the axis swap. Works on every board with directional input, in both the joystick UI and the button UI (where it swaps page-prev/page-next). Non-directional keys, tap codes and long-press gestures are unaffected. | diff --git a/docs/supported_boards.md b/docs/supported_boards.md index 2b16f7b..12ad150 100644 --- a/docs/supported_boards.md +++ b/docs/supported_boards.md @@ -55,6 +55,7 @@ station_g2/esp32s3/procpu heltec_wifi_lora32_v3/esp32s3/procpu heltec_wifi_lora32_v4/esp32s3/procpu heltec_wifi_lora32_v43/esp32s3/procpu +heltec_wifi_lora32_v4_r8/esp32s3/procpu # source-only, no published firmware heltec_wireless_tracker/esp32s3/procpu heltec_wireless_tracker_v2/esp32s3/procpu thinknode_m9/esp32s3/procpu @@ -66,6 +67,39 @@ ttgo_lora32/esp32/procpu # source-only, no published firmware > > Heltec V3 console/shell use `uart0` (UART serial) in ZephCore. > +> **Heltec WiFi LoRa 32 V4-R8** (`heltec_wifi_lora32_v4_r8/esp32s3/procpu`): a third board in the +> V4 family, distinct from both `heltec_wifi_lora32_v4` (the V4.2) and `heltec_wifi_lora32_v43` +> (the V4.3). The "R8" is the SoC part — **ESP32-S3R8 with 8 MB octal PSRAM**, where V4 and V4.3 +> are both S3R2 with 2 MB quad. +> +> Octal PSRAM consumes GPIO33–37, and that one fact explains every pin move on the revision: +> +> | Function | V4 (V4.2) | V4.3 | **V4-R8** | +> |---|---|---|---| +> | PSRAM | 2 MB quad | 2 MB quad | **8 MB octal** | +> | FEM | GC1109 | KCT8103L | KCT8103L | +> | PA control | TX_EN GPIO46 | CTX GPIO5 | **CTX GPIO5** | +> | Vext_Ctrl | GPIO36 | GPIO36 | **GPIO40** | +> | VGNSS_Ctrl | GPIO34 | GPIO34 | **GPIO42** | +> | LED | GPIO35 | GPIO35 | **GPIO46** | +> | GNSS_RST | GPIO42 | GPIO42 | **removed** | +> | ADC_Ctrl | GPIO37 | GPIO37 | **removed** | +> +> `PA_CTX` on GPIO5 is new relative to **V4**, which has no CTX at all — its GC1109 is driven by +> TX_EN on GPIO46 — and identical to **V4.3**. Note GPIO46 changes meaning across the family: FEM +> TX_EN on V4, the LED on V4-R8. CTX is bound as `lna-bypass-gpios`, not `tx-enable-gpios`, +> because it selects the receive path rather than enabling anything. +> +> Verified against the vendor pinmap at . +> +> **Do not flash `heltec_wifi_lora32_v4` or `heltec_wifi_lora32_v43` to a V4-R8.** Those boards +> declare `&psram0 { size = ; }`, so PSRAM comes up as 2 MB quad on an 8 MB octal +> part, and +> they drive GPIO35/36/37 as ordinary outputs — GPIO33-37 are the octal-PSRAM pads on an S3R8. +> PSRAM is therefore both misconfigured and pin-contended. On top of that Vext is driven on GPIO36 +> instead of GPIO40, so the rail feeding the OLED and sensors never switches, and GPIO37 is driven +> as an ADC enable this revision does not have. +> > **Heltec Wireless Tracker** (`heltec_wireless_tracker/esp32s3/procpu`): V1.1 > ESP32-S3-FN8 companion with SX1262, ST7735R 160x80 TFT, and UC6580 GPS. > diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index 1a6e7a6..7e116dc 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -489,6 +489,13 @@ endif() # looking excellent on a current meter. # ttgo_lora32 -> SX127x; the DIO1 wake flag lives in the sx126x half of # patches/zephyr/0003, so nothing marks the pin as a wake source. +# heltec_wifi_lora32_v4_r8 -> absent for a different reason: its wiring does +# qualify (DIO1=GPIO14, NSS=GPIO8, RESET=GPIO12 on &spi2, button on +# gpio0 0), so every criterion stated here and in pm_esp32.conf is met. +# It is out because CONFIG_PM has never been validated on the board. +# Without the anchoring below it would be swept in by substring match +# rather than by decision. Add it once someone has run pm_esp32.conf's +# three checks on hardware. # # CONSOLE, and why it does NOT gate this list. A native USB Serial/JTAG console # cannot survive light sleep: the peripheral drops off the bus and the HAL has @@ -507,11 +514,20 @@ endif() # re-arms the window. These boards are in the field deliberately to get light # sleep exercised; drop them from the regex to take it away. # -# The regex lists v43 explicitly. "heltec_wifi_lora32_v4" already substring- -# matches it (CMake MATCHES is unanchored), which made V4.3's inclusion look -# accidental; spelling it out makes the intent reviewable. +# Anchored, and every member spelled out. CMake MATCHES is a substring search, +# so the bare alternation matched any BOARD merely *containing* one of these +# names at any offset: "heltec_wifi_lora32_v4" also matches +# "heltec_wifi_lora32_v4_r8", so that board would get CONFIG_PM=y without +# anyone deciding it should. The leading "^" is what stops mid-string matches +# and is not made redundant by the tail. +# +# BOARD carries its qualifiers (heltec_wifi_lora32_v4/esp32s3/procpu), and may +# carry a revision as heltec_wifi_lora32_v3@1/esp32s3/procpu, so the tail +# accepts "@", "/" or end-of-string rather than "$" alone. No board here +# declares a revision today; without "@" one that later did would drop out of +# this list silently. if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND EXTRA_CONF_FILE MATCHES "repeater" - AND BOARD MATCHES "heltec_wifi_lora32_v3|heltec_wifi_lora32_v4|heltec_wifi_lora32_v43|heltec_wireless_tracker") + AND BOARD MATCHES "^(heltec_wifi_lora32_v3|heltec_wifi_lora32_v4|heltec_wifi_lora32_v43|heltec_wireless_tracker|heltec_wireless_tracker_v2)(@|/|$)") set(ZEPHCORE_PM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/pm_esp32.conf") if(EXISTS ${ZEPHCORE_PM_CONF}) list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_PM_CONF}) diff --git a/zephcore/Kconfig b/zephcore/Kconfig index d466c21..98448f6 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -598,7 +598,7 @@ config ZEPHCORE_SX126X_HELTEC_REG_PATCH help Sets the LSB of undocumented register 0x8B5 after the first lora_config(). Described by Heltec engineer @Quency-D in MeshCore PR#1398 — consistently - improves RX reception on boards with GC1109 or KCT8103L PA (Heltec V4/V4.3). + improves RX reception on boards with GC1109 or KCT8103L PA (Heltec V4/V4.3/V4-R8). Upstream MeshCore also enables it for the RAK3401's SKY66122 FEM, so it is not Heltec-specific despite the symbol name. Enable in board.conf for boards with an external PA/FEM in the RX path. diff --git a/zephcore/boards/common/pm_esp32.conf b/zephcore/boards/common/pm_esp32.conf index 4068449..afa0fab 100644 --- a/zephcore/boards/common/pm_esp32.conf +++ b/zephcore/boards/common/pm_esp32.conf @@ -54,6 +54,14 @@ # N/A ttgo_lora32 -> SX127x; patch 0003 arms DIO1 in the sx126x driver # only, so nothing marks the pin as a wake source at all # +# Note the "v4" above means heltec_wifi_lora32_v4 exactly, NOT the whole V4 +# family. heltec_wifi_lora32_v4_r8 is deliberately NOT allowlisted even +# though its wiring qualifies (DIO1 = GPIO14, NSS = GPIO8, RESET = GPIO12 on +# &spi2): CONFIG_PM has never been validated on that board. The allowlist +# regex in CMakeLists.txt is anchored so a name that merely extends one of +# these does not join by substring match. To add it, run the three checks at +# the end of this file on hardware first. +# # On a NOT-OK board the driver logs "Pin N is not wakeup capable" at boot and # the node will light-sleep straight through inbound packets while looking # excellent on a current meter. Check that log line before trusting any diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig new file mode 100644 index 0000000..959ef82 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig @@ -0,0 +1,3 @@ +config HEAP_MEM_POOL_ADD_SIZE_BOARD + int + default 4096 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig.heltec_wifi_lora32_v4_r8 b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig.heltec_wifi_lora32_v4_r8 new file mode 100644 index 0000000..a46e5c2 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/Kconfig.heltec_wifi_lora32_v4_r8 @@ -0,0 +1,3 @@ +config BOARD_HELTEC_WIFI_LORA32_V4_R8 + select SOC_ESP32S3 + select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIFI_LORA32_V4_R8_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.cmake b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.cmake new file mode 100644 index 0000000..f3c3ac1 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.cmake @@ -0,0 +1,7 @@ +if(NOT "${OPENOCD}" MATCHES "^${ESPRESSIF_TOOLCHAIN_PATH}/.*") + set(OPENOCD OPENOCD-NOTFOUND) +endif() +find_program(OPENOCD openocd PATHS ${ESPRESSIF_TOOLCHAIN_PATH}/openocd-esp32/bin NO_DEFAULT_PATH) + +include(${ZEPHYR_BASE}/boards/common/esp32.board.cmake) +include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.conf b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.conf new file mode 100644 index 0000000..13b54d5 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.conf @@ -0,0 +1,51 @@ +# Heltec WiFi LoRa 32 V4-R8 — ESP32-S3R8, 16 MB flash, 8 MB octal PSRAM +# +# Hardware: +# SX1262 + KCT8103L external PA/FEM (GPIO5 CTX instead of GC1109 GPIO46 CPS) +# SSD1306 128x64 OLED on I2C0 +# User button on GPIO0 +# +# V4-R8 vs V4/V4.3. "R8" is the SoC part — ESP32-S3R8, 8 MB octal PSRAM, where +# the plain V4 is an S3R2 with 2 MB quad. Octal PSRAM consumes GPIO33-37, and +# that single fact explains every pin move on this revision: +# Vext_Ctrl GPIO36 -> GPIO40 +# LED GPIO35 -> GPIO46 +# VGNSS_Ctrl GPIO34 -> GPIO42 +# ADC_Ctrl GPIO37 -> removed +# GNSS_RST GPIO42 -> removed (GPIO42 reused for VGNSS_Ctrl) +# FEM is KCT8103L with CTX on GPIO5, as on V4.3. +# Verified against the vendor pinmap at https://heltec.org/project/wifi-lora-32-v4/ +# and Arduino MeshCore variants/heltec_v4_r8. +# Include order: prj.conf → zephcore_common.conf → esp32_common.conf → board.conf + +# Board identification +CONFIG_ZEPHCORE_BOARD_NAME="Heltec V4-R8" +CONFIG_BT_DIS_MODEL_NUMBER_STR="Heltec WiFi LoRa 32 V4-R8" + +# Flash size — 16 MB (override esp32_common.conf 4 MB default) +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y + +# PSRAM mode — ESP32-S3R8 is silicon-strapped OPI octal, unlike the V4/V4.3 +# S3R2's quad. Zephyr 4.5 removed the per-module dtsi, so the size now comes +# from the board's own &psram0 node in the DTS; the mode is a separate choice +# that still defaults to CONFIG_SPIRAM_MODE_QUAD, which would address an octal +# part in the wrong mode. Only the mode is overridden here. Matches the other +# S3R8 boards (station_g2, xiao_esp32s3, thinknode_m9, meshnology_w12). +# +# Verified in the built config: CONFIG_ESP_SPIRAM_SIZE=8388608, +# CONFIG_SPIRAM_MODE_OCT=y, CONFIG_SPIRAM_TYPE_ESPPSRAM64=y. +CONFIG_SPIRAM_MODE_OCT=y + +# Undocumented register 0x8B5 RX improvement (MeshCore PR#1398) +# Applies to both GC1109 and KCT8103L PA boards. +CONFIG_ZEPHCORE_SX126X_HELTEC_REG_PATCH=y + +# TX power — SX1262 input to KCT8103L PA. +# Measured (MeshCore issue #1708, GC1109 data — KCT8103L similar): TX=18 → ~27 dBm +# radiated, practical efficiency peak. TX=10 → ~20 dBm; TX=22 saturates same as TX=18. +CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=12 + +# Single button, ENTER emitted via a zephyr,input-longpress filter (see +# board.overlay): the default 500ms double-press confirm window (shutdown/ +# DFU/offgrid) is too short for two ~1s holds, see Kconfig help text. +CONFIG_ZEPHCORE_UI_CONFIRM_WINDOW_MS=3000 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.overlay new file mode 100644 index 0000000..e7a729e --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.overlay @@ -0,0 +1,189 @@ +/* + * SPDX-License-Identifier: MIT + * Heltec WiFi LoRa 32 V4-R8 — ZephCore overlay + * + * The DTS already defines: SX1262 (lora0), SSD1306 OLED, button, LED, PWM, + * vext_enable, fem_power, I2C0, SPI2, BLE, WiFi. + * This overlay adds ZephCore-specific integration on top. + * + * LoRa SPI2: SCLK=9, MOSI=10, MISO=11, NSS=8 + * LoRa ctrl: DIO1=14, BUSY=13, RESET=12 + * FEM: POWER=7, KCT8103L CSD=2, CTX=5 + * Display I2C0: SDA=17, SCL=18, RST=21 + * Battery ADC: GPIO1 = ADC1_CH0 (&adc0), no enable pin (ADC_Ctrl removed on R8) + * Vext: GPIO40 (active-LOW, powers OLED and sensors) + * Button: GPIO0 + * LED: GPIO46 + * GPS UART1: TX=GPIO38, RX=GPIO39, 9600 baud (Quectel L76K) + * GPS ctrl: VGNSS_Ctrl GPIO42 (active-LOW); no reset line on R8 + */ + +#include + +/ { + chosen { + /* Native USB serial for ZephCore console/CLI */ + zephyr,console = &usb_serial; + zephyr,shell-uart = &usb_serial; + }; + + /* Battery ADC — ZephCore reads via zephyr,user { io-channels }. + * GPIO1 = ADC unit 1 = node &adc0 (label is 0-indexed, unit is 1-indexed); + * the old &adc1 was unit 2 = GPIO11 = SX1262 MISO — always read 0. + * Multiplier = 1100 * divider_ratio. The 1100 is NOT the classic-ESP32 + * ADC reference: Zephyr's adc_esp32 driver calibrates the hardware raw + * through adc_cali_raw_to_voltage() and then re-projects the result onto + * a fixed 1100 mV / 4095 scale before the application sees it + * (adc_esp32.c: acq_raw = acq_mv * ((1 << res) - 1) / meas_ref_internal, + * with meas_ref_internal hardcoded to 1100 and no devicetree property to + * change it). So 1100 is exactly right here whatever the SoC's true 0 dB + * span is, and every other ESP32 board in this tree uses the same form. + * + * The ratio is what 5720 got wrong: it used 5.2, while this 390K/100K + * divider is (390+100)/100 = 4.90. meshnology_w12 states the same 4.90 + * for the same network from its schematic (R50/R51) and lands on 5390. + * 5720 therefore reads about 6% high. + * + * Plausibility check, computed rather than observed through this driver: + * a separate ESP-IDF reader on this board measured 868.4 mV at the + * divider output against a cell metered at 4.18 V. Projecting that pin + * voltage onto the 1100 mV / 4095 scale above gives a raw of 3233, from + * which 5390 would yield 4254 mV, 1.8% high -- the same offset that + * reader itself showed against the meter, i.e. resistor tolerance rather + * than a modelling error. Every number after "868.4 mV" is arithmetic, so + * this supports 5390 without being a measurement of it. + * + * Note 5.2 is not arithmetic from these resistors at all; it comes from + * MeshCore's empirical Arduino multiplier, where analogReadMilliVolts() + * returns true millivolts and the constant therefore absorbs the loaded + * ratio. Settling 4.90 against an effective loaded value needs + * getBattMilliVolts() itself read against a metered cell, which has not + * been done -- and that is the reading this comment does NOT have. */ + zephyr,user { + io-channels = <&adc0 0>; + vbat-mv-multiplier = <5390>; + }; + + /* No vbat-enable regulator. + * + * V4/V4.3 gate the divider with ADC_Ctrl on GPIO37, but GPIO33-37 are the + * octal-PSRAM pads and this part is an ESP32-S3R8, so that pin is gone -- + * the vendor pinmap lists ADC_Ctrl as "removed" on V4-R8. The divider is + * therefore permanently connected and ZephyrBoard reads it directly; its + * vbat_enable lookup is DT_NODE_EXISTS-guarded and degrades to NULL. + */ + + /* GPS enable — VGNSS_Ctrl on GPIO42, active-LOW. + * + * V4/V4.3 put VGNSS_Ctrl on GPIO34 and a separate GNSS_RST on GPIO42. The + * R8 has neither pin available in that arrangement: GPIO33-37 are consumed + * by the octal PSRAM, so VGNSS_Ctrl was moved onto GPIO42 and the reset line + * was dropped. Matches the vendor pinmap and Arduino MeshCore + * variants/heltec_v4_r8 (PIN_GPS_EN=42, PIN_GPS_EN_ACTIVE=LOW, and no + * PIN_GPS_RESET at all). + * + * With no gps-reset alias this board stays off the manager's + * HAS_T1000_GPS_CONTROL sequence and uses the plain enable path, which is + * what the hardware supports. + */ + gps_en: gps-enable { + compatible = "gpio-leds"; + gps_enable_pin: gps_enable { + gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; /* GPIO42 = gpio1 pin 10 */ + label = "GPS Enable"; + }; + }; + + aliases { + gps-enable = &gps_enable_pin; + }; + + /* Button chain — same mapping as RAK4631 / WisMesh Pocket (rak4631/board.overlay). */ + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + page_btn_multitap { + compatible = "zephcore,input-multi-tap"; + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; +}; + +/* Enable native USB serial for console/CLI */ +&usb_serial { + status = "okay"; +}; + +/* Patch LoRa radio: add pull-down on DIO1 and enable RX boosted mode. + * DTS defines antenna-enable, dio2-tx-enable, TCXO and lna-bypass-gpios. + * Deliberately NOT tx-enable-gpios: binding CTX that way is what gated CSD in + * 1.17.2 and cost ~69 dB of receive path (see the DTS note on lna-bypass). */ +&lora0 { + dio1-gpios = <&gpio0 14 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; + rx-boosted; +}; + +/* GPS on UART1 — optional external NMEA module on the pin header, wired to the + * same pins Arduino MeshCore uses for this board. Its macro names read the + * other way round (PIN_GPS_RX=38, PIN_GPS_TX=39), but it calls + * Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX) and Arduino-ESP32's signature is + * setPins(rxPin, txPin) -- rx first -- so the wiring is MCU TX=38, RX=39. + * Proven on hardware: the module answers its version query (URANUS5) only + * with the pins this way round. Do not "fix" this back to the macro names. + * Upstream leaves uart1 disabled and gives it no pinctrl, so both are set here. + * GPIO38/39 are otherwise unclaimed on the V4-R8; the console stays on usb_serial + * (or uart0/GPIO43-44 for companion USB builds). 9600 baud matches Arduino's + * default for this variant. + * + * The node is unconditional, like the V3's gnss node for its optional module: + * with nothing fitted the driver simply never sees a fix. */ +&pinctrl { + uart1_default: uart1_default { + group1 { + pinmux = ; + output-high; + }; + + group2 { + pinmux = ; + bias-pull-up; + }; + }; +}; + +&uart1 { + status = "okay"; + current-speed = <9600>; + pinctrl-0 = <&uart1_default>; + pinctrl-names = "default"; + + gnss: gnss-nmea-generic { + compatible = "gnss-nmea-generic"; + }; +}; + +/* I2C sensors — auto-detected at runtime */ +&i2c0 { + #include "../../common/sensors-i2c.dtsi" +}; + +/* + * Flash partitions live in partitions.overlay (this dir) so the identical map + * is fed to both the app and the MCUboot image — see that file and + * sysbuild/CMakeLists.txt. + */ + +/* LittleFS auto-mount — standard /lfs mount point */ +#include "../../common/filesystem.dtsi" +/* USB CDC-ACM — companion USB transport, auto-enabled for companion builds via + * boards/common/esp32s3_usb.conf (see the auto-include block in CMakeLists.txt). + * The chosen console above is usb_serial, i.e. the same D+/D- pins, so the conf's + * paired overlay moves the console to uart0/GPIO43-44 for those builds. */ +#include "../../common/esp32s3_usb_otg.dtsi" diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.yml b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.yml new file mode 100644 index 0000000..f79b069 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/board.yml @@ -0,0 +1,6 @@ +board: + name: heltec_wifi_lora32_v4_r8 + full_name: Heltec WiFi LoRa 32 V4-R8 + vendor: heltec + socs: + - name: esp32s3 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8-pinctrl.dtsi b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8-pinctrl.dtsi new file mode 100644 index 0000000..efa8e24 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8-pinctrl.dtsi @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: MIT + * Heltec WiFi LoRa 32 V4-R8 — Pin Control Definitions + * + * Pinctrl assignments follow V4.2/V4.3 except LEDC0, which must track the LED's + * move to GPIO46 -- GPIO35 is one of the octal-PSRAM pads on an S3R8. GPIO5/CTX + * is plain GPIO and needs no pinctrl entry. Vext and VGNSS_Ctrl are plain GPIO + * too and live in the DTS and overlay, not here: + * UART0: TX=GPIO43, RX=GPIO44 + * I2C0: SDA=GPIO17, SCL=GPIO18 (SSD1306 OLED + sensors) + * SPI2: SCLK=GPIO9, MOSI=GPIO10, MISO=GPIO11 (SX1262 LoRa) + * LEDC0: CH0=GPIO46 (PWM LED — GPIO35 is an octal-PSRAM pad on the R8) + */ + +#include +#include +#include + +&pinctrl { + uart0_default: uart0_default { + group1 { + pinmux = ; + output-high; + }; + group2 { + pinmux = ; + bias-pull-up; + }; + }; + + i2c0_default: i2c0_default { + group1 { + pinmux = , + ; + drive-open-drain; + }; + }; + + spim2_default: spim2_default { + group1 { + pinmux = , + ; + }; + group2 { + pinmux = ; + output-low; + }; + }; + + ledc0_default: ledc0_default { + group1 { + pinmux = ; + output-enable; + }; + }; +}; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu.dts b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu.dts new file mode 100644 index 0000000..72fa348 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu.dts @@ -0,0 +1,315 @@ +/* + * SPDX-License-Identifier: MIT + * Heltec WiFi LoRa 32 V4-R8 — ZephCore procpu DTS + * + * Hardware: + * MCU: ESP32-S3R8 (16 MB flash, 8 MB octal PSRAM) + * Radio: SX1262 on SPI2, KCT8103L external PA/FEM + * OLED: SSD1306 128x64 on I2C0 + * Button: GPIO0 + * LED: GPIO46 (PWM via LEDC0) + * Vext: GPIO40 active-LOW PMOS rail switch (powers OLED and sensors) + * BatADC: GPIO1 = ADC1_CH0 (&adc0), no enable pin (ADC_Ctrl removed on R8) + * + * V4-R8: ESP32-S3R8 (8 MB octal PSRAM). Octal PSRAM claims GPIO33-37, which is + * why Vext moved 36->40, the LED 35->46, VGNSS_Ctrl 34->42, and ADC_Ctrl and + * GNSS_RST were dropped. FEM is KCT8103L as on V4.3. + * + * V4.3 vs V4.2 difference — FEM chip changed: + * V4.2: GC1109 PA — TX_EN on GPIO46 (CPS) + * V4.3: KCT8103L PA — CTX on GPIO5 (HIGH=TX, LOW=RX/LNA) + * Detection: GPIO2 default pull differs (GC1109: pull-down, KCT8103L: pull-up) + * (See MeshCore PR#1867, Heltec schematic HTIT-WB32LAF_V4.3) + * + * Pins the PSRAM reshuffle does not move. Origin is noted per line, because + * this board takes its FEM from V4.3 and not every line here exists on V4.2: + * GPIO7 = VFEM_Ctrl LDO power (active-HIGH, always-on) — V4.2 onward + * GPIO2 = FEM CSD (chip enable, active-HIGH) — V4.2 onward + * GPIO5 = KCT8103L CTX (TX/RX select: HIGH=TX, LOW=RX/LNA) — V4.3 onward. + * V4.2 has NO CTX: its GC1109 is driven by TX_EN on GPIO46. Do not + * carry this line back to heltec_wifi_lora32_v4. + * + * LoRa SPI2: SCLK=9, MOSI=10, MISO=11, NSS=8 + * LoRa ctrl: DIO1=14, BUSY=13, RESET=12 + * FEM: POWER=7, CSD=2, CTX=5 + * Display I2C0: SDA=17, SCL=18, RST=21 + */ + +/dts-v1/; + +#include +#include "heltec_wifi_lora32_v4_r8-pinctrl.dtsi" +#include +#include +#include +#include +#include +#include + +/* Module flash/PSRAM, declared by the board since Zephyr 4.5 removed the + * per-module dtsi (esp32s3_wroom_n16r8.dtsi). The R8 is the 8 MB octal part, + * which is what distinguishes it from the V4/V4.3 S3R2 at 2 MB quad. */ +&flash0 { + reg = <0x0 DT_SIZE_M(16)>; + ranges = <0x0 0x0 DT_SIZE_M(16)>; +}; + +&psram0 { + size = ; +}; + +/ { + model = "Heltec WiFi LoRa 32 V4-R8 PROCPU"; + compatible = "heltec,wifi-lora32-v4-r8"; + + aliases { + led0 = &led0; + pwm-0 = &ledc0; + pwm-led0 = &pwm_led_white; + uart-0 = &uart0; + i2c-0 = &i2c0; + lora0 = &lora0; + sw0 = &button0; + watchdog0 = &wdt0; + }; + + leds { + compatible = "gpio-leds"; + led0: led { + gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>; /* GPIO46 = gpio1 pin 14 */ + label = "White LED"; + }; + }; + + pwmleds { + compatible = "pwm-leds"; + pwm_led_white: pwm_led_gpio46 { + label = "White PWM LED"; + pwms = <&ledc0 0 PWM_MSEC(10) PWM_POLARITY_NORMAL>; + }; + }; + + buttons: buttons { + compatible = "gpio-keys"; + button0: button_0 { + gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + label = "USER Key"; + zephyr,code = ; + }; + }; + + /* No 'vbatt' voltage-divider node: ZephCore reads the battery via + * zephyr,user { io-channels } (see board.overlay), and a voltage-divider + * on &adc1 ch0 would target GPIO11 = the SX1262 MISO. See &adc0 below. */ + + /* Vext rail switch — PMOS, ACTIVE LOW (GPIO40 low = rail ON). + * Powers the OLED (incl. its I2C pull-ups) and the expansion header. + * + * Same rail circuit as V4.2/V4.3, moved from GPIO36 to GPIO40 by the + * octal-PSRAM pin reshuffle (GPIO33-37 are the PSRAM pads on an S3R8). + * + * Arduino MeshCore drives this revision the same way, and unlike the plain + * V4 it actually asserts the pin: variants/heltec_v4_r8 sets PIN_VEXT_EN=40 + * with PIN_VEXT_EN_ACTIVE=LOW, and its target.cpp constructs the display + * with &board.periph_power, so RefCountedDigitalPin::claim() drives the pin + * to the active level. (The plain V4 passes NULL there, so its pin only ever + * sits at the !active level begin() set -- which is why its ACTIVE=HIGH + * constant never shows up as a difference.) + * + * Confirmed on hardware here: with this regulator holding GPIO40 low, the + * SSD1306 acknowledges at 0x3c, which it cannot do on an unpowered rail. + * + * regulator-boot-on + always-on: the rail comes up at regulator init, + * well before the SSD1306 driver init. */ + vext_enable: vext-enable { + compatible = "regulator-fixed"; + regulator-name = "vext"; + enable-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>; /* GPIO40 = gpio1 pin 8 */ + regulator-boot-on; + regulator-always-on; + }; + + /* No adc_ctrl power-domain node: ADC_Ctrl is removed on the R8 and GPIO37 + * (gpio1 pin 5) is an octal-PSRAM pad, which must not be driven at all. + * There is no vbat-enable regulator either -- the divider is permanently + * connected, so ZephyrBoard's regulator lookup degrades to NULL by + * design. See board.overlay's battery-ADC block. */ + + /* FEM LDO power supply — GPIO7 powers the KCT8103L LDO. + * Must be HIGH before any radio operation. Keep always-on. */ + fem_power: fem-power { + compatible = "regulator-fixed"; + regulator-name = "fem-power"; + enable-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; /* VFEM_Ctrl = GPIO7 */ + regulator-boot-on; + regulator-always-on; + }; + + chosen { + zephyr,sram = &sram1; + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + zephyr,flash = &flash0; + zephyr,code-partition = &slot0_partition; + zephyr,bt-hci = &esp32_bt_hci; + zephyr,display = &ssd1306_128x64; + }; +}; + +/* Battery ADC on adc0 (ADC unit 1 -> GPIO1 = ADC1_CH0). The node *labelled* + * adc0 is unit 1; the node labelled adc1 is unit 2 (= GPIO11 = SX1262 MISO). + * The channel@0 config is required — without it adc_channel_setup_dt() fails and + * getBattMilliVolts() returns 0 before ever performing a conversion. ADC_GAIN_1 + * (0 dB) with ADC_REF_INTERNAL; the 390K/100K divider output for a 3.0-4.2 V + * cell sits well inside range. + * + * The SoC's true 0 dB span does not enter the battery maths: Zephyr's + * adc_esp32 driver calibrates the hardware raw and then re-projects it onto a + * fixed 1100 mV / 4095 scale before the application sees it, so the value + * board.overlay's vbat-mv-multiplier scales is already on that scale whatever + * the silicon does. That file owns the number and states its derivation. */ +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; + +/* ADC unit 2 (label adc1) is unused; keep it disabled so nothing can claim + * GPIO11 (SX1262 MISO) as an ADC pad. */ +&adc1 { + status = "disabled"; +}; + +&uart0 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&uart0_default>; + pinctrl-names = "default"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&ledc0 { + pinctrl-0 = <&ledc0_default>; + pinctrl-names = "default"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel0@0 { + reg = <0x0>; + timer = <0>; + }; +}; + +&i2c0 { + status = "okay"; + clock-frequency = ; + pinctrl-0 = <&i2c0_default>; + pinctrl-names = "default"; + + ssd1306_128x64: display-controller@3c { + compatible = "solomon,ssd1306"; + reg = <0x3c>; + width = <128>; + height = <64>; + segment-offset = <0>; + page-offset = <0>; + display-offset = <0>; + multiplex-ratio = <63>; + segment-remap; + com-invdir; + inversion-on; + prechargep = <0x22>; + reset-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>; /* RST_OLED = GPIO21 */ + vin-supply = <&vext_enable>; + }; +}; + +&spi2 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + pinctrl-0 = <&spim2_default>; + pinctrl-names = "default"; + + cs-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; /* NSS = GPIO8 */ + + lora0: modem@0 { + compatible = "semtech,sx1262"; + reg = <0>; + reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; /* RESET = GPIO12 */ + busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; /* BUSY = GPIO13 */ + dio1-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; /* DIO1 = GPIO14 */ + + /* External PA/FEM control (KCT8103L, V4.3): + * GPIO2 = CSD (chip enable): driven HIGH during radio, LOW in sleep + * GPIO5 = CTX (receive-path select): LOW = through the LNA, + * HIGH = bypass. Held HIGH during TX, where the branch + * is inert because DIO2/CPS has selected the TX path. + * V4.3 CHANGE: CTX moved from GPIO46 (GC1109 CPS) to GPIO5 (KCT8103L CTX). + * CTX is lna-bypass-gpios, NOT tx-enable-gpios: it enables nothing, + * and binding it as a TX enable is what made `set radio.fem.rxgain + * off` gate CSD instead in 1.17.2, shutting the FEM down and taking + * the receive path with it (~69 dB, measured). */ + /* + * ESP32_GPIO_SLEEP_HOLD_EN on both: light sleep powers down + * RTC_PERIPH (see boards/common/pm_esp32.overlay for the full + * chain), and GPIO2/GPIO5 are RTC-capable pads (0-21), so their + * state is not retained across a sleep unless explicitly held. + * These two lines are the FEM's entire control surface — CSD low + * disables the PA and the LNA with it, CTX high bypasses the LNA — + * so losing them means transmitting through a bypassed PA, tens of + * dB down, with RX sensitivity gone too, while the node otherwise + * looks healthy and keeps relaying. + * The flag is ignored when CONFIG_PM=n, so this is inert on + * builds that never light-sleep. + */ + antenna-enable-gpios = <&gpio0 2 (GPIO_ACTIVE_HIGH | + ESP32_GPIO_SLEEP_HOLD_EN)>; /* KCT8103L CSD = GPIO2 */ + lna-bypass-gpios = <&gpio0 5 (GPIO_ACTIVE_HIGH | + ESP32_GPIO_SLEEP_HOLD_EN)>; /* KCT8103L CTX = GPIO5 */ + + /* DIO2 drives the SX1262 RF switch output, which on a KCT8103L + * board is the part's CPS pin — the TX/RX path select. MeshCore's + * heltec_v4 target defines CSD=2 and CTX=5 for the KCT8103L, no + * CPS GPIO, and SX126X_DIO2_AS_RF_SWITCH=true; same on t096 and + * tracker_v2. Note the V4.2 GC1109 inverts these two pin names — + * there CPS is GPIO46 (an MCU pin, see heltec_wifi_lora32_v4) and + * DIO2 drives the pin that part calls CTX. On the KCT8103L, DIO2 + * doing the path select is what leaves CTX free to mean nothing + * but LNA-or-bypass. */ + dio2-tx-enable; + + /* TCXO 1.8V via DIO3 — same as V4.2 */ + dio3-tcxo-voltage = ; + tcxo-power-startup-delay-ms = <5>; + + spi-max-frequency = <4000000>; + }; +}; + +&timer0 { status = "okay"; }; +&timer1 { status = "okay"; }; +&timer2 { status = "okay"; }; +&timer3 { status = "okay"; }; + +&wdt0 { status = "okay"; }; +&trng0 { status = "okay"; }; + +&esp32_bt_hci { status = "okay"; }; +&wifi { status = "okay"; }; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu_defconfig b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu_defconfig new file mode 100644 index 0000000..aa3683b --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/heltec_wifi_lora32_v4_r8_procpu_defconfig @@ -0,0 +1,12 @@ +# Heltec WiFi LoRa 32 V4-R8 — minimal Zephyr defconfig (procpu) +CONFIG_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_CONSOLE=y +CONFIG_GPIO=y +CONFIG_CLOCK_CONTROL=y +CONFIG_PINCTRL=y +CONFIG_SPI=y +CONFIG_I2C=y +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/partitions.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/partitions.overlay new file mode 100644 index 0000000..d3b98bd --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4_r8/partitions.overlay @@ -0,0 +1,56 @@ +/* + * Flash partition layout — SHARED by the app image and MCUboot. + * + * Fed to BOTH images (app via EXTRA_DTC_OVERLAY_FILE, MCUboot via + * mcuboot_EXTRA_DTC_OVERLAY_FILE in sysbuild/CMakeLists.txt) so their flash maps + * are identical — otherwise MCUboot stages/looks for the OTA image at a different + * address than the app writes it to and OTA silently reverts on reboot. + * + * slot0 starts at 0x10000 (right after the 64KB MCUboot region), matching the + * Arduino/ESP-IDF app offset (Mesh America "flash-update" compatible). sys_partition + * is reclaimed. Slots are equal size and slot1 ends exactly on the unchanged + * storage/lfs offsets, so a -merged reflash keeps identity/prefs/contacts/BLE-bonds. + * + * 16MB layout. + */ +/delete-node/ &sys_partition; +/delete-node/ &slot0_lpcore_partition; +/delete-node/ &slot1_lpcore_partition; +/delete-node/ &storage_partition; +/delete-node/ &coredump_partition; +/delete-node/ &slot0_partition; +/delete-node/ &slot1_partition; + +/ { + chosen { + zephyr,settings-partition = &storage_partition; + }; +}; + +&flash0 { + partitions { + /* App slots — equal size (swap-mode compatible), slot0 at 0x10000. */ + slot0_partition: partition@10000 { + label = "image-0"; + compatible = "zephyr,mapped-partition"; + reg = <0x10000 0x7C6000>; + }; + slot1_partition: partition@7D6000 { + label = "image-1"; + compatible = "zephyr,mapped-partition"; + reg = <0x7D6000 0x7C6000>; + }; + /* BLE bond NVS — offset unchanged so bonds survive the reflash. */ + storage_partition: partition@F9C000 { + label = "storage"; + compatible = "zephyr,mapped-partition"; + reg = <0xF9C000 0x4000>; + }; + /* LittleFS (identity/prefs/contacts) — offset unchanged. */ + lfs_partition: partition@FA0000 { + label = "lfs"; + compatible = "zephyr,mapped-partition"; + reg = <0xFA0000 0x60000>; + }; + }; +};