ditch arduino's hacky-hacky FS, back to zephyr native

mg24 fix
dfu zip generation fix
This commit is contained in:
liquidraver
2026-03-03 13:46:48 +01:00
parent 6cb80a31af
commit 57d8cf1c46
27 changed files with 572 additions and 175 deletions
+1 -2
View File
@@ -171,7 +171,7 @@ set(ZEPHCORE_COMMON_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/zephcore_com
# Detect platform from board name and add platform-specific common config
# For boards with qualifiers like "wio_tracker_l1/nrf52840", check both the full BOARD
# string and the BOARD_QUALIFIERS which contains just the qualifier (e.g., "nrf52840")
if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1")
if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wismesh_tag" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf")
elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*nrf52.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf")
@@ -396,7 +396,6 @@ else()
src/main_companion.cpp
adapters/ble/ZephyrBLE.cpp
adapters/datastore/ZephyrDataStore.cpp
adapters/datastore/lfs_128b_erase.c
helpers/BaseChatMesh.cpp
helpers/TransportKeyStore.cpp
helpers/ui/ui_mesh_actions.cpp
+6 -6
View File
@@ -443,14 +443,14 @@ config ZEPHCORE_UI_BUZZER
config ZEPHCORE_UI_DISPLAY
bool "Enable display (OLED, LCD, e-ink)"
default y
depends on DISPLAY
depends on CHARACTER_FRAMEBUFFER
default y if $(dt_chosen_enabled,zephyr/display)
select DISPLAY
select CHARACTER_FRAMEBUFFER
help
Enable page-based UI on any Zephyr-supported display.
Auto-detects display via "zephyr,display" chosen node or
legacy nodelabels (sh1106, ssd1306). Resolution and font
are queried at runtime — no hardcoded dimensions.
Auto-enabled when "zephyr,display" chosen node exists in DT.
Auto-detects display via chosen node or legacy nodelabels
(sh1106, ssd1306). Resolution and font are queried at runtime.
Supports OLED (SSD1306, SH1106), LCD, e-ink, etc.
config ZEPHCORE_UI_DISPLAY_AUTO_OFF_MS
+50 -63
View File
@@ -4,14 +4,14 @@
*
* Universal mount strategy:
* nRF52: Manual mount /efs (ExtraFS, 100KB) + /ifs (InternalFS, 28KB)
* with block_size=128 and custom erase for Arduino MeshCore compat.
* using native Zephyr LittleFS (block_size=4096).
* Others: DTS-automounted /lfs (single partition).
* QSPI: /ext overrides contacts mount when available.
*
* Data format (all platforms):
* contacts3 — 152-byte records (Arduino-compatible)
* new_prefs — Arduino-compatible byte layout
* channels2 — 68-byte records (identical to Arduino)
* contacts3 — 152-byte records
* new_prefs — byte layout
* channels2 — 68-byte records
* _main.id — identity blob
* adv_blobs — fixed record array
*/
@@ -23,9 +23,6 @@
#include <zephyr/device.h>
#include <string.h>
/* Custom 128B-block erase — defined in lfs_128b_erase.c */
extern "C" int lfs_128b_erase(const struct lfs_config *c, lfs_block_t block);
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephcore_datastore, CONFIG_ZEPHCORE_DATASTORE_LOG_LEVEL);
@@ -56,27 +53,14 @@ static bool is_mounted(const char *mount_point)
return fs_statvfs(mount_point, &stat) == 0;
}
/* ── nRF52 dual-mount: ExtraFS + InternalFS (block_size=128) ───────── */
/* ── nRF52 dual-mount: ExtraFS + InternalFS ────────────────────────── */
#if FIXED_PARTITION_EXISTS(extrafs_partition) && FIXED_PARTITION_EXISTS(internalfs_partition)
#define HAS_NRF52_DUAL_MOUNT 1
/* Arduino-compatible LFS: block_size=128 */
FS_LITTLEFS_DECLARE_CUSTOM_CONFIG(extrafs_data,
4, /* alignment */
16, /* read_size */
16, /* prog_size */
128, /* cache_size = block_size */
128 /* lookahead_size */
);
FS_LITTLEFS_DECLARE_CUSTOM_CONFIG(internalfs_data,
4, /* alignment */
16, /* read_size */
16, /* prog_size */
128, /* cache_size = block_size */
128 /* lookahead_size */
);
/* Native Zephyr LFS block_size derived from flash erase size (4096). */
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(extrafs_data);
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(internalfs_data);
static struct fs_mount_t extrafs_mnt = {
.type = FS_LITTLEFS,
@@ -94,53 +78,56 @@ static struct fs_mount_t internalfs_mnt = {
.flags = 0,
};
/* Mount a LittleFS partition, format if mount fails. Returns the mount rc. */
static int mount_or_format(struct fs_mount_t *mnt, int partition_id)
{
int rc = fs_mount(mnt);
if (rc < 0) {
LOG_WRN("%s: mount failed (%d) — formatting", mnt->mnt_point, rc);
/* A failed fs_mount may leave fs->backend set (flash_area
* opened in littlefs_init_backend but lfs_mount failed).
* Clear it so the retry doesn't hit -EBUSY. */
struct fs_littlefs *fsd = (struct fs_littlefs *)mnt->fs_data;
if (fsd->backend) {
flash_area_close((const struct flash_area *)fsd->backend);
fsd->backend = NULL;
}
const struct flash_area *fap;
if (flash_area_open(partition_id, &fap) == 0) {
flash_area_flatten(fap, 0, fap->fa_size);
flash_area_close(fap);
}
rc = fs_mount(mnt);
if (rc < 0) {
LOG_ERR("%s: mount failed after format: %d",
mnt->mnt_point, rc);
}
}
return rc;
}
static bool try_mount_nrf52_dual()
{
int rc;
/* Set custom block_size and erase callback before mount.
* The Zephyr LFS patch (0007) preserves pre-set erase callbacks. */
extrafs_data.cfg.block_size = 128;
extrafs_data.cfg.erase = lfs_128b_erase;
internalfs_data.cfg.block_size = 128;
internalfs_data.cfg.erase = lfs_128b_erase;
/* Mount ExtraFS — contacts, channels, blobs */
rc = fs_mount(&extrafs_mnt);
if (rc < 0) {
LOG_ERR("ExtraFS mount failed: %d — formatting", rc);
const struct flash_area *fap;
if (flash_area_open(FIXED_PARTITION_ID(extrafs_partition), &fap) == 0) {
flash_area_flatten(fap, 0, fap->fa_size);
flash_area_close(fap);
}
rc = fs_mount(&extrafs_mnt);
if (rc < 0) {
LOG_ERR("ExtraFS mount failed after format: %d", rc);
return false;
}
if (mount_or_format(&extrafs_mnt,
FIXED_PARTITION_ID(extrafs_partition)) < 0) {
return false;
}
efs_mounted = true;
LOG_INF("ExtraFS at /efs (100KB, 128B blocks)");
LOG_INF("ExtraFS at /efs: blk_sz=%u blk_cnt=%u",
extrafs_data.cfg.block_size, extrafs_data.cfg.block_count);
/* Mount InternalFS — prefs, identity, BLE settings */
rc = fs_mount(&internalfs_mnt);
if (rc < 0) {
LOG_ERR("InternalFS mount failed: %d — formatting", rc);
const struct flash_area *fap;
if (flash_area_open(FIXED_PARTITION_ID(internalfs_partition), &fap) == 0) {
flash_area_flatten(fap, 0, fap->fa_size);
flash_area_close(fap);
}
rc = fs_mount(&internalfs_mnt);
if (rc < 0) {
LOG_ERR("InternalFS mount failed after format: %d", rc);
return false;
}
if (mount_or_format(&internalfs_mnt,
FIXED_PARTITION_ID(internalfs_partition)) < 0) {
return false;
}
ifs_mounted = true;
LOG_INF("InternalFS at /ifs (28KB, 128B blocks)");
LOG_INF("InternalFS at /ifs: blk_sz=%u blk_cnt=%u",
internalfs_data.cfg.block_size, internalfs_data.cfg.block_count);
return true;
}
@@ -158,7 +145,7 @@ bool ZephyrDataStore::mount()
return true; /* already mounted */
}
/* 1. Try nRF52 dual-mount (manual, block_size=128) */
/* 1. Try nRF52 dual-mount (manual, native block_size) */
if (try_mount_nrf52_dual()) {
_contacts_mnt = "/efs";
_prefs_mnt = "/ifs";
@@ -3,9 +3,9 @@
* Zephyr DataStore - LittleFS-backed persistence with optional QSPI flash
*
* Universal across all platforms (nRF52, ESP32, MG24, nRF54L).
* On nRF52, uses Arduino MeshCore-compatible dual-mount layout:
* /efs (ExtraFS @ 0xD4000, 100KB, block_size=128) — contacts3, channels2, blobs
* /ifs (InternalFS @ 0xED000, 28KB, block_size=128) — new_prefs, _main.id
* On nRF52, uses dual-mount layout with native Zephyr LittleFS:
* /efs (ExtraFS @ 0xD4000, 100KB) — contacts3, channels2, blobs
* /ifs (InternalFS @ 0xED000, 28KB) — new_prefs, _main.id
* On other platforms, uses DTS-automounted /lfs for everything.
* QSPI /ext overrides contacts path when available (any platform).
*/
@@ -1,64 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Custom LittleFS erase callback for 128-byte blocks on 4KB-erase flash.
*
* Arduino MeshCore uses LFS with block_size=128 on nRF52840 which has
* 4096-byte erase granularity. The standard Zephyr erase callback calls
* flash_area_flatten(fa, offset, 128) which fails because the flash driver
* requires page-aligned (4096-byte) erases.
*
* This callback implements read-modify-erase-write:
* 1. Read the entire 4KB page containing the 128B block
* 2. Memset the 128B region to 0xFF (erased state)
* 3. Hardware-erase the 4KB page
* 4. Write back the modified page
*/
#include <zephyr/storage/flash_map.h>
#include <zephyr/drivers/flash.h>
#include <lfs.h>
#include <string.h>
#define HW_ERASE_SIZE 4096
/* Static 4KB buffer for read-modify-erase-write.
* Both ExtraFS and InternalFS mounts are serialized by the Zephyr FS mutex,
* so this is never accessed concurrently. */
static uint8_t page_buf[HW_ERASE_SIZE];
int lfs_128b_erase(const struct lfs_config *c, lfs_block_t block)
{
const struct flash_area *fa = (const struct flash_area *)c->context;
int rc;
/* Offset of this LFS block within the flash area */
size_t block_offset = block * c->block_size;
/* Align down to the 4KB page boundary (within flash area) */
size_t page_offset = block_offset & ~((size_t)HW_ERASE_SIZE - 1);
size_t offset_in_page = block_offset - page_offset;
/* 1. Read entire 4KB page */
rc = flash_area_read(fa, page_offset, page_buf, HW_ERASE_SIZE);
if (rc < 0) {
return LFS_ERR_IO;
}
/* 2. Set the 128B block region to erased state (0xFF) */
memset(&page_buf[offset_in_page], 0xFF, c->block_size);
/* 3. Hardware-erase the 4KB page */
rc = flash_area_flatten(fa, page_offset, HW_ERASE_SIZE);
if (rc < 0) {
return LFS_ERR_IO;
}
/* 4. Write back the modified page */
rc = flash_area_write(fa, page_offset, page_buf, HW_ERASE_SIZE);
if (rc < 0) {
return LFS_ERR_IO;
}
return LFS_ERR_OK;
}
+8 -2
View File
@@ -267,12 +267,18 @@ void CompanionMesh::loop()
void CompanionMesh::markContactsDirty()
{
_dirty_contacts_expiry = _ms->getMillis() + LAZY_WRITE_DELAY_MS;
/* Only set the timer on first dirty — don't keep pushing
* the deadline forward or a busy mesh never flushes. */
if (!_dirty_contacts_expiry) {
_dirty_contacts_expiry = _ms->getMillis() + LAZY_WRITE_DELAY_MS;
}
}
void CompanionMesh::markChannelsDirty()
{
_dirty_channels_expiry = _ms->getMillis() + LAZY_WRITE_DELAY_MS;
if (!_dirty_channels_expiry) {
_dirty_channels_expiry = _ms->getMillis() + LAZY_WRITE_DELAY_MS;
}
}
void CompanionMesh::flushDirtyContacts()
+2 -2
View File
@@ -137,7 +137,7 @@ uint8_t RepeaterMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
if (perms != PERM_ACL_GUEST) {
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
if (!dirty_contacts_expiry) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
}
}
@@ -1018,7 +1018,7 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char*
if (hex_len > PUB_KEY_SIZE * 2) hex_len = PUB_KEY_SIZE * 2;
if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) {
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
if (!dirty_contacts_expiry) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - invalid params");
+2 -3
View File
@@ -35,10 +35,9 @@ CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE=y
# Boards with QSPI (Wio Tracker L1) override in board.conf.
CONFIG_NORDIC_QSPI_NOR=n
# ========== Storage: Arduino MeshCore compatible layout ==========
# ========== Storage ==========
# BLE bonds via file-based settings on InternalFS (replaces NVS).
# Required for Arduino MeshCore flash layout compatibility — the NVS region
# (0xD4000-0xD6000) is now part of ExtraFS.
# No NVS partition on nRF52 — ExtraFS occupies that region.
CONFIG_NVS=n
CONFIG_SETTINGS_NVS=n
CONFIG_SETTINGS_FILE=y
+4 -6
View File
@@ -39,12 +39,11 @@ CONFIG_LORA_MODULE_BACKEND_NATIVE=y
CONFIG_REGULATOR=y
# ========== Display Subsystem ==========
# When no display is in devicetree, no driver is instantiated — zero overhead.
CONFIG_DISPLAY=y
CONFIG_CHARACTER_FRAMEBUFFER=y
# Display and CFB are auto-enabled by ZEPHCORE_UI_DISPLAY (Kconfig select)
# when "zephyr,display" chosen node exists in the board's devicetree.
# No need to set CONFIG_DISPLAY or CONFIG_CHARACTER_FRAMEBUFFER here.
# Heap for CFB framebuffer allocation (cfb_framebuffer_init uses k_malloc).
# 2KB baseline — platforms with larger BLE stacks override this.
# Heap baseline — display boards override for larger framebuffers.
CONFIG_HEAP_MEM_POOL_SIZE=2048
# ========== Sensor Subsystem ==========
@@ -156,7 +155,6 @@ CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_NVS=y
CONFIG_SETTINGS_NVS_SECTOR_COUNT=2
CONFIG_FLASH_PAGE_LAYOUT=y
# LittleFS for DataStore
+2 -2
View File
@@ -105,11 +105,11 @@ CONFIG_BT_DIS_MODEL_NUMBER_STR="My Board v1"
# CRITICAL: SoftDevice version determines the app start address!
# Check your board's Arduino platformio.ini for which linker script it uses:
# nrf52840_s140_v6*.ld → s140 v6.1.1, app@0x26000, FWID=0x00B6
# nrf52840_s140_v7*.ld → s140 v7.3.0, app@0x27000, FWID=0x0100
# nrf52840_s140_v7*.ld → s140 v7.3.0, app@0x27000, FWID=0x0123
# Your board.overlay partition table MUST match (see Section 4 in board.overlay).
# Wrong address = bootloop / bricked device with no USB/BLE/logs.
# CONFIG_ZEPHCORE_SD_FWID=0x00B6 # SoftDevice firmware ID (s140 v6.1.1)
# CONFIG_ZEPHCORE_SD_FWID=0x0100 # SoftDevice firmware ID (s140 v7.3.0)
# CONFIG_ZEPHCORE_SD_FWID=0x0123 # SoftDevice firmware ID (s140 v7.3.0)
#
# --- MG24 (Silicon Labs) ---
# Requires: west blobs fetch hal_silabs (before first build)
@@ -82,6 +82,13 @@
*/
/* --- Display (uncomment if board has OLED/LCD/e-paper) ---
* Setting zephyr,display auto-enables CONFIG_DISPLAY,
* CONFIG_CHARACTER_FRAMEBUFFER, and the UI page system.
* No board.conf changes needed.
* HEAP: 128x64 OLEDs fit in the default 2KB heap.
* Larger displays need CONFIG_HEAP_MEM_POOL_SIZE
* override in board.conf (e.g. 6144 for 200x200 EPD).
*
* zephyr,display = &oled; I2C OLED (SH1106, SSD1306)
* zephyr,display = &epd; SPI e-paper (SSD1681, etc.)
*/
+1 -2
View File
@@ -8,5 +8,4 @@ CONFIG_ZEPHCORE_BOARD_NAME="XIAO MG24"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="XIAO MG24"
# RAM-limited: Silabs BLE blob needs 32KB heap, override prod.conf MAX_CONTACTS
CONFIG_ZEPHCORE_MAX_CONTACTS=650
# RAM-limited: Silabs BLE blob needs 32KB heap — use Kconfig default (350)
@@ -19,3 +19,6 @@ CONFIG_ZEPHCORE_BOARD_NAME="RAK 4631"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="RAK4631"
# SoftDevice firmware ID (s140 v6.1.1)
CONFIG_ZEPHCORE_SD_FWID=0x00B6
@@ -0,0 +1,6 @@
# RAK WisMesh Tag board configuration
# Copyright (c) 2025 ZephCore
# SPDX-License-Identifier: Apache-2.0
config BOARD_RAK_WISMESH_TAG
select SOC_NRF52840_QIAA
@@ -0,0 +1,13 @@
# Copyright (c) 2025 ZephCore
# SPDX-License-Identifier: Apache-2.0
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
@@ -0,0 +1,31 @@
# RAK WisMesh Tag (nRF52840 + SX1262 GPS Tracker)
# Board-specific configuration
#
# Hardware:
# - nRF52840 SoC, SX1262 LoRa (22dBm, TCXO 1.8V)
# - AT6558R GNSS on UART0 at 9600 baud
# - LIS2DH accelerometer on I2C0
# - 1000 mAh battery, ADC on AIN3 (P0.05)
# - Buzzer on P0.21, user button on P0.09
# - LEDs: GREEN=P1.03 (TX), BLUE=P1.04 (status)
# - No display, no QSPI flash
# - SoftDevice s140 v6.1.1, UF2 bootloader
# Board identification (matches Arduino MeshCore variant name)
CONFIG_ZEPHCORE_BOARD_NAME="RAK WisMesh Tag"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="RAK WisMesh Tag"
# SoftDevice firmware ID (s140 v6.1.1)
CONFIG_ZEPHCORE_SD_FWID=0x00B6
# No display — enable LED heartbeat instead
CONFIG_ZEPHCORE_UI_DISPLAY=n
# PWM for buzzer
CONFIG_PWM=y
# RX duty cycle — safe on SX1262, extends battery life on this
# card-sized tracker with 1000mAh battery
CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=y
@@ -0,0 +1,9 @@
# Copyright (c) 2025 ZephCore
# SPDX-License-Identifier: Apache-2.0
board:
name: rak_wismesh_tag
full_name: RAK WisMesh Tag (nRF52840 + SX1262 GPS Tracker)
vendor: rak
socs:
- name: nrf52840
@@ -0,0 +1,82 @@
/*
* RAK WisMesh Tag pin control definitions
* Copyright (c) 2025 ZephCore
*
* SPDX-License-Identifier: Apache-2.0
*
* Pin mappings (from Arduino MeshCore variant.h):
*
* SPI2 (LoRa SX1262): SCK=P1.11, MOSI=P1.12, MISO=P1.13, CS=P1.10
* I2C0 (sensors): SDA=P0.25, SCL=P0.24
* UART0 (GPS 9600): TX=P0.16 (GPS RX), RX=P0.15 (GPS TX)
* PWM0 (buzzer): P0.21
*/
&pinctrl {
/* UART0 for GPS module at 9600 baud */
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 16)>; /* P0.16 → GPS RX */
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 15)>; /* P0.15 ← GPS TX */
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 16)>,
<NRF_PSEL(UART_RX, 0, 15)>;
low-power-enable;
};
};
/* I2C0 for sensors (LIS2DH, environment sensors) */
i2c0_default: i2c0_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 25)>, /* P0.25 */
<NRF_PSEL(TWIM_SCL, 0, 24)>; /* P0.24 */
};
};
i2c0_sleep: i2c0_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 25)>,
<NRF_PSEL(TWIM_SCL, 0, 24)>;
low-power-enable;
};
};
/* SPI2 for LoRa SX1262 */
spi2_default: spi2_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 11)>, /* P1.11 (Arduino 43) */
<NRF_PSEL(SPIM_MOSI, 1, 12)>, /* P1.12 (Arduino 44) */
<NRF_PSEL(SPIM_MISO, 1, 13)>; /* P1.13 (Arduino 45) */
};
};
spi2_sleep: spi2_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 11)>,
<NRF_PSEL(SPIM_MOSI, 1, 12)>,
<NRF_PSEL(SPIM_MISO, 1, 13)>;
low-power-enable;
};
};
/* PWM0 for buzzer on P0.21 */
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 21)>;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 21)>;
low-power-enable;
};
};
};
@@ -0,0 +1,277 @@
/*
* RAK WisMesh Tag nRF52840 + SX1262 (card-sized GPS tracker)
* Copyright (c) 2025 ZephCore
*
* SPDX-License-Identifier: Apache-2.0
*
* Hardware:
* - nRF52840 SoC with BLE 5
* - SX1262 LoRa radio (DIO2 as RF switch, TCXO 1.8V via DIO3)
* - AT6558R GNSS on UART0 at 9600 baud
* - LIS2DH 3-axis accelerometer on I2C0
* - 1000 mAh battery with ADC on AIN3 (P0.05)
* - Buzzer on PWM0 (P0.21)
* - LEDs: GREEN=P1.03 (TX, active HIGH), BLUE=P1.04 (status, active HIGH)
* - User button on P0.09 (NFC1, active LOW)
* - Board power enable on P0.12
* - No display, no QSPI flash
* - IP66 sealed, pogo-pin USB interface
*
* Pin mappings (from Arduino MeshCore variant.h):
* - LoRa SPI: SCK=P1.11, MOSI=P1.12, MISO=P1.13, CS=P1.10
* - LoRa ctl: DIO1=P1.15, BUSY=P1.14, RESET=P1.06, PWR_EN=P1.05
* - I2C: SDA=P0.25, SCL=P0.24
* - GPS: TX=P0.16, RX=P0.15, EN=P1.02, PPS=P0.17
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "rak_wismesh_tag-pinctrl.dtsi"
#include <zephyr/dt-bindings/lora/sx126x.h>
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
/ {
model = "RAK WisMesh Tag";
compatible = "rak,wismesh-tag";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &code_partition;
zephyr,console = &cdc_acm_uart;
zephyr,shell-uart = &cdc_acm_uart;
};
aliases {
lora0 = &lora;
led0 = &led_green;
led1 = &led_blue;
lora-tx-led = &led_green;
sw0 = &user_button;
watchdog0 = &wdt0;
buzzer = &buzzer;
gps-enable = &gps_enable_pin;
};
/* ---- LEDs (active HIGH per Arduino LED_STATE_ON=HIGH) ---- */
leds {
compatible = "gpio-leds";
led_green: led_0 {
gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>;
label = "Green LED";
};
led_blue: led_1 {
gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
label = "Blue LED";
};
};
/* ---- Button (P0.09/NFC1, active LOW with pull-up) ---- */
buttons: buttons {
compatible = "gpio-keys";
user_button: button_0 {
gpios = <&gpio0 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
zephyr,code = <INPUT_KEY_0>;
label = "User Button";
};
};
/* Input filter: long-press (1000ms) */
user_btn_longpress {
compatible = "zephyr,input-longpress";
input = <&buttons>;
input-codes = <INPUT_KEY_0>;
short-codes = <INPUT_KEY_A>;
long-codes = <INPUT_KEY_POWER>;
long-delay-ms = <1000>;
};
/* Input filter: multi-tap
* 1 tap KEY_1 = (reserved / next page if display added)
* 2 taps KEY_B = flood advert
* 3 taps KEY_D = buzzer mute toggle
* 4 taps KEY_C = GPS on/off */
user_btn_multitap {
compatible = "zephcore,input-multi-tap";
input-codes = <INPUT_KEY_A>;
tap-codes = <INPUT_KEY_1 INPUT_KEY_B INPUT_KEY_D INPUT_KEY_C>;
tap-delay-ms = <400>;
};
/* ---- Power control ---- */
/* Board power enable — must be HIGH for peripherals */
pwr_enable: pwr-enable {
compatible = "regulator-fixed";
regulator-name = "pwr-enable";
enable-gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
regulator-boot-on;
};
/* LoRa radio power enable — must be HIGH before SX1262 can communicate */
lora_pwr_enable: lora-pwr-enable {
compatible = "regulator-fixed";
regulator-name = "lora-pwr-enable";
enable-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
regulator-boot-on;
startup-delay-us = <10000>;
};
/* GPS power control */
gps_en: gps-enable {
compatible = "gpio-leds";
gps_enable_pin: gps_enable {
gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
label = "GPS Enable";
};
};
/* ---- Battery ADC ---- */
/* P0.05 (AIN3), same divider circuit as RAK4631 */
zephyr,user {
io-channels = <&adc 3>;
vbat-mv-multiplier = <4216>;
};
/* ---- Buzzer on PWM0 ---- */
pwmbuzzer {
compatible = "pwm-leds";
buzzer: buzzer {
pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
};
};
};
/* ---- SoC configuration ---- */
&reg0 {
status = "okay";
};
&reg1 {
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
};
&adc {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
/* Battery voltage on P0.05 (AIN3) */
channel@3 {
reg = <3>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN3>;
zephyr,resolution = <12>;
};
};
&uicr {
nfct-pins-as-gpios;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
/* ---- PWM0 for buzzer ---- */
&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
};
/* ---- GPS on UART0 (AT6558R, 9600 baud) ----
* Uses luatos,air530z driver for NMEA with power control.
* GPS power controlled via on-off-gpios (P1.02 = GPS_EN). */
&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <9600>;
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
gnss: gnss {
compatible = "luatos,air530z";
on-off-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
};
};
/* ---- I2C0 for sensors ---- */
&i2c0 {
compatible = "nordic,nrf-twim";
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
pinctrl-0 = <&i2c0_default>;
pinctrl-1 = <&i2c0_sleep>;
pinctrl-names = "default", "sleep";
/* All supported environment & power sensors — auto-detected at runtime */
#include "../../common/sensors-i2c.dtsi"
};
/* ---- SPI2 for LoRa SX1262 ---- */
&spi2 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi2_default>;
pinctrl-1 = <&spi2_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; /* P1.10 (Arduino 42) LORA_CS */
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <8000000>;
/* LoRa control pins */
reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>; /* P1.06 (Arduino 38) */
busy-gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>; /* P1.14 (Arduino 46) */
dio1-gpios = <&gpio1 15 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; /* P1.15 (Arduino 47) */
/* DIO2 drives RF switch for TX */
dio2-tx-enable;
/* TCXO voltage 1.8V via DIO3 */
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_1V8>;
tcxo-power-startup-delay-ms = <5>;
/* RX boosted mode for better sensitivity */
rx-boosted;
};
};
/* ---- QSPI Flash — not present on WisMesh Tag ---- */
&qspi {
status = "disabled";
};
/* ---- USB CDC for console/CLI ---- */
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
cdc_acm_uart: cdc_acm_uart {
compatible = "zephyr,cdc-acm-uart";
};
};
/* ---- Flash partitions (SoftDevice v6, app@0x26000) ---- */
#include "../../common/nrf52_partitions_sdv6.dtsi"
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: Apache-2.0
# RAK WisMesh Tag default configuration
# Enable MPU
CONFIG_ARM_MPU=y
CONFIG_HW_STACK_PROTECTION=y
# Enable GPIO
CONFIG_GPIO=y
# Enable console on USB CDC
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_LINE_CTRL=y
# Enable flash/NVS storage
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
# Pinctrl
CONFIG_PINCTRL=y
# SPI for LoRa
CONFIG_SPI=y
# ADC for battery
CONFIG_ADC=y
@@ -13,6 +13,9 @@ CONFIG_ZEPHCORE_BOARD_NAME="T1000-E"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="Seeed T1000-E"
# SoftDevice firmware ID (s140 v7.3.0 — Seeed bootloader)
CONFIG_ZEPHCORE_SD_FWID=0x0123
# Radio type (TCXO, RF switch, PA config in DTS)
CONFIG_ZEPHCORE_RADIO_LR1110=y
@@ -209,7 +209,8 @@
reg = <0>;
width = <200>;
height = <200>;
rotation = <270>;
rotation = <0>;
gate-inverted;
busy-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
};
};
@@ -13,7 +13,10 @@
CONFIG_ZEPHCORE_BOARD_NAME="Wio Tracker L1"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="Wio Tracker 1110"
CONFIG_BT_DIS_MODEL_NUMBER_STR="Wio Tracker L1"
# SoftDevice firmware ID (s140 v7.3.0 — Seeed bootloader)
CONFIG_ZEPHCORE_SD_FWID=0x0123
# QSPI flash present (overrides nrf52_common.conf default of =n)
CONFIG_NORDIC_QSPI_NOR=y
+9 -2
View File
@@ -73,9 +73,12 @@ static void auto_off_handler(struct k_work *work)
return;
}
/* E-paper content persists without power — blanking wastes a full
* refresh cycle (~2s) for no benefit. Just turn off the backlight. */
* refresh cycle (~2s) for no benefit. Just turn off the backlight
* and mark display "off" so the next button press triggers
* mc_display_on() backlight restore. */
if (is_epd) {
backlight_set(false);
disp_on = false;
return;
}
if (disp_on) {
@@ -232,7 +235,11 @@ void mc_display_on(void)
}
if (!disp_on) {
display_blanking_off(disp_dev);
/* EPD: content persists (bistable) — no need to unblank,
* just restore backlight. OLED: actually unblank. */
if (!is_epd) {
display_blanking_off(disp_dev);
}
disp_on = true;
}
backlight_set(true);
+3 -1
View File
@@ -114,7 +114,7 @@ static struct ui_state local_ui_state;
/* ========== Constants ========== */
#define SPLASH_DURATION_MS 3000
#define RENDER_DEBOUNCE_MS 50
#define RENDER_DEBOUNCE_EPD_MS 2500 /* e-paper: coalesce updates across full refresh (~2s) */
#define RENDER_DEBOUNCE_EPD_MS 200 /* e-paper: coalesce rapid state updates, then refresh */
/* ========== State ========== */
static bool ui_initialized;
@@ -1038,6 +1038,7 @@ void ui_refresh_display(void)
return;
}
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
/* EPD displays: skip periodic housekeeping renders.
* Each full e-paper refresh takes ~2s and causes visible flashing.
* All meaningful events (messages, BLE, GPS fix, button presses)
@@ -1049,4 +1050,5 @@ void ui_refresh_display(void)
}
schedule_render();
#endif
}
@@ -1,15 +0,0 @@
diff --git a/subsys/fs/littlefs_fs.c b/subsys/fs/littlefs_fs.c
index 6176d533e32..1ea405a3d1f 100644
--- a/subsys/fs/littlefs_fs.c
+++ b/subsys/fs/littlefs_fs.c
@@ -901,7 +901,9 @@ static int littlefs_init_cfg(struct fs_littlefs *fs, int flags)
#ifdef CONFIG_FS_LITTLEFS_FMP_DEV
lcp->read = lfs_api_read;
lcp->prog = lfs_api_prog;
- lcp->erase = lfs_api_erase;
+ if (!lcp->erase) {
+ lcp->erase = lfs_api_erase;
+ }
#endif
lcp->read_size = read_size;
+15
View File
@@ -485,6 +485,21 @@ int main(void)
k_sleep(K_MSEC(100));
LOG_INF("=== ZephCore starting ===");
/* Log reset reason so we can diagnose random reboots */
{
uint32_t cause;
if (hwinfo_get_reset_cause(&cause) == 0) {
LOG_INF("Reset cause: 0x%08x%s%s%s%s%s%s", cause,
(cause & RESET_PIN) ? " PIN" : "",
(cause & RESET_SOFTWARE) ? " SOFTWARE" : "",
(cause & RESET_BROWNOUT) ? " BROWNOUT" : "",
(cause & RESET_POR) ? " POR" : "",
(cause & RESET_WATCHDOG) ? " WATCHDOG" : "",
(cause & RESET_CPU_LOCKUP)? " LOCKUP" : "");
hwinfo_clear_reset_cause();
}
}
if (!ZephyrDataStore::mount()) {
LOG_ERR("LittleFS mount failed");
}