Skip to content

Added support for Heltec Wireless Stick Lite V3 (WSL V3)#35

Open
slack-t wants to merge 13 commits into
attermann:masterfrom
slack-t:master
Open

Added support for Heltec Wireless Stick Lite V3 (WSL V3)#35
slack-t wants to merge 13 commits into
attermann:masterfrom
slack-t:master

Conversation

@slack-t
Copy link
Copy Markdown

@slack-t slack-t commented Apr 14, 2026

I´ve been using this on my WSL v3 for a couple of weeks now and it seems to be fine.
I'd appreciate if someone could review my changes. I used Claude Code for help as I was getting very rusty with Arduino based projects.

Thanks! Keep up the great work!

l33chy and others added 13 commits March 24, 2026 15:51
Add build environment for the HTIT-WSL_V3, which is hardware-identical
to the Heltec WiFi LoRa 32 V3 but lacks an OLED display. Uses a
NO_DISPLAY build flag to conditionally disable HAS_DISPLAY in the V3
board block. Also adds missing heltec32v3 provisioning case to
extra_script.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds full support for the Seeed XIAO nRF52840 with the Wio-SX1262 expansion
board, including TNC (standalone transport) and HOST (KISS modem) modes.

Board support:
- Boards.h: BOARD_XIAO_NRF52840 (0x52), pin mapping, TCXO, RF switch,
  active-low LED, VALIDATE_FIRMWARE disabled (Seeed bootloader incompatibility)
- platformio.ini: xiao_nrf52840 and xiao_nrf52840_lowpower environments using
  Seeed platform (https://github.com/Seeed-Studio/platform-seeedboards.git)
- extra_script.py: is_nrf52_platform() helper covers seeedboards; xiao env
  provisioned with RAK4631 product/model bytes until dedicated ID exists
- LowPower.h/cpp: runtime power management (PERFORMANCE / BALANCED / LOW_POWER)
- Utilities.h: XIAO LED functions, eeprom_model_valid() XIAO case, written_bytes
  initialised to 0 (was 4, causing spurious EEPROM flush), eeprom_conf_save()
  no longer gated on radio_online

CSMA bug fixes (affected TNC mode on all SX1262 boards):
- RNode_Firmware.ino medium_free(): added update_noise_floor() call alongside
  update_modem_status(). In TNC mode, queue_height>0 causes medium_free() to
  run every loop iteration, continuously refreshing last_status_update and
  starving check_modem_status() — the only previous caller of
  update_noise_floor(). With noise_floor stuck at -292, interference_detected
  was always true (any real RSSI > -281 dBm), blocking TX permanently and
  preventing led_rx_off() from ever being called.
- sx126x.cpp dcd(): clear PREAMBLE_DET IRQ bit immediately on detection rather
  than after a 147 ms timeout. The bit is not routed to DIO1, so it was never
  cleared by handleDio0Rise(); under high-frequency CSMA polling spurious
  detections kept dcd=true. Software timer preserves the correct busy window;
  header detection resets the timer to prevent expiry mid-reception.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Supported Hardware section covering pin mapping, PlatformIO environments,
build/flash procedure (DFU bootloader), rnodeconf configuration commands, and
known-limitations notes (VALIDATE_FIRMWARE, provisional product ID).

Update PlatformIO command line examples to include xiao_nrf52840 environment.
Update roadmap to reflect completed power management and new board support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Errata 15.4: SetPacketParams resets register 0x0736 to an incorrect IQ
polarity default. Bit 2 must be SET for standard IQ after every
SetPacketParams call, otherwise LoRa RX fails silently while TX works.

Errata 15.1: Register 0x0889 bit 2 must be cleared for 500 kHz BW and
set for all other bandwidths. The previous implementation was a no-op stub.

Ported from markqvist/RNode_Firmware@ae04347 (PR markqvist#132 by GlassOnTin).
Applies to all SX1262-based boards in this fork.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Downloads the latest release zip from GitHub, flashes via
adafruit-nrfutil DFU, provisions EEPROM, and writes the firmware hash.
Requires adafruit-nrfutil and rnodeconf on PATH.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…P, v1.86

- OCP_TUNED raised 0x18 → 0x28 across all boards (T-Beam SX1262, Heltec
  V2/V3/V4, T-Beam Supreme, global default) to match upstream headroom.

- Heltec32 V4: runtime PA model detection (GC1109 vs KCT8103L) by probing
  LORA_PA_CSD at boot. Each PA gets its own gain table and LNA gain value
  (GC1109: 17 dB, KCT8103L: 21 dB). Replaces the previous compile-time
  LORA_PA_GC1109 flag with runtime lora_pa_model checks in begin(),
  beginPacket(), and receive().

- Heltec T114: turn TFT backlight off when display blanks, on when active.

- Version bumped 1.85 → 1.86 to match upstream.

Ported from markqvist/RNode_Firmware commits 9fd0ae3, fe594b2, 0c07c1b.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…L_V3 0x43)

Hardware is identical to Heltec LoRa32 V3 (ESP32-S3 + SX1262 + TCXO) but
without the OLED display. Defined as a proper separate board rather than
using BOARD_HELTEC32_V3 + -DNO_DISPLAY, following the same pattern as all
other boards in this firmware.

Changes:
- Boards.h: add BOARD_HELTEC_WSL_V3 constant (0x43) and full board config
  section with HAS_DISPLAY false and identical pin/modem/TCXO definitions
- sx126x.cpp: add BOARD_HELTEC_WSL_V3 to TCXO 3.3V branch in enableTCXO()
- Utilities.h: add BOARD_HELTEC_WSL_V3 to LED function definitions
- platformio.ini: switch env to -DBOARD_MODEL=BOARD_HELTEC_WSL_V3,
  remove -DNO_DISPLAY, fix stale DUSTORE_* flags to RNS_PATH_TABLE_*
- extra_script.py: add heltec32v3_wsl variant to provisioning (uses same
  rnodeconf product/model as V3: 0xC1/0xCA — identical hardware)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BOARD_HELTEC_WSL_V3 was missing from several per-board dispatch chains,
causing the radio to stay offline:

- sx126x.cpp preInit(): WSL V3 fell through to bare SPI.begin() without
  explicit pin arguments, so SX1262 SPI bus never initialised and the
  sync word check failed → modem_installed = false
- Utilities.h eeprom_model_valid(): no case for WSL V3, so always returned
  false → "Invalid EEPROM configuration" printed at boot, hw_ready never set
- Utilities.h eeprom_model_valid(): combined with BOARD_HELTEC32_V3 since
  both accept MODEL_C5 / MODEL_CA (identical provisioning bytes)

Also add WSL V3 to Power.h battery/PMU init (same pins and ADC scaling
as V3: pin_vbat=1, pin_ctrl=37, measurement factor 0.0041).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Adafruit nRF52 BSP hard-codes the LittleFS region to 7 pages (28 KB),
which severely constrains the microReticulum path table and microStore
segments on nRF52840-based boards.  This commit lifts that limit to 32
pages (128 KB) for the XIAO nRF52840 envs without touching any other board.

## extra_script.py — pre-build InternalFS patcher

Adds patch_internalfs_for_xiao(env), called from the nRF52 env-setup block
at SCons script-import time (before any source is compiled).  For the two
XIAO variants it locates InternalFileSystem.cpp inside the PlatformIO
framework-arduinoadafruitnrf52 package and rewrites two #define lines:

  LFS_FLASH_ADDR:       0xED000 → 0xD4000   (region base, nRF52840 branch only)
  LFS_FLASH_TOTAL_SIZE: 7 pages → 32 pages   (28 KB → 128 KB)

The patch is idempotent (regex-anchored; skips if already applied) and backs
up the original file to InternalFileSystem.cpp.xiao.orig on first run.  A
RuntimeError is raised if the regex matches nothing and the file looks
unpatched, so a BSP upstream change fails loudly rather than silently
shipping a 28 KB FS.  Region size is overridable per-env via the build flag
-DXIAO_INTERNALFS_PAGES=N (supported: 7, 16, 32, 64).

nRF52840 flash map after the patch (SoftDevice S140 v7.3.0):
  0x00000–0x01000  MBR            4 KB
  0x01000–0x27000  SoftDevice   152 KB
  0x27000–0xD4000  Application  692 KB   (was 792 KB; 100 KB donated to FS)
  0xD4000–0xF4000  LittleFS     128 KB   (was 28 KB)
  0xF4000–0x100000 Bootloader    48 KB   (Seeed/Adafruit — untouched)

No changes in Utilities.h, RNode_Firmware.ino, Boards.h or microStore;
all InternalFS.* call sites continue to work via the patched _InternalFSConfig.

Also bumps the post-DFU sleep from 5 s to 12 s so the new firmware has time
to mount (or format) the 128 KB FS before rnodeconf connects for provisioning.

## platformio.ini — XIAO env updates (both xiao_nrf52840 and xiao_nrf52840_lowpower)

- upload_port = /dev/ttyACM0
    Pins the upload port explicitly, bypassing a crash in PlatformIO's
    HWID auto-detection (board_config NoneType bug in platformio/device/finder.py
    triggered by the Seeed platform's board JSON lacking hwids for 0x2886:0x0045,
    the VID:PID the XIAO presents in normal mode).  The board JSON's
    use_1200bps_touch=true still fires automatically to trigger DFU.

- -DXIAO_INTERNALFS_PAGES=32  (replaces dead -DURTN_PATH_TABLE_MAX_RECS=25)
    Documents the intended FS size explicitly and drives the patcher.
    URTN_PATH_TABLE_MAX_RECS was never defined in the microReticulum source;
    it was a no-op carried over from the varna9000 fork.

- -DRNS_PATH_TABLE_MAX=100  (replaces the dead flag; makes the cap explicit)
    RNS_PATH_TABLE_MAX is the actual define in Transport.cpp (default 100).
    128 KB of flash comfortably holds 100 persisted path entries; RAM usage
    at boot is ~42 KB (17.6% of 237 KB), leaving ample headroom.

## .gitignore — exclude local tooling files

Adds CLAUDE.md, AGENTS.md, .claude/, implementation.md so that AI assistant
context files and design notes are never accidentally staged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Flash storage and RNS persistence" subsection under the XIAO
hardware section explaining the 28 KB BSP limitation, the pre-build
patcher approach, the resulting flash map, and how to verify the 128 KB
region is mounted at runtime.  Also documents the upload_port workaround
for PlatformIO's HWID auto-detection crash on the Seeed platform.

Adds -DXIAO_INTERNALFS_PAGES and -DRNS_PATH_TABLE_MAX to the Build
Options section.  Updates the Roadmap to mark the storage work done and
add Option 2 (QSPI flash) as a future item.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RNode_Firmware.ino:642 calls Transport::path_table_maxsize(URTN_PATH_TABLE_MAX_RECS)
directly at startup, overriding the static initializer that RNS_PATH_TABLE_MAX
controls. The previous commit used the wrong define; this corrects both envs
(xiao_nrf52840, xiao_nrf52840_lowpower) and the README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove f-strings and !!! print prefix from patch_internalfs_for_xiao()
to match Attermann upstream conventions. Replace !!! with --- WARNING:
prefix or raise for fatal paths. Remove hardcoded upload_port from both
XIAO envs in platformio.ini — port is workstation-specific.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nilu96
Copy link
Copy Markdown

nilu96 commented May 8, 2026

Thanks for sharing how you enlarged the InternalFS to more than 28 KB! A little hacky, but definitely a workaround for now. This method also works fine on Heltec T114. Maybe this could be added for all nrf52-based boards.

Did you have any problems with not having enough space for your program code with 32 pages reserved for InternalFS? PlatformIO still uses default values for its build summary (at least for me), so this info might not help to evaluate if there is enough space. Here is my memory summary from PIO:
RAM: [== ] 18.7% (used 46436 bytes from 248832 bytes)
Flash: [======== ] 83.6% (used 681288 bytes from 815104 bytes)

@slack-t
Copy link
Copy Markdown
Author

slack-t commented May 8, 2026

Thank you! Yeah it is extremely hacky... so if anyone has a better idea to workaround this limitation, I'd be very thankful!

No, so far I haven't run into space issues, but it's extremely tight... Also note that the pio flash numbers are lying to your face:
The S140 v6 linker script the Adafruit nrf52 framework uses has:
FLASH: ORIGIN = 0x26000, LENGTH = 0xED000 - 0x26000 = 815,104 bytes
The linker thus assumes that the default 7-page (28KB) InternalFS boundary is at 0xED000 and so it reports against that fixed ceiling. The build system has no knowledge of this InternalFS hack, it always reports against that fixed 0xED000 ceiling.

With 32 pages (128KB) InternalFS, you'd end up with roughly 31KB of headroom instead of 131KB! If your firmware.bin is exceeding the 0xD4000 - 0x26000 = 0xAE000 = 712,704 bytes, it won't warn you during the build and overwrite InternalFS... The build for my XIAO nrf52 is about 577KB so I have some headroom left, maybe it's larger for your T114 due to the display libs...

I'd suggest, if you're getting too close to the 712,704 bytes ceiling to drop the pages to 16 (XIAO_INTERNALFS_PAGES), so it'll be 64KB (it's still more than the stock 28KB FS size...), just to be safe.

That's interesting... the patch should be xiao-specific and not apply to the T114 env, or did you first ran a build for the Xiao and then for the T114? I think a subsequent build would inherit the patch from the previous xiao build 😅 I probably need to put more work into this...

@nilu96
Copy link
Copy Markdown

nilu96 commented May 8, 2026

Hi, thanks for your further explanation! And sorry for the confusion – I did not run your exact same script, which is indeed should be specific for xiao, but I used the same approach to extend the InternalFS storage for my T114.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants