refactor storage, got rid of packet loss on lr1110

This commit is contained in:
liquidraver
2026-02-27 22:41:16 +01:00
parent 2bd7c5d062
commit bb5aca423c
28 changed files with 1053 additions and 744 deletions
+1
View File
@@ -396,6 +396,7 @@ 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
+40 -41
View File
@@ -62,6 +62,11 @@ struct frame {
/* ========== Static state ========== */
/* Deferred connection parameter update — applied after initial sync
* completes (NO_MORE_MSGS) rather than immediately on security_changed,
* because the negotiation disrupts BLE throughput during channel/contact sync. */
static bool conn_params_pending;
/* Callbacks to main */
static const struct ble_callbacks *ble_cbs;
@@ -113,9 +118,6 @@ static enum zephcore_iface active_iface = ZEPHCORE_IFACE_NONE;
/* DLE tracking — set after successful DLE request to avoid double-request */
static bool dle_requested;
/* PHY override — request Coded|1M once if phone chose 2M */
static bool phy_override_sent;
/* Advertising state */
static bool adv_switching = false;
static bool adv_is_slow = false;
@@ -276,7 +278,6 @@ static void connected(struct bt_conn *conn, uint8_t err)
* DLE is deferred to le_phy_updated() (after PHY negotiation completes)
* with a fallback in security_changed() if PHY update never fires. */
dle_requested = false;
phy_override_sent = false;
/* Do NOT proactively request security here.
*
@@ -321,6 +322,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
/* Reset BLE TX state */
ble_tx_in_progress = false;
ble_tx_ready = false;
conn_params_pending = false;
/* Clear interface state if BLE was active */
if (active_iface == ZEPHCORE_IFACE_BLE) {
@@ -391,22 +393,13 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_
* request_dle() returns immediately (dle_requested flag). */
request_dle(conn);
#endif
/* Request our preferred connection parameters. */
struct bt_le_conn_param conn_param = {
.interval_min = BLE_DEFAULT_MIN_INTERVAL,
.interval_max = BLE_DEFAULT_MAX_INTERVAL,
.latency = BLE_DEFAULT_LATENCY,
.timeout = BLE_DEFAULT_TIMEOUT,
};
int param_err = bt_conn_le_param_update(conn, &conn_param);
if (param_err) {
LOG_WRN("Failed to request conn param update: %d", param_err);
} else {
LOG_INF("Requested conn params: %d-%dms interval, latency=%d",
BLE_DEFAULT_MIN_INTERVAL * 5 / 4,
BLE_DEFAULT_MAX_INTERVAL * 5 / 4,
BLE_DEFAULT_LATENCY);
}
/* Defer connection parameter update until after the initial
* app sync finishes (channels + contacts + offline messages).
* Requesting a param change now would disrupt BLE throughput
* during the sync burst. CompanionMesh calls
* zephcore_ble_conn_params_ready() when sync is done. */
conn_params_pending = true;
LOG_INF("conn param update deferred until post-sync");
}
}
@@ -481,27 +474,9 @@ static void le_phy_updated(struct bt_conn *conn, struct bt_conn_le_phy_info *par
LOG_INF("BLE PHY updated: TX=%s RX=%s", phy_name(param->tx_phy),
phy_name(param->rx_phy));
/* If the phone chose 2M, override with Coded|1M preference.
* Coded gives ~4× BLE range (S=8); if the phone doesn't support it,
* the intersection is 1M (better range than 2M for a mesh device).
* Only try once to avoid ping-pong if the phone insists on 2M. */
if (param->tx_phy == BT_GAP_LE_PHY_2M && !phy_override_sent) {
const struct bt_conn_le_phy_param phy_pref = {
.options = BT_CONN_LE_PHY_OPT_NONE,
.pref_tx_phy = BT_GAP_LE_PHY_CODED | BT_GAP_LE_PHY_1M,
.pref_rx_phy = BT_GAP_LE_PHY_CODED | BT_GAP_LE_PHY_1M,
};
phy_override_sent = true;
int err = bt_conn_le_phy_update(conn, &phy_pref);
if (err) {
LOG_WRN("PHY override failed: %d, requesting DLE", err);
} else {
LOG_INF("Requested Coded|1M PHY (overriding 2M)");
return; /* DLE when this callback fires again */
}
}
/* PHY is settled — request DLE. Deferred here from connected()
/* Accept whatever PHY the phone chose overriding 2M with Coded|1M
* caused iPhone to freeze the connection (and the whole node).
* PHY is settled — request DLE. Deferred here from connected()
* because the phone starts a PHY procedure on connect and BLE
* only allows one LL procedure at a time. */
request_dle(conn);
@@ -1017,3 +992,27 @@ void zephcore_ble_disconnect(void)
bt_conn_disconnect(current_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
}
}
void zephcore_ble_conn_params_ready(void)
{
if (!conn_params_pending || !current_conn) {
return;
}
conn_params_pending = false;
struct bt_le_conn_param conn_param = {
.interval_min = BLE_DEFAULT_MIN_INTERVAL,
.interval_max = BLE_DEFAULT_MAX_INTERVAL,
.latency = BLE_DEFAULT_LATENCY,
.timeout = BLE_DEFAULT_TIMEOUT,
};
int err = bt_conn_le_param_update(current_conn, &conn_param);
if (err) {
LOG_WRN("Post-sync conn param update failed: %d", err);
} else {
LOG_INF("Post-sync conn params: %d-%dms interval, latency=%d",
BLE_DEFAULT_MIN_INTERVAL * 5 / 4,
BLE_DEFAULT_MAX_INTERVAL * 5 / 4,
BLE_DEFAULT_LATENCY);
}
}
+8
View File
@@ -111,6 +111,14 @@ void zephcore_ble_kick_tx(void);
*/
void zephcore_ble_disconnect(void);
/**
* Apply deferred connection parameters.
* Call after the initial app sync is complete (channels + contacts +
* offline messages) so the param negotiation doesn't disrupt throughput
* during the sync burst.
*/
void zephcore_ble_conn_params_ready(void);
#ifdef __cplusplus
}
#endif
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -1,6 +1,13 @@
/*
* SPDX-License-Identifier: Apache-2.0
* 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 other platforms, uses DTS-automounted /lfs for everything.
* QSPI /ext overrides contacts path when available (any platform).
*/
#pragma once
@@ -47,36 +54,29 @@ public:
static bool mount();
static void unmount();
static const char *mountPoint() { return MNT_POINT; }
static const char *extMountPoint() { return EXT_MNT_POINT; }
private:
/* Internal flash (always available) - identity, prefs */
static constexpr const char *MNT_POINT = "/lfs";
static constexpr const char *PREFS_FILE = "/lfs/new_prefs";
static constexpr const char *MAIN_ID_FILE = "/lfs/_main.id";
/* Mount points resolved at mount time:
* nRF52: _contacts_mnt="/efs", _prefs_mnt="/ifs"
* Others: _contacts_mnt="/lfs", _prefs_mnt="/lfs"
* QSPI: _contacts_mnt="/ext" (override) */
static const char *_contacts_mnt;
static const char *_prefs_mnt;
/* External QSPI flash (optional) - contacts, channels, blobs */
/* External QSPI flash (optional, any platform) */
static constexpr const char *EXT_MNT_POINT = "/ext";
static constexpr const char *EXT_CONTACTS_FILE = "/ext/contacts3";
static constexpr const char *EXT_CHANNELS_FILE = "/ext/channels2";
static constexpr const char *EXT_ADV_BLOBS_FILE = "/ext/adv_blobs";
/* Fallback to internal if no external */
static constexpr const char *INT_CONTACTS_FILE = "/lfs/contacts3";
static constexpr const char *INT_CHANNELS_FILE = "/lfs/channels2";
static constexpr const char *INT_ADV_BLOBS_FILE = "/lfs/adv_blobs";
mesh::RTCClock *_clock;
bool _has_ext_fs;
/* Get path based on external availability */
const char *contactsFile() const { return _has_ext_fs ? EXT_CONTACTS_FILE : INT_CONTACTS_FILE; }
const char *channelsFile() const { return _has_ext_fs ? EXT_CHANNELS_FILE : INT_CHANNELS_FILE; }
const char *advBlobsFile() const { return _has_ext_fs ? EXT_ADV_BLOBS_FILE : INT_ADV_BLOBS_FILE; }
/* Build full paths from resolved mount points */
const char *contactsFile() const;
const char *channelsFile() const;
const char *advBlobsFile() const;
static const char *prefsFile();
static const char *identityFile();
int maxBlobRecs() const { return _has_ext_fs ? 100 : 20; }
void cleanStaleTmpFiles();
void checkAdvBlobFile();
void migrateToExternalFS();
bool openRead(const char *path, uint8_t *buf, size_t buf_sz, size_t &out_len);
@@ -0,0 +1,64 @@
/*
* 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;
}
@@ -57,7 +57,7 @@ static lr11xx_hal_status_t wait_on_busy(struct lr11xx_hal_context *ctx)
gpio_pin_get_dt(&ctx->dio1));
return LR11XX_HAL_STATUS_ERROR;
}
k_busy_wait(100); /* 100us */
k_usleep(100); /* 100us — yields CPU so other threads can run */
loops++;
}
+7 -1
View File
@@ -15,6 +15,7 @@
#include <zephyr/sys/reboot.h>
#include <ZephyrSensorManager.h>
#include <adapters/sensors/SimpleLPP.h>
#include <adapters/ble/ZephyrBLE.h>
LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
/* Protocol commands (matches Arduino companion_radio) - sorted by opcode */
@@ -313,7 +314,7 @@ void CompanionMesh::sendPacketError(uint8_t code)
void CompanionMesh::sendPush(uint8_t code, const uint8_t *data, size_t len)
{
LOG_INF("sendPush: code=0x%02x len=%u _push_cb=%s", code, (unsigned)len, _push_cb ? "set" : "NULL");
LOG_DBG("sendPush: code=0x%02x len=%u _push_cb=%s", code, (unsigned)len, _push_cb ? "set" : "NULL");
if (_push_cb) {
_push_cb(code, data, len);
} else {
@@ -1686,6 +1687,11 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
LOG_INF("CMD_SYNC_NEXT_MESSAGE: queue empty, sending NO_MORE_MSGS");
uint8_t rsp[] = { PACKET_NO_MORE_MSGS };
writeFrame(rsp, sizeof(rsp));
/* Initial sync is done — safe to apply deferred
* connection parameters now without disrupting
* channel/contact/message throughput. */
zephcore_ble_conn_params_ready();
}
return true;
}
+17 -5
View File
@@ -1,10 +1,22 @@
/*
* Common LittleFS filesystem configuration for ZephCore boards.
* Include this in board overlays to get standard /lfs mount.
*
* Requires board to define: &lfs_partition
*
* SPDX-License-Identifier: Apache-2.0
* Common LittleFS /lfs automount for non-nRF52 boards
*
* See nrf52_partitions_sdv7.dtsi for full include guide.
*
* USE THIS FILE FOR: ESP32, MG24, nRF54L any board with a single
* LFS partition and standard 4KB erase blocks.
*
* DO NOT USE FOR nRF52: Use nrf52_partitions_sdv6.dtsi or
* nrf52_partitions_sdv7.dtsi instead (dual-mount, 128B blocks).
*
* Requires board DTS to define: lfs_partition
*
* Usage in board DTS/overlay:
* #include "../../common/filesystem.dtsi"
*
* Optional add-on (if board has QSPI flash):
* #include "../../common/qspi-ext.dtsi"
*/
/ {
+14 -2
View File
@@ -25,12 +25,24 @@ CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
CONFIG_BT_CTLR_PHY_CODED=y
# ========== Flash: nRF52-specific ==========
# Partial erase — splits 4KB page erase into small sub-erases that fit
# between BLE connection events. Without this, the flash driver waits for
# an ~85ms gap in BLE activity (ticker sync) which never comes during an
# active connection, freezing the system on the first flash write.
CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE=y
# QSPI flash — disabled by default (safer — avoids hang if chip missing).
# Boards with QSPI (Wio Tracker L1) override in board.conf.
CONFIG_NORDIC_QSPI_NOR=n
# Recover from corrupted NVS sectors (nRF52 flash can develop bad regions)
CONFIG_NVS_INIT_BAD_MEMORY_REGION=y
# ========== Storage: Arduino MeshCore compatible layout ==========
# 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.
CONFIG_NVS=n
CONFIG_SETTINGS_NVS=n
CONFIG_SETTINGS_FILE=y
CONFIG_SETTINGS_FILE_PATH="/ifs/settings"
# ========== SEGGER RTT (J-Link debug) ==========
# RTT log backend is in boards/common/logging.conf (only included for debug builds).
@@ -0,0 +1,67 @@
/*
* SPDX-License-Identifier: Apache-2.0
* nRF52840 partition layout SoftDevice s140 v6 + UF2 bootloader
* Arduino MeshCore compatible (dual LFS, block_size=128)
*
* See nrf52_partitions_sdv7.dtsi for full include guide.
* Use THIS file instead of sdv7 when the board's UF2 bootloader
* bundles SoftDevice s140 v6.x (app starts at 0x26000).
*
* Boards: RAK4631
*
* Usage in board overlay:
* /delete-node/ &boot_partition;
* /delete-node/ &slot0_partition;
* /delete-node/ &slot1_partition;
* /delete-node/ &storage_partition;
* #include "../../common/nrf52_partitions_sdv6.dtsi"
*
* Memory map (1MB internal flash):
* 0x00000 - 0x26000 (152KB) SoftDevice s140 v6 (reserved)
* 0x26000 - 0xD4000 (696KB) Application
* 0xD4000 - 0xED000 (100KB) ExtraFS (contacts, channels, blobs)
* 0xED000 - 0xF4000 (28KB) InternalFS (prefs, identity, BLE settings)
* 0xF4000 - 0x100000 (48KB) UF2 bootloader (reserved)
*/
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/* SoftDevice s140 v6.1.1 — 152KB (reserved, managed by bootloader) */
boot_partition: partition@0 {
label = "softdevice";
read-only;
reg = <0x00000000 0x00026000>;
};
/* Application — 696KB */
code_partition: partition@26000 {
label = "Application";
reg = <0x00026000 0x000AE000>;
};
/* ExtraFS 100KB: contacts3, channels2, adv_blobs
* Arduino: CustomLFS(0xD4000, 0x19000, 128) */
extrafs_partition: partition@d4000 {
label = "extrafs";
reg = <0x000D4000 0x00019000>;
};
/* InternalFS 28KB: new_prefs, _main.id, BLE settings
* Arduino: Adafruit InternalFS(0xED000, 0x7000, 128) */
internalfs_partition: partition@ed000 {
label = "internalfs";
reg = <0x000ED000 0x00007000>;
};
/* UF2 bootloader — 48KB (reserved) */
uf2_partition: partition@f4000 {
label = "UF2";
read-only;
reg = <0x000F4000 0x0000C000>;
};
};
};
@@ -0,0 +1,96 @@
/*
* SPDX-License-Identifier: Apache-2.0
* nRF52840 partition layout SoftDevice s140 v7 + UF2 bootloader
* Arduino MeshCore compatible (dual LFS, block_size=128)
*
*
* ZephCore Board DTS Include Guide Storage & Partitions
*
*
* PLATFORM INCLUDE THIS FILE
*
* nRF52 + SD v7 nrf52_partitions_sdv7.dtsi THIS FILE
* nRF52 + SD v6 nrf52_partitions_sdv6.dtsi
* ESP32 / MG24 / filesystem.dtsi (single /lfs automount)
* nRF54L
*
* OPTIONAL ADD-ON WHEN TO USE
*
* qspi-ext.dtsi Board has QSPI flash adds /ext mount
* (contacts/channels/blobs migrate to QSPI)
* Requires board to define qspi_storage_part
*
* sensors-i2c.dtsi Board has I2C sensors (auto-detect)
*
* HOW TO IDENTIFY YOUR SOFTDEVICE VERSION:
* - SD v7: Application starts at 0x27000 (156KB reserved)
* Boards: T1000-E, Ikoka Nano 30dBm, Wio Tracker L1
* - SD v6: Application starts at 0x26000 (152KB reserved)
* Boards: RAK4631
*
* USAGE IN BOARD DTS (if base includes nrf52840_partition.dtsi):
* /delete-node/ &boot_partition;
* /delete-node/ &slot0_partition;
* /delete-node/ &slot1_partition;
* /delete-node/ &storage_partition;
* #include "../../common/nrf52_partitions_sdv7.dtsi"
*
* CHOSEN NODE:
* zephyr,code-partition = &code_partition;
* (Do NOT set zephyr,settings-partition uses file settings)
*
* QSPI ADD-ON (if board has external QSPI flash):
* In board.overlay:
* #include "../../common/qspi-ext.dtsi"
*
*
*
* Memory map (1MB internal flash):
* 0x00000 - 0x27000 (156KB) SoftDevice s140 v7 (reserved)
* 0x27000 - 0xD4000 (692KB) Application
* 0xD4000 - 0xED000 (100KB) ExtraFS (contacts, channels, blobs)
* 0xED000 - 0xF4000 (28KB) InternalFS (prefs, identity, BLE settings)
* 0xF4000 - 0x100000 (48KB) UF2 bootloader (reserved)
*/
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/* SoftDevice s140 v7 — 156KB (reserved, managed by bootloader) */
boot_partition: partition@0 {
label = "softdevice";
read-only;
reg = <0x00000000 0x00027000>;
};
/* Application — 692KB */
code_partition: partition@27000 {
label = "Application";
reg = <0x00027000 0x000AD000>;
};
/* ExtraFS 100KB: contacts3, channels2, adv_blobs
* Arduino: CustomLFS(0xD4000, 0x19000, 128) */
extrafs_partition: partition@d4000 {
label = "extrafs";
reg = <0x000D4000 0x00019000>;
};
/* InternalFS 28KB: new_prefs, _main.id, BLE settings
* Arduino: Adafruit InternalFS(0xED000, 0x7000, 128) */
internalfs_partition: partition@ed000 {
label = "internalfs";
reg = <0x000ED000 0x00007000>;
};
/* UF2 bootloader — 48KB (reserved) */
uf2_partition: partition@f4000 {
label = "UF2";
read-only;
reg = <0x000F4000 0x0000C000>;
};
};
};
+3 -3
View File
@@ -17,6 +17,6 @@ CONFIG_USE_SEGGER_RTT=n
# Disable thread name strings (saves flash — only useful for debug)
CONFIG_THREAD_NAME=n
# Max contacts for production (App protocol limit: 510, encoded as uint8 * 2, so you can set it higher, but the current app can't display it or work with it)
# Check board configs for overrides — too low RAM can freeze the device.
CONFIG_ZEPHCORE_MAX_CONTACTS=510
# Max contacts: Kconfig default is 350 (safe for 100KB ExtraFS).
# Do NOT set here — EXTRA_CONF_FILE overrides board.conf, which would
# prevent boards with more storage (Wio QSPI=510) from raising the limit.
+14 -5
View File
@@ -1,10 +1,19 @@
/*
* Common QSPI external flash LittleFS configuration for ZephCore boards.
* Include this in board overlays that have QSPI flash for /ext mount.
*
* Requires board to define: &qspi_storage_partition
*
* SPDX-License-Identifier: Apache-2.0
* QSPI external flash /ext automount optional add-on for any platform
*
* See nrf52_partitions_sdv7.dtsi for full include guide.
*
* When /ext is available, ZephyrDataStore redirects contacts, channels,
* and blobs to QSPI flash (more space, faster writes on native 4KB blocks).
* Prefs and identity stay on internal flash.
*
* Requires board DTS to define: qspi_storage_partition
*
* Usage in board overlay (in addition to partition or filesystem include):
* #include "../../common/qspi-ext.dtsi"
*
* Works with BOTH nRF52 dual-mount and single /lfs mount boards.
*/
/ {
+4 -2
View File
@@ -81,8 +81,10 @@ CONFIG_BT_BONDING_REQUIRED=y
CONFIG_BT_MAX_PAIRED=5
CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
# Connection parameter update
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=y
# Connection parameter update — disabled because we apply conn params
# manually after initial sync completes (zephcore_ble_conn_params_ready).
# The auto-update would fire too early and disrupt channel/contact sync.
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
# MTU max (controller limit 251, L2CAP MTU 247, ATT payload 244)
CONFIG_BT_BUF_ACL_RX_SIZE=251
@@ -33,7 +33,6 @@
zephyr,code-partition = &code_partition;
zephyr,console = &cdc_acm_uart;
zephyr,shell-uart = &cdc_acm_uart;
zephyr,settings-partition = &storage_partition;
};
aliases {
@@ -178,46 +177,5 @@ zephyr_udc0: &usbd {
};
};
/* LittleFS auto-mount — uses common config for /lfs */
#include "../../common/filesystem.dtsi"
/* QSPI disabled on this board — see note above */
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/* SoftDevice 156KB (reserved) */
boot_partition: partition@0 {
label = "softdevice";
read-only;
reg = <0x00000000 0x00027000>;
};
/* Application 692KB */
code_partition: partition@27000 {
label = "Application";
reg = <0x00027000 0x000AD000>;
};
/* NVS 8KB */
storage_partition: partition@d4000 {
label = "storage";
reg = <0x000D4000 0x00002000>;
};
/* LittleFS 92KB */
lfs_partition: partition@d6000 {
label = "lfs";
reg = <0x000D6000 0x00017000>;
};
/* UF2 bootloader 76KB (reserved) */
uf2_partition: partition@ed000 {
label = "UF2";
read-only;
reg = <0x000ED000 0x00013000>;
};
};
};
/* Arduino MeshCore compatible partition layout (SoftDevice v7) */
#include "../../common/nrf52_partitions_sdv7.dtsi"
+2 -49
View File
@@ -31,7 +31,6 @@
zephyr,code-partition = &code_partition;
zephyr,console = &cdc_acm_uart;
zephyr,shell-uart = &cdc_acm_uart;
zephyr,settings-partition = &storage_partition;
};
};
@@ -106,51 +105,5 @@
status = "disabled";
};
/* Filesystem mounts - internal /lfs only (no QSPI) */
#include "../../common/filesystem.dtsi"
&flash0 {
partitions {
/*
* SoftDevice + UF2 bootloader partition layout
* Total: 1MB internal flash
*
* 0x00000 - 0x26000 (152KB) SoftDevice s140 v6.1.1 (reserved)
* 0x26000 - 0xD4000 (696KB) Application
* 0xD4000 - 0xD6000 (8KB) NVS storage (BLE bonds)
* 0xD6000 - 0xF4000 (120KB) LittleFS (identity, prefs)
* 0xF4000 - 0x100000 (48KB) UF2 bootloader (reserved)
*/
/* SoftDevice 152KB (reserved, managed by bootloader) */
boot_partition: partition@0 {
label = "softdevice";
read-only;
reg = <0x00000000 0x00026000>;
};
/* Application 696KB */
code_partition: partition@26000 {
label = "Application";
reg = <0x00026000 0x000AE000>;
};
/* NVS 8KB for BLE bonds */
storage_partition: partition@d4000 {
label = "storage";
reg = <0x000D4000 0x00002000>;
};
/* LittleFS 120KB for DataStore (identity, contacts, channels, etc) */
lfs_partition: partition@d6000 {
label = "lfs";
reg = <0x000D6000 0x0001E000>;
};
/* UF2 bootloader 48KB (reserved) */
uf2_partition: partition@f4000 {
label = "UF2";
read-only;
reg = <0x000F4000 0x0000C000>;
};
};
};
/* Arduino MeshCore compatible partition layout (SoftDevice v6) */
#include "../../common/nrf52_partitions_sdv6.dtsi"
@@ -34,10 +34,9 @@
compatible = "seeed,t1000-e";
chosen {
zephyr,code-partition = &slot0_partition;
zephyr,code-partition = &code_partition;
zephyr,console = &cdc_acm_uart;
zephyr,shell-uart = &cdc_acm_uart;
zephyr,settings-partition = &storage_partition;
};
leds {
@@ -278,56 +277,5 @@ zephyr_udc0: &usbd {
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/*
* SoftDevice + UF2 bootloader partition layout
* Total: 1MB internal flash
*
* 0x00000 - 0x27000 (156KB) SoftDevice s140 v7 (reserved)
* 0x27000 - 0xD4000 (692KB) Application
* 0xD4000 - 0xD6000 (8KB) NVS storage (BLE bonds)
* 0xD6000 - 0xED000 (92KB) LittleFS (identity, prefs)
* 0xED000 - 0x100000 (76KB) UF2 bootloader (reserved)
*/
/* SoftDevice 156KB (reserved, managed by bootloader) */
boot_partition: partition@0 {
label = "softdevice";
read-only;
reg = <0x00000000 0x00027000>;
};
/* Application 692KB */
slot0_partition: partition@27000 {
label = "Application";
reg = <0x00027000 0x000AD000>;
};
/* NVS 8KB for BLE bonds */
storage_partition: partition@d4000 {
label = "storage";
reg = <0x000D4000 0x00002000>;
};
/* LittleFS 92KB for DataStore (identity, contacts, channels, etc) */
lfs_partition: partition@d6000 {
label = "lfs";
reg = <0x000D6000 0x00017000>;
};
/* UF2 bootloader 76KB (reserved) */
uf2_partition: partition@ed000 {
label = "UF2";
read-only;
reg = <0x000ED000 0x00013000>;
};
};
};
/* LittleFS auto-mount — uses common config for /lfs */
#include "../../common/filesystem.dtsi"
/* Arduino MeshCore compatible partition layout (SoftDevice v7) */
#include "../../common/nrf52_partitions_sdv7.dtsi"
@@ -19,5 +19,8 @@ CONFIG_BT_DIS_MODEL_NUMBER_STR="Wio Tracker 1110"
CONFIG_NORDIC_QSPI_NOR=y
CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096
# Contacts on QSPI (2MB) — override nrf52_common.conf limit of 350
CONFIG_ZEPHCORE_MAX_CONTACTS=510
# PWM for buzzer
CONFIG_PWM=y
@@ -23,6 +23,6 @@
};
};
/* Filesystem mounts - internal /lfs + external /ext QSPI */
#include "../../common/filesystem.dtsi"
/* Internal LFS mounted manually by ZephyrDataStore (block_size=128, custom erase) */
/* External QSPI /ext auto-mounted via DTS (standard 4KB blocks) */
#include "../../common/qspi-ext.dtsi"
@@ -34,10 +34,9 @@
compatible = "seeed,wio-tracker-l1";
chosen {
zephyr,code-partition = &slot0_partition;
zephyr,code-partition = &code_partition;
zephyr,console = &cdc_acm_uart;
zephyr,shell-uart = &cdc_acm_uart;
zephyr,settings-partition = &storage_partition;
zephyr,display = &sh1106;
};
@@ -324,56 +323,8 @@ zephyr_udc0: &usbd {
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/*
* SoftDevice + UF2 bootloader partition layout
* Total: 1MB internal flash
*
* 0x00000 - 0x27000 (156KB) SoftDevice s140 v7 (reserved)
* 0x27000 - 0xD4000 (692KB) Application
* 0xD4000 - 0xD6000 (8KB) NVS storage (BLE bonds)
* 0xD6000 - 0xED000 (92KB) LittleFS (identity, prefs)
* 0xED000 - 0x100000 (76KB) UF2 bootloader (reserved)
*/
/* SoftDevice 156KB (reserved, managed by bootloader) */
boot_partition: partition@0 {
label = "softdevice";
read-only;
reg = <0x00000000 0x00027000>;
};
/* Application 692KB */
slot0_partition: partition@27000 {
label = "Application";
reg = <0x00027000 0x000AD000>;
};
/* NVS 8KB for BLE bonds */
storage_partition: partition@d4000 {
label = "storage";
reg = <0x000D4000 0x00002000>;
};
/* LittleFS 92KB for DataStore (identity, contacts, channels, etc) */
lfs_partition: partition@d6000 {
label = "lfs";
reg = <0x000D6000 0x00017000>;
};
/* UF2 bootloader 76KB (reserved) */
uf2_partition: partition@ed000 {
label = "UF2";
read-only;
reg = <0x000ED000 0x00013000>;
};
};
};
/* Arduino MeshCore compatible partition layout (SoftDevice v7) */
#include "../../common/nrf52_partitions_sdv7.dtsi"
/* External QSPI flash partitions */
&qspi_flash {
+55 -16
View File
@@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*
* Non-blocking RTTTL parser using Zephyr PWM API.
* Each note is scheduled via k_work_delayable - fully event-driven.
* Each note is scheduled via k_work_delayable on a dedicated work queue
* so flash/BLE/filesystem operations can't delay note timing.
*
* RTTTL Format: "Name:d=D,o=O,b=B:note,note,..."
* D = default duration (1,2,4,8,16,32)
@@ -28,6 +29,20 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(buzzer, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
/* ========== Dedicated Buzzer Work Queue ========== */
/* Runs note scheduling at high priority so flash/BLE/FS operations
* on the system workqueue can't delay tone timing. */
#define BUZZER_WQ_STACK_SIZE 512
#define BUZZER_WQ_PRIORITY 2 /* Higher than default workqueue (usually 10+) */
K_THREAD_STACK_DEFINE(buzzer_wq_stack, BUZZER_WQ_STACK_SIZE);
static struct k_work_q buzzer_wq;
/* Maximum duration (ms) a single tone can play before auto-silence.
* Safety net: if the work queue stalls or the handler doesn't fire,
* the hardware timer kills the PWM after this timeout. */
#define BUZZER_TONE_MAX_MS 2000
/* ========== Note Frequency Table ========== */
/* Frequencies for octave 4 (middle C = C4 = 262 Hz) */
/* Index: C=0, C#=1, D=2, D#=3, E=4, F=5, F#=6, G=7, G#=8, A=9, A#=10, B=11 */
@@ -40,6 +55,7 @@ struct buzzer_ctx {
struct pwm_dt_spec pwm;
const struct device *enable_reg; /* Optional regulator for buzzer amp */
struct k_work_delayable note_work;
struct k_work_delayable safety_work; /* Auto-silence watchdog */
/* RTTTL parser state */
const char *melody; /* Current position in RTTTL string */
@@ -295,6 +311,23 @@ static void buzzer_silence(void)
buzzer_set_tone(0);
}
/* ========== Safety Watchdog ========== */
/**
* Auto-silence handler: kills PWM if a note has been playing too long.
* This is a safety net for crashes or workqueue stalls the PWM hardware
* is autonomous and keeps driving the pin until explicitly stopped.
*/
static void safety_work_handler(struct k_work *work)
{
if (ctx.playing) {
LOG_WRN("safety timeout — silencing stuck tone");
ctx.playing = false;
buzzer_silence();
buzzer_amp_off();
}
}
/* ========== Note Work Handler ========== */
static void note_work_handler(struct k_work *work)
@@ -305,6 +338,7 @@ static void note_work_handler(struct k_work *work)
if (!ctx.playing) {
buzzer_silence();
buzzer_amp_off();
k_work_cancel_delayable(&ctx.safety_work);
return;
}
@@ -313,25 +347,21 @@ static void note_work_handler(struct k_work *work)
buzzer_silence();
buzzer_amp_off();
ctx.playing = false;
k_work_cancel_delayable(&ctx.safety_work);
return;
}
/* Play this note */
buzzer_set_tone(freq);
/* Add a small gap between notes (90% tone, 10% silence) */
uint32_t tone_ms = (dur_ms * 9) / 10;
uint32_t gap_ms = dur_ms - tone_ms;
/* Reset safety watchdog — if the next note_work doesn't fire
* within BUZZER_TONE_MAX_MS, the safety handler kills the PWM. */
k_work_reschedule_for_queue(&buzzer_wq, &ctx.safety_work,
K_MSEC(BUZZER_TONE_MAX_MS));
if (gap_ms < 1) {
gap_ms = 1;
}
/* Schedule silence gap after tone, then next note */
/* For simplicity: play tone for full duration, next work picks up next note.
* The inter-note gap comes from the 90/10 split. */
(void)gap_ms;
k_work_reschedule(&ctx.note_work, K_MSEC(dur_ms));
/* Schedule next note after this note's duration */
k_work_reschedule_for_queue(&buzzer_wq, &ctx.note_work,
K_MSEC(dur_ms));
}
/* ========== Public API ========== */
@@ -370,7 +400,15 @@ int buzzer_init(void)
}
}
/* Start dedicated buzzer work queue */
k_work_queue_init(&buzzer_wq);
k_work_queue_start(&buzzer_wq, buzzer_wq_stack,
K_THREAD_STACK_SIZEOF(buzzer_wq_stack),
BUZZER_WQ_PRIORITY, NULL);
k_thread_name_set(&buzzer_wq.thread, "buzzer_wq");
k_work_init_delayable(&ctx.note_work, note_work_handler);
k_work_init_delayable(&ctx.safety_work, safety_work_handler);
ctx.quiet = true; /* Start quiet like Arduino */
ctx.playing = false;
@@ -380,7 +418,7 @@ int buzzer_init(void)
buzzer_silence();
buzzer_amp_off();
LOG_INF("buzzer initialized (PWM)");
LOG_INF("buzzer initialized (PWM, dedicated wq)");
return 0;
}
@@ -414,8 +452,8 @@ void buzzer_play(const char *rtttl)
/* Enable buzzer amplifier power */
buzzer_amp_on();
/* Start playing first note immediately */
k_work_reschedule(&ctx.note_work, K_NO_WAIT);
/* Start playing first note immediately on dedicated wq */
k_work_reschedule_for_queue(&buzzer_wq, &ctx.note_work, K_NO_WAIT);
}
void buzzer_stop(void)
@@ -426,6 +464,7 @@ void buzzer_stop(void)
ctx.playing = false;
k_work_cancel_delayable(&ctx.note_work);
k_work_cancel_delayable(&ctx.safety_work);
buzzer_silence();
buzzer_amp_off();
}
@@ -29,7 +29,7 @@ LOG_MODULE_REGISTER(lr11xx_lora, CONFIG_LORA_LOG_LEVEL);
/* Dedicated DIO1 work queue — keeps LoRa interrupt processing off the
* system work queue so USB/BLE/timer work items cannot delay packet RX. */
#define LR11XX_DIO1_WQ_STACK_SIZE 1536
#define LR11XX_DIO1_WQ_STACK_SIZE 2560
K_THREAD_STACK_DEFINE(lr11xx_dio1_wq_stack, LR11XX_DIO1_WQ_STACK_SIZE);
/* ── Driver data structures ─────────────────────────────────────────── */
@@ -79,13 +79,18 @@ struct lr11xx_data {
volatile bool in_rx_mode;
/* Extension features (duty cycle, boost) */
bool rx_duty_cycle_enabled;
bool rx_duty_cycle_enabled; /* unused — LR1110 always continuous RX */
bool rx_boost_enabled;
bool rx_boost_applied; /* RX boost register written to hardware */
/* Deferred hardware init — heavy SPI/radio work runs on first config() */
bool hw_initialized;
/* DIO1 stuck-HIGH detection: counts consecutive empty IRQ cycles.
* If DIO1 stays HIGH with no actionable IRQ for too many cycles,
* the LR1110 is hung trigger a hardware reset. */
int dio1_stuck_count;
/* RX data buffer — filled in DIO1 handler, passed to callback */
uint8_t rx_buf[256];
};
@@ -259,71 +264,12 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data,
0);
}
/* ── RX duty cycle (RadioLib algorithm, same as SX126x) ─────────────── */
/* ── RX duty cycle — disabled on LR1110 ─────────────────────────────── */
/* Same algorithm as SX126x SetRxDutyCycle — standard RX during the wake
* window, full demodulation capability. Preamble detected hardware
* extends timeout to receive the complete packet. No activity back
* to sleep. Identical detection reliability to the SX126x driver. */
#define LR11XX_DC_MIN_SYMBOLS_SF7_PLUS 8
#define LR11XX_DC_MIN_SYMBOLS_SF6_LESS 12
#define LR11XX_DC_TCXO_DELAY_US 1000
static void lr11xx_apply_rx_duty_cycle(struct lr11xx_data *data)
{
void *ctx = &data->hal_ctx;
struct lora_modem_config *mc = &data->modem_cfg;
uint8_t sf = (uint8_t)mc->datarate;
float bw_khz = bw_enum_to_khz(mc->bandwidth);
uint16_t preamble_len = mc->preamble_len;
uint16_t min_symbols = (sf >= 7) ? LR11XX_DC_MIN_SYMBOLS_SF7_PLUS
: LR11XX_DC_MIN_SYMBOLS_SF6_LESS;
int16_t sleep_symbols = (int16_t)preamble_len - (int16_t)min_symbols;
if (sleep_symbols <= 0) {
LOG_WRN("Preamble too short for duty cycle (need >%d, have %d)",
min_symbols, preamble_len);
data->rx_duty_cycle_enabled = false;
lr11xx_radio_set_rx(ctx, 0xFFFFFF);
return;
}
uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz);
/* Shave 2 symbols off sleep for timing margin */
int16_t sleep_symbols_safe = sleep_symbols - 2;
if (sleep_symbols_safe < 1) {
sleep_symbols_safe = 1;
}
uint32_t sleep_period_us = (uint16_t)sleep_symbols_safe * symbol_us;
uint32_t preamble_total_us = (preamble_len + 1) * symbol_us;
int32_t wake_calc1 = ((int32_t)preamble_total_us -
((int32_t)sleep_period_us - LR11XX_DC_TCXO_DELAY_US)) / 2;
uint32_t wake_calc2 = (min_symbols + 1) * symbol_us;
uint32_t wake_period_us = (wake_calc1 > 0 && (uint32_t)wake_calc1 > wake_calc2)
? (uint32_t)wake_calc1 : wake_calc2;
/* LR1110 API takes milliseconds (converted to RTC steps internally) */
uint32_t rx_ms = (wake_period_us + 500) / 1000;
uint32_t sleep_ms = (sleep_period_us + 500) / 1000;
if (rx_ms < 1) {
rx_ms = 1;
}
if (sleep_ms < 1) {
sleep_ms = 1;
}
lr11xx_radio_set_rx_duty_cycle(ctx, rx_ms, sleep_ms,
LR11XX_RADIO_RX_DUTY_CYCLE_MODE_RX);
LOG_INF("RX duty cycle: rx=%ums sleep=%ums (SF%d BW%.0f)",
rx_ms, sleep_ms, sf, (double)bw_khz);
}
/* LR1110 SetRxDutyCycle is fundamentally broken: both MODE_RX and
* MODE_CAD fail to detect in-progress preambles, dropping 23-40% of
* packets depending on the sleep fraction. The SX1262 handles this
* correctly. LR1110 always uses continuous RX instead. */
/* ── Start RX (internal) ────────────────────────────────────────────── */
@@ -332,8 +278,7 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
{
void *ctx = &data->hal_ctx;
LOG_INF("start_rx: t=%lld duty=%d", k_uptime_get(),
data->rx_duty_cycle_enabled);
LOG_DBG("start_rx: t=%lld", k_uptime_get());
/* Standby first — wake from any sleep state */
data->hal_ctx.radio_is_sleeping = true;
@@ -356,15 +301,12 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
data->rx_boost_applied = true;
}
/* Start RX — continuous or duty cycle.
/* Start continuous RX.
* 0xFFFFFF is the magic RTC-step value for continuous RX.
* Must use the raw RTC-step API set_rx() converts from ms,
* which overflows uint32_t and gives a ~131 s timeout instead. */
if (data->rx_duty_cycle_enabled) {
lr11xx_apply_rx_duty_cycle(data);
} else {
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
}
* which overflows uint32_t and gives a ~131 s timeout instead.
* LR1110 always uses continuous RX (duty cycle is broken). */
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
/* LR1110 firmware sets CMD_ERROR IRQ flag on several write commands
* (SetModParams, SetSyncWord, SetRxBoosted, SetRx) across all
@@ -393,12 +335,7 @@ static void lr11xx_restart_rx(struct lr11xx_data *data)
void *ctx = &data->hal_ctx;
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
if (data->rx_duty_cycle_enabled) {
lr11xx_apply_rx_duty_cycle(data);
} else {
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
}
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
/* RX boost persists through SetRx — no re-apply needed. */
data->in_rx_mode = true;
@@ -432,7 +369,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
goto safety_check;
}
LOG_INF("DIO1 IRQ: 0x%08x tx=%d t=%lld", irq, data->tx_active,
LOG_DBG("DIO1 IRQ: 0x%08x tx=%d t=%lld", irq, data->tx_active,
k_uptime_get());
/* CMD_ERROR (bit 22) is expected — LR1110 firmware sets it on
@@ -445,6 +382,11 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
LOG_WRN("IRQ hardware ERROR: 0x%08x", irq);
}
/* Any valid IRQ clears the stuck counter */
if (irq != 0) {
data->dio1_stuck_count = 0;
}
/* ── RX done ── */
if (irq & LR11XX_SYSTEM_IRQ_RX_DONE) {
lr11xx_radio_rx_buffer_status_t rx_stat;
@@ -499,7 +441,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
/* ── TX done ── */
if (irq & LR11XX_SYSTEM_IRQ_TX_DONE) {
LOG_INF("TX done");
LOG_DBG("TX done");
data->tx_active = false;
/* Full restart — modem was reconfigured for TX */
@@ -514,8 +456,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
/* ── Timeout ── */
if (irq & LR11XX_SYSTEM_IRQ_TIMEOUT) {
LOG_DBG("Timeout IRQ — restarting RX (duty_cycle=%d)",
data->rx_duty_cycle_enabled);
LOG_DBG("Timeout IRQ — restarting RX");
if (!data->tx_active) {
lr11xx_restart_rx(data);
rx_restarted = true;
@@ -568,9 +509,25 @@ safety_check:
/* Edge-triggered DIO1: if the pin is still HIGH after processing,
* a new IRQ arrived during handling. No rising edge will fire,
* so re-submit work to process the pending flags. */
* so re-submit work to process the pending flags.
*
* Guard against DIO1 stuck HIGH: if we loop here with no
* actionable IRQ, the LR1110 is in a bad state. After 5
* consecutive empty cycles, do a full hardware reset. */
if (gpio_pin_get_dt(&data->hal_ctx.dio1)) {
k_work_submit_to_queue(&data->dio1_wq, &data->dio1_work);
data->dio1_stuck_count++;
if (data->dio1_stuck_count >= 5) {
LOG_ERR("DIO1 stuck HIGH for %d cycles — "
"hardware reset", data->dio1_stuck_count);
data->dio1_stuck_count = 0;
lr11xx_hardware_reset(data, cfg);
lr11xx_start_rx(data, cfg);
} else {
k_work_submit_to_queue(&data->dio1_wq,
&data->dio1_work);
}
} else {
data->dio1_stuck_count = 0;
}
k_mutex_unlock(&data->spi_mutex);
@@ -707,7 +664,7 @@ static int lr11xx_lora_send_async(const struct device *dev,
k_mutex_unlock(&data->spi_mutex);
LOG_INF("TX started: len=%u", data_len);
LOG_DBG("TX started: len=%u", data_len);
return 0;
}
@@ -762,9 +719,8 @@ static int lr11xx_lora_recv_async(const struct device *dev,
k_mutex_unlock(&data->spi_mutex);
LOG_DBG("recv_async started%s%s",
data->rx_duty_cycle_enabled ? " (duty cycle)" : "",
data->rx_boost_enabled ? " (boosted)" : "");
LOG_INF("recv_async started (continuous RX%s)",
data->rx_boost_enabled ? ", boosted" : "");
return 0;
}
@@ -817,22 +773,9 @@ bool lr11xx_is_receiving(const struct device *dev)
void lr11xx_set_rx_duty_cycle(const struct device *dev, bool enable)
{
struct lr11xx_data *data = dev->data;
const struct lr11xx_config *cfg = dev->config;
data->rx_duty_cycle_enabled = enable;
LOG_INF("RX duty cycle %s", enable ? "enabled" : "disabled");
/* If currently in RX, restart with proper Standby transition.
* LR1110 requires Standby before SetRx or SetRxDutyCycle
* issuing these while already in RX puts the radio in an
* undefined state where RSSI reads work but packet detection
* is broken (zero DIO1 IRQs). */
if (data->in_rx_mode) {
k_mutex_lock(&data->spi_mutex, K_FOREVER);
lr11xx_start_rx(data, cfg);
k_mutex_unlock(&data->spi_mutex);
}
/* LR1110 duty cycle is broken — always continuous RX. Ignore. */
(void)dev;
(void)enable;
}
void lr11xx_set_rx_boost(const struct device *dev, bool enable)
@@ -1,5 +1,5 @@
diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
index 8e0ca45c271..6b0ee88ff0c 100644
index 8e0ca45c271..eb9342a13b9 100644
--- a/drivers/lora/native/sx126x/sx126x.c
+++ b/drivers/lora/native/sx126x/sx126x.c
@@ -9,10 +9,19 @@
@@ -13,7 +13,7 @@ index 8e0ca45c271..6b0ee88ff0c 100644
+/* Dedicated DIO1 work queue — keeps LoRa interrupt processing off the
+ * system work queue so USB/BLE/timer work items cannot delay packet RX. */
+#define SX126X_DIO1_WQ_STACK_SIZE 1536
+#define SX126X_DIO1_WQ_STACK_SIZE 2560
+K_THREAD_STACK_DEFINE(sx126x_dio1_wq_stack, SX126X_DIO1_WQ_STACK_SIZE);
+
+/* Register not in sx126x_regs.h — only used for §15.3 workaround */
@@ -0,0 +1,15 @@
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;
+4 -1
View File
@@ -170,6 +170,9 @@ static size_t write_frame(const uint8_t *src, size_t len)
*/
static void push_callback(uint8_t code, const uint8_t *data, size_t len)
{
/* No point serializing if nobody is listening */
if (!zephcore_ble_is_connected()) return;
/* Push frame: code byte + optional data */
uint8_t push_buf[1 + MAX_FRAME_SIZE - 1];
size_t total_len = 1 + len;
@@ -185,7 +188,7 @@ static void push_callback(uint8_t code, const uint8_t *data, size_t len)
memcpy(&push_buf[1], data, len);
}
LOG_INF("code=0x%02x len=%u (total frame)", code, (unsigned)total_len);
LOG_DBG("code=0x%02x len=%u (total frame)", code, (unsigned)total_len);
write_frame(push_buf, total_len);
}
@@ -14,7 +14,7 @@
zephyr,code-partition = &code_partition;
zephyr,console = &cdc_acm_uart;
zephyr,shell-uart = &cdc_acm_uart;
zephyr,settings-partition = &storage_partition;
/* No zephyr,settings-partition — nRF52 uses file-based settings */
};
};
@@ -29,33 +29,5 @@
status = "disabled";
};
&flash0 {
partitions {
boot_partition: partition@0 {
label = "softdevice";
read-only;
reg = <0x00000000 0x00026000>;
};
code_partition: partition@26000 {
label = "Application";
reg = <0x00026000 0x000AE000>;
};
storage_partition: partition@d4000 {
label = "storage";
reg = <0x000D4000 0x00002000>;
};
lfs_partition: partition@d6000 {
label = "lfs";
reg = <0x000D6000 0x0001E000>;
};
uf2_partition: partition@f4000 {
label = "UF2";
read-only;
reg = <0x000F4000 0x0000C000>;
};
};
};
/* Arduino MeshCore compatible partition layout (SoftDevice v6) */
#include "../../../../boards/common/nrf52_partitions_sdv6.dtsi"
+121 -12
View File
@@ -1,11 +1,20 @@
/*
* ZephCore Universal Flash Formatter
*
* Erases all filesystem partitions (NVS + LittleFS + QSPI if present)
* and reboots into Adafruit UF2 DFU mode for clean firmware installation.
* Erases all filesystem partitions and reboots into Adafruit UF2 DFU mode
* for clean firmware installation.
*
* Partition addresses come from the board's devicetree overlay no
* hardcoded addresses, works for both s140 v6.1.1 and v7.3.0 boards.
* nRF52 builds use hardcoded flash addresses (identical across all boards)
* so a single UF2 works on every board with the same SoftDevice version.
* QSPI is compile-time conditional included only when building for a
* QSPI-capable board target (pin config is board-specific).
*
* Non-nRF52 builds use DTS FIXED_PARTITION_EXISTS guards as before.
*
* Build:
* SD v7: west build -b t1000_e/nrf52840 zephcore/tools/formatter --pristine
* SD v6: west build -b rak4631/nrf52840 zephcore/tools/formatter --pristine
* SD v7 + QSPI: west build -b wio_tracker_l1/nrf52840 zephcore/tools/formatter --pristine
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -19,14 +28,34 @@
#if defined(CONFIG_SOC_SERIES_NRF52X) || defined(CONFIG_SOC_SERIES_NRF52)
#include <hal/nrf_power.h>
#define IS_NRF52 1
#else
#define IS_NRF52 0
#endif
/* Adafruit UF2 bootloader magic — enter mass storage DFU mode */
#define BOOTLOADER_DFU_UF2_MAGIC 0x57
/* ── LED feedback (optional) ─────────────────────────────────── */
/*
* nRF52840 Arduino MeshCore partition addresses.
* These are IDENTICAL across all nRF52 boards (SD v6 and v7):
* ExtraFS @ 0xD4000 (100KB) contacts, channels, blobs
* InternalFS @ 0xED000 (28KB) prefs, identity, BLE settings
*
* Only the SoftDevice/app boundary differs (v6=0x26000, v7=0x27000),
* which doesn't affect the formatter since we don't touch app flash.
*/
#define NRF52_EXTRAFS_OFF 0xD4000
#define NRF52_EXTRAFS_SIZE 0x19000 /* 100KB */
#define NRF52_INTERNALFS_OFF 0xED000
#define NRF52_INTERNALFS_SIZE 0x7000 /* 28KB */
#if DT_NODE_EXISTS(DT_ALIAS(led0))
/* ── LED feedback (optional — may not match actual board) ──── */
#if DT_NODE_EXISTS(DT_ALIAS(led0)) && !IS_NRF52
/* Only use DTS LED on non-nRF52 (board-specific builds).
* On nRF52 universal builds, skip LED to avoid toggling
* wrong pins on boards other than the build target. */
#define HAS_LED 1
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static void led_init(void) { gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); }
@@ -39,8 +68,35 @@ static void led_on(void) {}
static void led_off(void) {}
#endif
/* ── Partition erase helper ──────────────────────────────────── */
/* ── Flash erase helpers ───────────────────────────────────── */
#if IS_NRF52
/**
* Erase a region of internal flash by absolute address.
* Works on any nRF52840 board regardless of DTS.
*/
static int erase_region(const struct device *dev, off_t offset, size_t size,
const char *name)
{
printk(" %s: erasing 0x%lx - 0x%lx (%u KB)...",
name, (unsigned long)offset,
(unsigned long)(offset + size),
(unsigned)(size / 1024));
int rc = flash_erase(dev, offset, size);
if (rc) {
printk(" FAILED (rc %d)\n", rc);
} else {
printk(" OK\n");
}
return rc;
}
#endif /* IS_NRF52 */
#if !IS_NRF52
/**
* Erase a DTS partition by flash_area ID (non-nRF52 boards).
*/
static int erase_partition(uint8_t id, const char *name)
{
const struct flash_area *fa;
@@ -68,6 +124,7 @@ static int erase_partition(uint8_t id, const char *name)
flash_area_close(fa);
return rc;
}
#endif /* !IS_NRF52 */
/* ── Main ────────────────────────────────────────────────────── */
@@ -85,18 +142,68 @@ int main(void)
led_init();
led_on();
/* ── Erase internal NVS partition (BLE bonds + settings) ── */
#if IS_NRF52
/*
* nRF52 universal path: hardcoded addresses, no DTS dependency.
* This binary works on ANY nRF52840 board with the same SoftDevice
* version (determines UF2 load address, not erase targets).
*/
const struct device *flash_dev = DEVICE_DT_GET(DT_NODELABEL(flash_controller));
if (!device_is_ready(flash_dev)) {
printk(" ERROR: flash device not ready!\n");
errors++;
} else {
if (erase_region(flash_dev, NRF52_EXTRAFS_OFF,
NRF52_EXTRAFS_SIZE, "ExtraFS (contacts/channels)")) {
errors++;
}
if (erase_region(flash_dev, NRF52_INTERNALFS_OFF,
NRF52_INTERNALFS_SIZE, "InternalFS (prefs/identity)")) {
errors++;
}
}
/* QSPI: try to erase if present via flash_area (compiled in by DTS).
* On boards without QSPI, FIXED_PARTITION_EXISTS is false at compile
* time so this block is excluded no runtime probe needed. */
#if FIXED_PARTITION_EXISTS(qspi_storage_partition)
{
const struct flash_area *fa;
int rc = flash_area_open(FIXED_PARTITION_ID(qspi_storage_partition), &fa);
if (rc == 0) {
printk(" QSPI: erasing 0x%lx (%u KB, may take a while)...",
(unsigned long)fa->fa_off,
(unsigned)(fa->fa_size / 1024));
rc = flash_area_erase(fa, 0, fa->fa_size);
printk(rc ? " FAILED (rc %d)\n" : " OK\n", rc);
if (rc) errors++;
flash_area_close(fa);
} else {
printk(" QSPI: not accessible (rc %d) — skipped\n", rc);
}
}
#else
printk(" QSPI: not present in build target — skipped\n");
#endif
#else /* !IS_NRF52 */
/*
* Non-nRF52 path: use DTS partitions (board-specific builds).
*/
#if FIXED_PARTITION_EXISTS(storage_partition)
if (erase_partition(FIXED_PARTITION_ID(storage_partition), "NVS (storage)")) {
errors++;
}
#endif
/* ── Erase internal LittleFS partition (identity, contacts, channels) ── */
#if FIXED_PARTITION_EXISTS(lfs_partition)
if (erase_partition(FIXED_PARTITION_ID(lfs_partition), "LittleFS (lfs)")) {
errors++;
}
#endif
/* ── Erase external QSPI flash (if present in devicetree) ── */
#if DT_NODE_EXISTS(DT_NODELABEL(qspi_storage_partition))
#if FIXED_PARTITION_EXISTS(qspi_storage_partition)
if (erase_partition(FIXED_PARTITION_ID(qspi_storage_partition), "QSPI external")) {
errors++;
}
@@ -104,6 +211,8 @@ int main(void)
printk(" QSPI: not present on this board — skipped\n");
#endif
#endif /* IS_NRF52 */
led_off();
/* ── Summary ── */
@@ -119,7 +228,7 @@ int main(void)
k_msleep(500);
/* ── Reboot to UF2 bootloader ── */
#if defined(CONFIG_SOC_SERIES_NRF52X) || defined(CONFIG_SOC_SERIES_NRF52)
#if IS_NRF52
nrf_power_gpregret_set(NRF_POWER, 0, BOOTLOADER_DFU_UF2_MAGIC);
#endif
sys_reboot(SYS_REBOOT_COLD);