mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-08-29 14:58:16 +00:00
the repo is radiating seeed X1 vibes
This commit is contained in:
@@ -10,6 +10,7 @@ nRF_boards=(
|
||||
rak3401_1watt
|
||||
wio_tracker_l1
|
||||
t1000_e
|
||||
meshtracker_x1
|
||||
thinknode_m1
|
||||
thinknode_m3
|
||||
thinknode_m6
|
||||
|
||||
@@ -770,14 +770,25 @@ Shutdown.
|
||||
Single button; tap-count → key-code mapping comes from the board's devicetree `tap-codes` (up to 5). Typical mapping:
|
||||
- 1 tap → Page next
|
||||
- 2 taps → LED heartbeat toggle
|
||||
- 3 taps → Buzzer toggle
|
||||
- 3 taps → Notification mode (sound+vibrate → vibrate → silent → sound → …; boards with no motor fall back to a plain on/off toggle)
|
||||
- 4 taps → GPS toggle
|
||||
- 5 taps → Flood advert (immediate, no delay)
|
||||
|
||||
### 8.4 Buzzer
|
||||
### 8.4 Buzzer and vibration
|
||||
|
||||
Non-blocking RTTTL parser on dedicated work queue. Predefined melodies for startup, shutdown, messages, ACKs. 2-second safety watchdog auto-silences on work queue stall.
|
||||
|
||||
Boards with a DRV2605 haptic driver (`ti,drv2605` in DT) also vibrate on every notification — `buzzer_play()` pulses the motor. The two outputs share one setting, the notification mode, which lives in `helpers/buzzer_gate.c` (always linked, same pattern as `led_gate.c`, so the CLI resolves its symbols on boards that compile no buzzer). `set buzzer 0|1|2|3` and the 3-tap button action both drive it:
|
||||
|
||||
| Mode | Name | Buzzer | Motor |
|
||||
|------|------|--------|-------|
|
||||
| 0 | silent | - | - |
|
||||
| 1 | sound+vib | yes | yes |
|
||||
| 2 | vibrate | - | yes |
|
||||
| 3 | sound | yes | - |
|
||||
|
||||
Modes 2 and 3 are rejected on boards with no motor, where they would be indistinguishable from 0 and 1. The setting persists in the existing `buzzer_quiet` prefs byte — 0 and 1 keep their original meaning, 2 and 3 are new and read as "quiet" by older firmware, so a downgrade silences a node left on sound-only.
|
||||
|
||||
### 8.5 Doom Easter Egg
|
||||
|
||||
Wolf3D-style raycaster on OLED: textured walls, 2 enemy types, shooting, HUD. Bypasses CFB, writes directly to display. ~1.7KB RAM, ~5KB flash. Enabled via `CONFIG_ZEPHCORE_EASTER_EGG_DOOM`. Button UI: triple-press ENTER on Messages page. Joystick UI: Tools menu → "Doom".
|
||||
@@ -852,6 +863,7 @@ Build strings and flash methods: `boards/supported_boards.md` and `boards/exampl
|
||||
| RAK3401 1W | nRF52840 | SX1262+SKY66122 (30dBm) | u-blox MAX-7Q (opt) | - | I2C sensors |
|
||||
| RAK WisMesh Tag | nRF52840 | SX1262 | AT6558R | - | Accelerometer, buzzer, multitap |
|
||||
| T1000-E | nRF52840 | **LR1110** | AG3335 | - | Buzzer, button, multitap |
|
||||
| SenseCAP MeshTracker X1 | nRF52840 | **LR2021** | AG3335M (L1+L5) | - | SPA06 barometer, DRV2605L vibration, YSN8900 RTC, QSPI 8MB, RGB LEDs, buzzer |
|
||||
| ThinkNode M1 | nRF52840 | SX1262 | Air530Z | EPD 200x200 (SSD1681) | Buzzer, 2 buttons, QSPI 2MB, RGB LEDs |
|
||||
| ThinkNode M3 | nRF52840 | **LR1110** | Yes | - | Buzzer, 2 buttons, RGB LEDs |
|
||||
| ThinkNode M6 | nRF52840 | SX1262 | L76K | - | QSPI, RGB LEDs |
|
||||
|
||||
@@ -265,7 +265,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 "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo" OR BOARD MATCHES "lilygo_timpulse_plus" OR BOARD MATCHES "heltec_t114" OR BOARD MATCHES "heltec_t096" OR BOARD MATCHES "gat562" OR BOARD MATCHES "muziworks_r1neo")
|
||||
if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "meshtracker_x1" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo" OR BOARD MATCHES "lilygo_timpulse_plus" OR BOARD MATCHES "heltec_t114" OR BOARD MATCHES "heltec_t096" OR BOARD MATCHES "gat562" OR BOARD MATCHES "muziworks_r1neo")
|
||||
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")
|
||||
@@ -571,6 +571,9 @@ target_sources(app PRIVATE
|
||||
# LED master gate ("set leds on|off"). Always compiled: helpers/ui/ui_common.c
|
||||
# only exists in UI builds, but a headless repeater still drives lora-tx-led.
|
||||
helpers/led_gate.c
|
||||
# Notification-mode gate. Same reason as led_gate: the CLI needs these
|
||||
# symbols on boards that compile no buzzer at all.
|
||||
helpers/buzzer_gate.c
|
||||
)
|
||||
|
||||
# Boot-time hardware-RTC auto-discovery (compact raw-I2C). Always compiled so
|
||||
@@ -579,6 +582,10 @@ target_sources(app PRIVATE
|
||||
# present in DT.
|
||||
target_sources(app PRIVATE adapters/clock/ZephyrRTCDiscover.c)
|
||||
|
||||
# SPA06-003 barometer. No upstream Zephyr driver; compiles to nothing when no
|
||||
# goertek,spa06 node is present.
|
||||
target_sources_ifdef(CONFIG_SENSOR app PRIVATE adapters/sensors/spa06.c)
|
||||
|
||||
# ========== Battery Curve Selection ==========
|
||||
# helpers/battery_curve.c is always compiled — it provides battery_curve_lookup()
|
||||
# and the weak battery_curve_default. A board-specific battery_curve.c is compiled
|
||||
@@ -876,6 +883,7 @@ endif()
|
||||
if(CONFIG_ZEPHCORE_UI_BUZZER)
|
||||
target_sources(app PRIVATE
|
||||
helpers/ui/buzzer.c
|
||||
helpers/ui/haptic.c
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
+7
-1
@@ -251,10 +251,16 @@ config ZEPHCORE_OFFLINE_QUEUE_SIZE
|
||||
|
||||
config ZEPHCORE_ACK_TABLE_SIZE
|
||||
int "ACK tracking table size"
|
||||
default 8
|
||||
default 16
|
||||
help
|
||||
Number of pending ACKs that can be tracked.
|
||||
|
||||
Sizing this below the number of messages that can be in flight at
|
||||
once costs delivery confirmations: a peer working through a backlog
|
||||
can answer seconds later, and an entry evicted before its ACK
|
||||
arrives leaves the message unconfirmed, prompting a retry that adds
|
||||
further load. Each entry costs 16 bytes.
|
||||
|
||||
config ZEPHCORE_MAX_CONNECTIONS
|
||||
int "Maximum server connections"
|
||||
default 16
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Auto-detects available sensors via Zephyr devicetree nodelabels.
|
||||
*
|
||||
* Environment sensors (temp/humidity/pressure):
|
||||
* SHTC3, AHT20/DHT20/AM2301B, SHT4x, SHT3xD, BME280, BME680, BMP280, BMP388, LPS22HB
|
||||
* SHTC3, AHT20/DHT20/AM2301B, SHT4x, SHT3xD, BME280, BME680, BMP280, BMP388, LPS22HB, SPA06
|
||||
* MCU die temperature as fallback (nordic,nrf-temp)
|
||||
*
|
||||
* Power monitors (voltage/current/power):
|
||||
@@ -144,6 +144,18 @@ check_pressure:
|
||||
LOG_INF("Found pressure sensor: %s (BMP388)", dev->name);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* SPA06 — the two nodes are the same part at its two possible
|
||||
* addresses; the one that isn't there fails its ID check. */
|
||||
dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(spa06));
|
||||
if (!dev || !device_is_ready(dev)) {
|
||||
dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(spa06_alt));
|
||||
}
|
||||
if (dev && device_is_ready(dev)) {
|
||||
pressure_dev = dev;
|
||||
LOG_INF("Found pressure sensor: %s (SPA06)", dev->name);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
@@ -204,6 +216,13 @@ int env_sensors_read(struct env_data *data)
|
||||
data->pressure_hpa = sensor_value_to_float(&val) * 10.0f;
|
||||
data->has_pressure = true;
|
||||
}
|
||||
/* Barometers carry a die temperature. It beats the MCU's own
|
||||
* sensor as a fallback on boards with no dedicated temp part. */
|
||||
if (!data->has_temperature &&
|
||||
sensor_channel_get(pressure_dev, SENSOR_CHAN_AMBIENT_TEMP, &val) == 0) {
|
||||
data->temperature_c = sensor_value_to_float(&val);
|
||||
data->has_temperature = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* === MCU die temperature — always read when available ===
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Goertek SPA06-003 pressure + temperature sensor
|
||||
* Copyright (c) 2026 ZephCore
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT goertek_spa06
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/sensor.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
LOG_MODULE_REGISTER(spa06, CONFIG_SENSOR_LOG_LEVEL);
|
||||
|
||||
#define SPA06_REG_PSR_B2 0x00
|
||||
#define SPA06_REG_PRS_CFG 0x06
|
||||
#define SPA06_REG_TMP_CFG 0x07
|
||||
#define SPA06_REG_MEAS_CFG 0x08
|
||||
#define SPA06_REG_CFG_REG 0x09
|
||||
#define SPA06_REG_RESET 0x0C
|
||||
#define SPA06_REG_ID 0x0D
|
||||
#define SPA06_REG_COEF 0x10
|
||||
|
||||
#define SPA06_CHIP_ID 0x11
|
||||
#define SPA06_SOFT_RESET 0x09
|
||||
|
||||
#define SPA06_MEAS_COEF_RDY BIT(7)
|
||||
#define SPA06_MEAS_SENSOR_RDY BIT(6)
|
||||
#define SPA06_MEAS_CONT_BOTH 0x07
|
||||
|
||||
/* 8x oversampling for both channels: no result shift needed and the
|
||||
* scaling factor below is the datasheet's kP/kT for that rate. */
|
||||
#define SPA06_OVERSAMPLE_8 0x03
|
||||
#define SPA06_RATE_1HZ 0x00
|
||||
#define SPA06_SCALE_FACTOR 7864320.0f
|
||||
|
||||
#define SPA06_COEF_LEN 21
|
||||
|
||||
struct spa06_config {
|
||||
struct i2c_dt_spec bus;
|
||||
};
|
||||
|
||||
struct spa06_data {
|
||||
int16_t c0, c1;
|
||||
int32_t c00, c10;
|
||||
int16_t c01, c11, c20, c21, c30;
|
||||
int16_t c31, c40;
|
||||
|
||||
int32_t raw_press;
|
||||
int32_t raw_temp;
|
||||
};
|
||||
|
||||
/* 24-bit two's complement, MSB first */
|
||||
static int32_t spa06_raw24(const uint8_t *buf)
|
||||
{
|
||||
int32_t v = ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | buf[2];
|
||||
|
||||
return (v & 0x800000) ? (v | ~0xFFFFFF) : v;
|
||||
}
|
||||
|
||||
static int16_t sign_extend_12(uint16_t v)
|
||||
{
|
||||
return (v & 0x800) ? (int16_t)(v | 0xF000) : (int16_t)v;
|
||||
}
|
||||
|
||||
static int32_t sign_extend_20(uint32_t v)
|
||||
{
|
||||
return (v & 0x80000) ? (int32_t)(v | 0xFFF00000) : (int32_t)v;
|
||||
}
|
||||
|
||||
static int spa06_read_coefficients(const struct device *dev)
|
||||
{
|
||||
const struct spa06_config *cfg = dev->config;
|
||||
struct spa06_data *data = dev->data;
|
||||
uint8_t c[SPA06_COEF_LEN];
|
||||
int rc;
|
||||
|
||||
rc = i2c_burst_read_dt(&cfg->bus, SPA06_REG_COEF, c, sizeof(c));
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
data->c0 = sign_extend_12(((uint16_t)c[0] << 4) | (c[1] >> 4));
|
||||
data->c1 = sign_extend_12((((uint16_t)c[1] & 0x0F) << 8) | c[2]);
|
||||
data->c00 = sign_extend_20(((uint32_t)c[3] << 12) |
|
||||
((uint32_t)c[4] << 4) | (c[5] >> 4));
|
||||
data->c10 = sign_extend_20((((uint32_t)c[5] & 0x0F) << 16) |
|
||||
((uint32_t)c[6] << 8) | c[7]);
|
||||
data->c01 = (int16_t)(((uint16_t)c[8] << 8) | c[9]);
|
||||
data->c11 = (int16_t)(((uint16_t)c[10] << 8) | c[11]);
|
||||
data->c20 = (int16_t)(((uint16_t)c[12] << 8) | c[13]);
|
||||
data->c21 = (int16_t)(((uint16_t)c[14] << 8) | c[15]);
|
||||
data->c30 = (int16_t)(((uint16_t)c[16] << 8) | c[17]);
|
||||
data->c31 = sign_extend_12(((uint16_t)c[18] << 4) | (c[19] >> 4));
|
||||
data->c40 = sign_extend_12((((uint16_t)c[19] & 0x0F) << 8) | c[20]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spa06_sample_fetch(const struct device *dev,
|
||||
enum sensor_channel chan)
|
||||
{
|
||||
const struct spa06_config *cfg = dev->config;
|
||||
struct spa06_data *data = dev->data;
|
||||
uint8_t buf[6];
|
||||
int rc;
|
||||
|
||||
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_PRESS &&
|
||||
chan != SENSOR_CHAN_AMBIENT_TEMP) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
rc = i2c_burst_read_dt(&cfg->bus, SPA06_REG_PSR_B2, buf, sizeof(buf));
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
data->raw_press = spa06_raw24(&buf[0]);
|
||||
data->raw_temp = spa06_raw24(&buf[3]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spa06_channel_get(const struct device *dev,
|
||||
enum sensor_channel chan,
|
||||
struct sensor_value *val)
|
||||
{
|
||||
struct spa06_data *data = dev->data;
|
||||
float t_sc = (float)data->raw_temp / SPA06_SCALE_FACTOR;
|
||||
float p_sc, p2, p3, p4, pa;
|
||||
|
||||
switch (chan) {
|
||||
case SENSOR_CHAN_AMBIENT_TEMP:
|
||||
return sensor_value_from_float(val,
|
||||
(float)data->c0 * 0.5f +
|
||||
(float)data->c1 * t_sc);
|
||||
|
||||
case SENSOR_CHAN_PRESS:
|
||||
p_sc = (float)data->raw_press / SPA06_SCALE_FACTOR;
|
||||
p2 = p_sc * p_sc;
|
||||
p3 = p2 * p_sc;
|
||||
p4 = p3 * p_sc;
|
||||
|
||||
pa = (float)data->c00 + (float)data->c10 * p_sc +
|
||||
(float)data->c20 * p2 + (float)data->c30 * p3 +
|
||||
(float)data->c40 * p4 +
|
||||
t_sc * ((float)data->c01 + (float)data->c11 * p_sc +
|
||||
(float)data->c21 * p2 + (float)data->c31 * p3);
|
||||
|
||||
/* Sensor API expects kPa */
|
||||
return sensor_value_from_float(val, pa / 1000.0f);
|
||||
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE_API(sensor, spa06_api) = {
|
||||
.sample_fetch = spa06_sample_fetch,
|
||||
.channel_get = spa06_channel_get,
|
||||
};
|
||||
|
||||
static int spa06_init(const struct device *dev)
|
||||
{
|
||||
const struct spa06_config *cfg = dev->config;
|
||||
uint8_t id, meas;
|
||||
int rc;
|
||||
|
||||
if (!i2c_is_ready_dt(&cfg->bus)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rc = i2c_reg_read_byte_dt(&cfg->bus, SPA06_REG_ID, &id);
|
||||
if (rc < 0) {
|
||||
return -ENODEV;
|
||||
}
|
||||
if (id != SPA06_CHIP_ID) {
|
||||
LOG_DBG("no SPA06 at 0x%02x (id 0x%02x)", cfg->bus.addr, id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rc = i2c_reg_write_byte_dt(&cfg->bus, SPA06_REG_RESET, SPA06_SOFT_RESET);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
k_msleep(15);
|
||||
|
||||
/* Calibration data is only valid once the part reports both ready */
|
||||
for (int i = 0; i < 20; i++) {
|
||||
rc = i2c_reg_read_byte_dt(&cfg->bus, SPA06_REG_MEAS_CFG, &meas);
|
||||
if (rc == 0 && (meas & SPA06_MEAS_COEF_RDY) &&
|
||||
(meas & SPA06_MEAS_SENSOR_RDY)) {
|
||||
goto ready;
|
||||
}
|
||||
k_msleep(10);
|
||||
}
|
||||
LOG_ERR("SPA06 not ready after reset");
|
||||
return -EIO;
|
||||
|
||||
ready:
|
||||
rc = spa06_read_coefficients(dev);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = i2c_reg_write_byte_dt(&cfg->bus, SPA06_REG_PRS_CFG,
|
||||
(SPA06_RATE_1HZ << 4) | SPA06_OVERSAMPLE_8);
|
||||
if (rc == 0) {
|
||||
rc = i2c_reg_write_byte_dt(&cfg->bus, SPA06_REG_TMP_CFG,
|
||||
(SPA06_RATE_1HZ << 4) | SPA06_OVERSAMPLE_8);
|
||||
}
|
||||
if (rc == 0) {
|
||||
/* No result shift: only needed above 8x oversampling */
|
||||
rc = i2c_reg_write_byte_dt(&cfg->bus, SPA06_REG_CFG_REG, 0x00);
|
||||
}
|
||||
if (rc == 0) {
|
||||
rc = i2c_reg_write_byte_dt(&cfg->bus, SPA06_REG_MEAS_CFG,
|
||||
SPA06_MEAS_CONT_BOTH);
|
||||
}
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
LOG_INF("SPA06 at 0x%02x ready", cfg->bus.addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SPA06_DEFINE(inst) \
|
||||
static struct spa06_data spa06_data_##inst; \
|
||||
static const struct spa06_config spa06_config_##inst = { \
|
||||
.bus = I2C_DT_SPEC_INST_GET(inst), \
|
||||
}; \
|
||||
SENSOR_DEVICE_DT_INST_DEFINE(inst, spa06_init, NULL, \
|
||||
&spa06_data_##inst, &spa06_config_##inst,\
|
||||
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,\
|
||||
&spa06_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(SPA06_DEFINE)
|
||||
|
||||
#endif /* DT_HAS_COMPAT_STATUS_OKAY */
|
||||
@@ -187,7 +187,6 @@ CompanionMesh::CompanionMesh(mesh::Radio &radio, mesh::MillisecondClock &ms, mes
|
||||
_offline_queue_count = 0;
|
||||
_sync_pending = false;
|
||||
memset(_ack_table, 0, sizeof(_ack_table));
|
||||
_ack_next_overwrite = 0;
|
||||
memset(_advert_paths, 0, sizeof(_advert_paths));
|
||||
_next_advert_path_idx = 0;
|
||||
_sign_data = nullptr;
|
||||
@@ -652,13 +651,22 @@ void CompanionMesh::addPendingAck(uint32_t expected, int contact_idx)
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Table full — circular overwrite
|
||||
int idx = _ack_next_overwrite;
|
||||
// Table full — evict the entry that has been waiting longest. A rotating
|
||||
// write cursor is not equivalent: it advances independently of when each
|
||||
// slot was filled, so under a burst it can discard an ACK registered
|
||||
// moments ago while a much older one survives. Whichever entry we drop
|
||||
// can never be confirmed, so drop the one least likely to still be
|
||||
// answered.
|
||||
int idx = 0;
|
||||
for (int i = 1; i < ACK_TABLE_SIZE; i++) {
|
||||
if ((int32_t)(_ack_table[i].sent_time - _ack_table[idx].sent_time) < 0) {
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
_ack_table[idx].expected_ack = expected;
|
||||
_ack_table[idx].contact_idx = contact_idx;
|
||||
_ack_table[idx].sent_time = now_ms;
|
||||
_ack_table[idx].active = true;
|
||||
_ack_next_overwrite = (idx + 1) % ACK_TABLE_SIZE;
|
||||
}
|
||||
|
||||
int CompanionMesh::findAndRemoveAck(uint32_t ack, uint32_t *out_sent_time)
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
#ifdef CONFIG_ZEPHCORE_ACK_TABLE_SIZE
|
||||
#define ACK_TABLE_SIZE CONFIG_ZEPHCORE_ACK_TABLE_SIZE
|
||||
#else
|
||||
#define ACK_TABLE_SIZE 8
|
||||
#define ACK_TABLE_SIZE 16
|
||||
#endif
|
||||
|
||||
/* Recently-heard advert path slots */
|
||||
@@ -387,7 +387,6 @@ private:
|
||||
bool active;
|
||||
};
|
||||
AckEntry _ack_table[ACK_TABLE_SIZE];
|
||||
int _ack_next_overwrite;
|
||||
|
||||
/* Advert path table for tracking recently heard nodes */
|
||||
AdvertPath _advert_paths[ADVERT_PATH_TABLE_SIZE];
|
||||
|
||||
@@ -192,12 +192,34 @@ uint8_t RepeaterMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t
|
||||
LOG_INF("Login success");
|
||||
client->last_timestamp = sender_timestamp;
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
client->permissions &= ~0x03;
|
||||
client->permissions |= perms;
|
||||
/* Role assignment only ever escalates. putClient() hands back the
|
||||
* existing entry for a known pubkey, so writing the role bits
|
||||
* unconditionally lets a later guest login on a key that already holds
|
||||
* admin silently strip those rights — the operator is then locked out
|
||||
* of their own repeater with no way back in over the air. Revoking
|
||||
* admin is a deliberate act and belongs to the CLI. */
|
||||
if (!client->isAdmin()) {
|
||||
client->permissions &= ~0x03;
|
||||
client->permissions |= perms;
|
||||
}
|
||||
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
if (perms != PERM_ACL_GUEST) {
|
||||
if (!dirty_contacts_expiry) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
if (client->isAdmin()) {
|
||||
/* Flush admin sessions now instead of leaving them on the lazy
|
||||
* timer. The entry carries the shared secret this session's
|
||||
* traffic is encrypted with; if power is lost before the timer
|
||||
* fires, the client keeps a secret the repeater no longer knows
|
||||
* and every later request decrypts to nothing, which presents as
|
||||
* a wrong password or an unreachable node. A repeater is the
|
||||
* device most likely to lose power unattended, so the few
|
||||
* milliseconds are worth it. save() writes the whole ACL, so any
|
||||
* other pending changes go out with it. */
|
||||
if (_store) {
|
||||
acl.save(_store->getAclPath());
|
||||
dirty_contacts_expiry = 0;
|
||||
} else if (!dirty_contacts_expiry) {
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ Supported Boards
|
||||
| XIAO nRF52840 | `west build -b xiao_nrf52840 zephcore` | UF2 drag-drop or `west flash` |
|
||||
| ProMicro SX1262 | `west build -b promicro_sx1262 zephcore` | UF2 drag-drop or `west flash` |
|
||||
| T1000-E | `west build -b t1000_e zephcore` | UF2 drag-drop or `west flash` |
|
||||
| SenseCAP MeshTracker X1 | `west build -b meshtracker_x1 zephcore` | UF2 drag-drop or `west flash` |
|
||||
| ThinkNode M1 | `west build -b thinknode_m1 zephcore` | UF2 drag-drop or `west flash` |
|
||||
| ThinkNode M3 | `west build -b thinknode_m3 zephcore` | UF2 drag-drop or `west flash` |
|
||||
| ThinkNode M6 | `west build -b thinknode_m6 zephcore` | UF2 drag-drop or `west flash` |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
config BOARD_MESHTRACKER_X1
|
||||
select SOC_NRF52840_QIAA
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* MeshTracker X1 high-voltage LiPo OCV curve — 21 points at 5% steps.
|
||||
* 11-point base linearly interpolated to 5% resolution.
|
||||
* Index 0 = 100% (4290 mV), index 20 = 0% (3100 mV).
|
||||
*
|
||||
* 4.37 V cut-off cell, so the top of the curve sits ~100 mV above a
|
||||
* conventional 4.2 V pack.
|
||||
*/
|
||||
|
||||
#include "battery_curve.h"
|
||||
|
||||
static const uint16_t ocv_meshtracker_x1[21] = {
|
||||
4290, 4204, 4118, 4060, 4002, /* 100 .. 80% */
|
||||
3946, 3889, 3844, 3798, 3763, /* 75 .. 55% */
|
||||
3728, 3703, 3677, 3660, 3643, /* 50 .. 30% */
|
||||
3619, 3594, 3547, 3500, 3300, /* 25 .. 5% */
|
||||
3100, /* 0% */
|
||||
};
|
||||
|
||||
const battery_curve_t battery_curve_default = {
|
||||
.ocv_mv = ocv_meshtracker_x1,
|
||||
.num_points = 21,
|
||||
.num_cells = 1,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
|
||||
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
|
||||
|
||||
include(${ZEPHYR_BASE}/boards/common/uf2.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
|
||||
@@ -0,0 +1,23 @@
|
||||
# SenseCAP MeshTracker X1 (nRF52840 + LR2021)
|
||||
#
|
||||
# Hardware:
|
||||
# - LR2021 LoRa on SPI2 (P0.12 CS, P1.10 RST, P0.07 BUSY, P1.01 DIO8/IRQ), TCXO 1.6V
|
||||
# - Airoha AG3335M dual-band L1+L5 GNSS on UART0 (P0.13 TX, P0.14 RX) @ 115200
|
||||
# - SPA06-003 barometer (0x77) + DRV2605L haptic (0x5A) on I2C0 (P1.15/P1.14)
|
||||
# - YSN8900 RTC (0x32) on I2C1 (P0.26 SDA, P0.27 SCL), rail enable P0.09
|
||||
# - GD25Q64E 8MB QSPI flash -> /ext, rail enable P0.15
|
||||
# - RGB LED (P0.03 R, P0.24 G, P0.28 B), buzzer (P0.25), button (P0.06)
|
||||
# - Battery ADC on AIN0 (P0.02), 2.0x divider, enable P1.06, BQ24045 charger
|
||||
|
||||
CONFIG_ZEPHCORE_BOARD_NAME="MeshTracker X1"
|
||||
|
||||
CONFIG_BT_DIS_MODEL_NUMBER_STR="Seeed MeshTracker X1"
|
||||
|
||||
# SoftDevice firmware ID (s140 v7.3.0 — Seeed bootloader)
|
||||
CONFIG_ZEPHCORE_SD_FWID=0x0123
|
||||
|
||||
CONFIG_ZEPHCORE_RADIO_LR2021=y
|
||||
|
||||
# DRV2605L vibration motor
|
||||
CONFIG_HAPTICS=y
|
||||
CONFIG_HAPTICS_DRV2605=y
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
board:
|
||||
name: meshtracker_x1
|
||||
full_name: Seeed SenseCAP MeshTracker X1
|
||||
vendor: seeed
|
||||
socs:
|
||||
- name: nrf52840
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Seeed SenseCAP MeshTracker X1 - Pin Control
|
||||
* Copyright (c) 2026 ZephCore
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
&pinctrl {
|
||||
/* GNSS UART - Airoha dual-band L1+L5 at 115200 baud */
|
||||
uart0_default: uart0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(UART_TX, 0, 13)>;
|
||||
};
|
||||
group2 {
|
||||
psels = <NRF_PSEL(UART_RX, 0, 14)>;
|
||||
bias-pull-up;
|
||||
};
|
||||
};
|
||||
|
||||
uart0_sleep: uart0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(UART_TX, 0, 13)>,
|
||||
<NRF_PSEL(UART_RX, 0, 14)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* I2C0 - SPA06 barometer + DRV2605L haptic driver */
|
||||
i2c0_default: i2c0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 1, 15)>,
|
||||
<NRF_PSEL(TWIM_SCL, 1, 14)>;
|
||||
};
|
||||
};
|
||||
|
||||
i2c0_sleep: i2c0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 1, 15)>,
|
||||
<NRF_PSEL(TWIM_SCL, 1, 14)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* I2C1 - YSN8900 RTC on its own bus */
|
||||
i2c1_default: i2c1_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 27)>;
|
||||
};
|
||||
};
|
||||
|
||||
i2c1_sleep: i2c1_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 27)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* QSPI - GD25Q64E 8MB flash */
|
||||
qspi_default: qspi_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(QSPI_SCK, 0, 19)>,
|
||||
<NRF_PSEL(QSPI_IO0, 0, 21)>,
|
||||
<NRF_PSEL(QSPI_IO1, 0, 22)>,
|
||||
<NRF_PSEL(QSPI_IO2, 0, 23)>,
|
||||
<NRF_PSEL(QSPI_IO3, 1, 0)>,
|
||||
<NRF_PSEL(QSPI_CSN, 0, 20)>;
|
||||
};
|
||||
};
|
||||
|
||||
qspi_sleep: qspi_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(QSPI_SCK, 0, 19)>,
|
||||
<NRF_PSEL(QSPI_IO0, 0, 21)>,
|
||||
<NRF_PSEL(QSPI_IO1, 0, 22)>,
|
||||
<NRF_PSEL(QSPI_IO2, 0, 23)>,
|
||||
<NRF_PSEL(QSPI_IO3, 1, 0)>;
|
||||
low-power-enable;
|
||||
};
|
||||
group2 {
|
||||
psels = <NRF_PSEL(QSPI_CSN, 0, 20)>;
|
||||
low-power-enable;
|
||||
bias-pull-up;
|
||||
};
|
||||
};
|
||||
|
||||
/* PWM0 for buzzer on P0.25 */
|
||||
pwm0_default: pwm0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(PWM_OUT0, 0, 25)>;
|
||||
};
|
||||
};
|
||||
|
||||
pwm0_sleep: pwm0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(PWM_OUT0, 0, 25)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* SPI2 for LR2021 (SPI1 shares its peripheral slot with I2C1) */
|
||||
spi2_default: spi2_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 0, 11)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 1, 9)>,
|
||||
<NRF_PSEL(SPIM_MISO, 1, 8)>;
|
||||
};
|
||||
};
|
||||
|
||||
spi2_sleep: spi2_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 0, 11)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 1, 9)>,
|
||||
<NRF_PSEL(SPIM_MISO, 1, 8)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Seeed SenseCAP MeshTracker X1 - nRF52840 + LR2021
|
||||
* Copyright (c) 2026 ZephCore
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Hardware:
|
||||
* - nRF52840 SoC with BLE
|
||||
* - LR2021 LoRa radio (TCXO 1.6V on DIO3, no external RF switch)
|
||||
* - Airoha dual-band L1+L5 GNSS on UART0
|
||||
* - SPA06 barometer + DRV2605L haptic driver (I2C0)
|
||||
* - RGB LED, buzzer, single user button
|
||||
* - 1100 mAh high-voltage LiPo, ADC on AIN0
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
#include <nordic/nrf52840_qiaa.dtsi>
|
||||
#include <nordic/nrf52840_partition.dtsi>
|
||||
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
|
||||
#include <zephyr/dt-bindings/input/input-event-codes.h>
|
||||
#include <zephyr/dt-bindings/pwm/pwm.h>
|
||||
#include "meshtracker_x1_nrf52840-pinctrl.dtsi"
|
||||
|
||||
/* Delete default partitions from nrf52840_partition.dtsi */
|
||||
/delete-node/ &boot_partition;
|
||||
/delete-node/ &slot0_partition;
|
||||
/delete-node/ &slot1_partition;
|
||||
/delete-node/ &storage_partition;
|
||||
|
||||
/ {
|
||||
model = "Seeed SenseCAP MeshTracker X1";
|
||||
compatible = "seeed,meshtracker-x1";
|
||||
|
||||
chosen {
|
||||
zephyr,code-partition = &code_partition;
|
||||
zephyr,console = &cdc_acm_uart;
|
||||
zephyr,shell-uart = &cdc_acm_uart;
|
||||
};
|
||||
|
||||
leds {
|
||||
compatible = "gpio-leds";
|
||||
|
||||
led_green: led_0 {
|
||||
gpios = <&gpio0 24 GPIO_ACTIVE_HIGH>;
|
||||
label = "Green LED";
|
||||
};
|
||||
|
||||
led_red: led_1 {
|
||||
gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
|
||||
label = "Red LED";
|
||||
};
|
||||
|
||||
led_blue: led_2 {
|
||||
gpios = <&gpio0 28 GPIO_ACTIVE_HIGH>;
|
||||
label = "Blue LED";
|
||||
};
|
||||
};
|
||||
|
||||
buttons: buttons {
|
||||
compatible = "gpio-keys";
|
||||
|
||||
user_button: button_0 {
|
||||
/* Active HIGH (pressed = VCC), external pull-down on board */
|
||||
gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
|
||||
zephyr,code = <INPUT_KEY_0>;
|
||||
label = "User Button";
|
||||
};
|
||||
};
|
||||
|
||||
/* Short-press → KEY_A, long-press → KEY_F */
|
||||
user_btn_longpress {
|
||||
compatible = "zephyr,input-longpress";
|
||||
input = <&buttons>;
|
||||
input-codes = <INPUT_KEY_0>;
|
||||
short-codes = <INPUT_KEY_A>;
|
||||
long-codes = <INPUT_KEY_F>;
|
||||
long-delay-ms = <1000>;
|
||||
};
|
||||
|
||||
/* 1 tap → page next, 2 → LED heartbeat, 3 → buzzer mute,
|
||||
* 4 → GPS on/off, 5 → flood advert */
|
||||
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 INPUT_KEY_E>;
|
||||
tap-delay-ms = <400>;
|
||||
};
|
||||
|
||||
/* P1.07 — 3V3 rail feeding the I2C sensors (barometer, haptic driver) */
|
||||
sensor_power: sensor-power {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "sensor-power";
|
||||
enable-gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
|
||||
regulator-boot-on;
|
||||
startup-delay-us = <5000>;
|
||||
};
|
||||
|
||||
/* P1.06 — gates the battery ADC divider, enabled on demand */
|
||||
vbat_enable: vbat-enable {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "vbat-enable";
|
||||
enable-gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
|
||||
/* Battery ADC channel reference for ZephyrBoard */
|
||||
zephyr,user {
|
||||
io-channels = <&adc 0>;
|
||||
vbat-mv-multiplier = <7236>; /* 2:1 divider, 3.6V ref; +0.5% for nRF SAADC gain error */
|
||||
};
|
||||
|
||||
/* Buzzer on PWM0 channel 0 (P0.25) */
|
||||
pwmbuzzer {
|
||||
compatible = "pwm-leds";
|
||||
buzzer: buzzer {
|
||||
pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
|
||||
};
|
||||
};
|
||||
|
||||
/* GNSS control pins */
|
||||
gps_en: gps-enable {
|
||||
compatible = "gpio-leds";
|
||||
gps_enable_pin: gps_enable {
|
||||
gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS Enable";
|
||||
};
|
||||
};
|
||||
|
||||
gps_vrtc: gps-vrtc-enable {
|
||||
compatible = "gpio-leds";
|
||||
gps_vrtc_pin: gps_vrtc {
|
||||
gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS VRTC Enable";
|
||||
};
|
||||
};
|
||||
|
||||
gps_rst: gps-reset {
|
||||
compatible = "gpio-leds";
|
||||
gps_reset_pin: gps_reset {
|
||||
gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS Reset";
|
||||
};
|
||||
};
|
||||
|
||||
gps_sleep: gps-sleep-int {
|
||||
compatible = "gpio-leds";
|
||||
gps_sleep_pin: gps_sleep {
|
||||
gpios = <&gpio0 30 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS Sleep Int";
|
||||
};
|
||||
};
|
||||
|
||||
/* Held LOW during normal operation, HIGH wakes the GNSS RTC domain */
|
||||
gps_rtcint: gps-rtc-int {
|
||||
compatible = "gpio-leds";
|
||||
gps_rtcint_pin: gps_rtc_int {
|
||||
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS RTC Int";
|
||||
};
|
||||
};
|
||||
|
||||
aliases {
|
||||
led0 = &led_green;
|
||||
led1 = &led_red;
|
||||
sw0 = &user_button;
|
||||
watchdog0 = &wdt0;
|
||||
buzzer = &buzzer;
|
||||
lora0 = &lora;
|
||||
gps-enable = &gps_enable_pin;
|
||||
gps-vrtc-enable = &gps_vrtc_pin;
|
||||
gps-reset = &gps_reset_pin;
|
||||
gps-sleep-int = &gps_sleep_pin;
|
||||
gps-rtc-int = &gps_rtcint_pin;
|
||||
};
|
||||
};
|
||||
|
||||
®0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
®1 {
|
||||
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
|
||||
};
|
||||
|
||||
&adc {
|
||||
status = "okay";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* Battery voltage on P0.02 (AIN0) - 2x voltage divider */
|
||||
channel@0 {
|
||||
reg = <0>;
|
||||
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_AIN0>;
|
||||
zephyr,resolution = <12>;
|
||||
};
|
||||
};
|
||||
|
||||
/* P0.09/P0.10 are the NFC antenna pins — the board wires P0.09 to the RTC
|
||||
* rail's power switch */
|
||||
&uicr {
|
||||
nfct-pins-as-gpios;
|
||||
};
|
||||
|
||||
&gpiote {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
/* Both rails are hogged rather than modelled as regulators: a hog is applied
|
||||
* at PRE_KERNEL_1, ahead of the QSPI NOR driver and the RTC probe, and neither
|
||||
* rail is ever switched off — cutting the RTC would lose wall-clock time. */
|
||||
&gpio0 {
|
||||
status = "okay";
|
||||
|
||||
/* RTC power switch (P0.09) */
|
||||
rtc-power-on {
|
||||
gpio-hog;
|
||||
gpios = <9 GPIO_ACTIVE_HIGH>;
|
||||
output-high;
|
||||
};
|
||||
|
||||
/* QSPI flash power switch (P0.15) */
|
||||
flash-power-on {
|
||||
gpio-hog;
|
||||
gpios = <15 GPIO_ACTIVE_HIGH>;
|
||||
output-high;
|
||||
};
|
||||
};
|
||||
|
||||
&gpio1 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&pwm0 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&pwm0_default>;
|
||||
pinctrl-1 = <&pwm0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
};
|
||||
|
||||
/* Airoha dual-band GNSS on UART0 - 115200 baud.
|
||||
* Passive NMEA listener: the module streams NMEA at factory defaults, so no
|
||||
* PAIR command exchange is needed and driver init cannot block on a timeout. */
|
||||
&uart0 {
|
||||
compatible = "nordic,nrf-uarte";
|
||||
status = "okay";
|
||||
current-speed = <115200>;
|
||||
pinctrl-0 = <&uart0_default>;
|
||||
pinctrl-1 = <&uart0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
gnss: gnss {
|
||||
compatible = "gnss-nmea-generic";
|
||||
/* ZephyrGPSManager powers the module up, then calls device_init() */
|
||||
zephyr,deferred-init;
|
||||
};
|
||||
};
|
||||
|
||||
&i2c0 {
|
||||
compatible = "nordic,nrf-twim";
|
||||
status = "okay";
|
||||
clock-frequency = <I2C_BITRATE_FAST>;
|
||||
pinctrl-0 = <&i2c0_default>;
|
||||
pinctrl-1 = <&i2c0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
/* Sealed enclosure — only these two parts are ever on this bus */
|
||||
spa06: spa06@77 {
|
||||
compatible = "goertek,spa06";
|
||||
reg = <0x77>;
|
||||
};
|
||||
|
||||
haptic: drv2605@5a {
|
||||
compatible = "ti,drv2605";
|
||||
reg = <0x5a>;
|
||||
actuator-mode = "LRA";
|
||||
en-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
|
||||
/* I2C1 — YSN8900 RTC, on its own bus and its own switched rail.
|
||||
*
|
||||
* The part is an RX8900 second-source (same 3.2x2.5x1.0 package, same
|
||||
* 32.768 kHz TCXO grades), so it uses the RX8900 register layout rather than
|
||||
* the RX8130CE one that rtc-i2c.dtsi assumes for address 0x32: time at 0x00,
|
||||
* FLAG at 0x0E, VLF in bit 1. Boot-time discovery BCD-validates what it reads,
|
||||
* so a wrong guess here is detected and skipped, not silently trusted. */
|
||||
&i2c1 {
|
||||
compatible = "nordic,nrf-twim";
|
||||
status = "okay";
|
||||
clock-frequency = <I2C_BITRATE_FAST>;
|
||||
pinctrl-0 = <&i2c1_default>;
|
||||
pinctrl-1 = <&i2c1_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
rtc_ysn8900: rtc-ysn8900@32 {
|
||||
compatible = "zephcore,rtc-i2c";
|
||||
reg = <0x32>;
|
||||
time-reg = <0x00>;
|
||||
date-index = <4>;
|
||||
status-reg = <0x0e>
|
||||
status-mask = <0x02>
|
||||
};
|
||||
};
|
||||
|
||||
/* GD25Q64E — 8MB, all of it LittleFS for contacts, channels and blobs */
|
||||
&qspi {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&qspi_default>;
|
||||
pinctrl-1 = <&qspi_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
qspi_flash: qspi_flash@0 {
|
||||
compatible = "nordic,qspi-nor";
|
||||
reg = <0>;
|
||||
writeoc = "pp4io";
|
||||
readoc = "read4io";
|
||||
sck-frequency = <16000000>;
|
||||
jedec-id = [c8 40 17];
|
||||
size = <67108864>; /* 64Mbit = 8MB, in bits */
|
||||
has-dpd;
|
||||
t-enter-dpd = <3000>;
|
||||
t-exit-dpd = <30000>;
|
||||
};
|
||||
};
|
||||
|
||||
/* SPI2 for LR2021 (SPI1 shares its peripheral slot with I2C1) */
|
||||
&spi2 {
|
||||
compatible = "nordic,nrf-spim";
|
||||
status = "okay";
|
||||
cs-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
|
||||
pinctrl-0 = <&spi2_default>;
|
||||
pinctrl-1 = <&spi2_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
lora: lora@0 {
|
||||
compatible = "semtech,lr2021";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <8000000>;
|
||||
|
||||
reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
|
||||
busy-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>;
|
||||
dio1-gpios = <&gpio1 1 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
|
||||
irq-dio = <8>;
|
||||
|
||||
tcxo-voltage-mv = <1600>;
|
||||
tcxo-startup-delay-ms = <5>;
|
||||
rx-boosted;
|
||||
|
||||
/* Single antenna path, no DIO-driven RF switch on this board */
|
||||
};
|
||||
};
|
||||
|
||||
zephyr_udc0: &usbd {
|
||||
compatible = "nordic,nrf-usbd";
|
||||
status = "okay";
|
||||
|
||||
cdc_acm_uart: cdc_acm_uart {
|
||||
compatible = "zephyr,cdc-acm-uart";
|
||||
};
|
||||
};
|
||||
|
||||
#include "../../common/nrf52_partitions_sdv7.dtsi"
|
||||
|
||||
&qspi_flash {
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
qspi_storage_partition: partition@0 {
|
||||
label = "qspi_storage";
|
||||
reg = <0x00000000 0x00800000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#include "../../common/qspi-ext.dtsi"
|
||||
#include "../../common/nrf52_wakeup.dtsi"
|
||||
@@ -0,0 +1,21 @@
|
||||
identifier: meshtracker_x1
|
||||
name: Seeed SenseCAP MeshTracker X1
|
||||
type: mcu
|
||||
arch: arm
|
||||
ram: 256
|
||||
flash: 1024
|
||||
toolchain:
|
||||
- zephyr
|
||||
- gnuarmemb
|
||||
- xtools
|
||||
supported:
|
||||
- adc
|
||||
- ble
|
||||
- gpio
|
||||
- i2c
|
||||
- nvs
|
||||
- pwm
|
||||
- spi
|
||||
- usb_device
|
||||
- watchdog
|
||||
vendor: seeed
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
CONFIG_ARM_MPU=y
|
||||
CONFIG_HW_STACK_PROTECTION=y
|
||||
CONFIG_GPIO=y
|
||||
CONFIG_SERIAL=y
|
||||
CONFIG_CONSOLE=y
|
||||
|
||||
CONFIG_BUILD_OUTPUT_UF2=y
|
||||
CONFIG_USE_DT_CODE_PARTITION=y
|
||||
@@ -138,6 +138,7 @@
|
||||
reset-gpios = <&gpio0 9 GPIO_ACTIVE_LOW>;
|
||||
busy-gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
|
||||
dio1-gpios = <&gpio0 10 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
|
||||
irq-dio = <9>; /* chip DIO9 → module pin 15 → MCU P0.10 */
|
||||
|
||||
/* TCXO: tried 1800mV on DIO3 (per Meshtastic diy_tcxo) — it WEDGED
|
||||
* the chip (HF-XOSC startup hangs, BUSY stuck, BLE never starts),
|
||||
|
||||
@@ -9,6 +9,7 @@ rak4631
|
||||
rak3401_1watt
|
||||
wio_tracker_l1
|
||||
t1000_e
|
||||
meshtracker_x1
|
||||
thinknode_m1
|
||||
thinknode_m3
|
||||
thinknode_m6
|
||||
@@ -28,6 +29,8 @@ muziworks_r1neo
|
||||
>
|
||||
> **Heltec T114** screenless build: append `boards/nrf52840/heltec_t114/no_display.conf` to `EXTRA_CONF_FILE` for units without the TFT module.
|
||||
>
|
||||
> **SenseCAP MeshTracker X1** (`meshtracker_x1`): nRF52840 + LR2021, AG3335M dual-band L1+L5 GNSS, SPA06 barometer, DRV2605L vibration, YSN8900 RTC, 8 MB QSPI flash (`/ext`), RGB LED, buzzer. Untested on hardware — first ZephCore board to use a real LR2021. The RTC is treated as an RX8900 second-source; boot-time discovery validates that before trusting it.
|
||||
>
|
||||
> **Heltec Mesh Node T096** (`heltec_t096`): nRF52840 with SX1262 + KCT8103L PA/FEM, UC6580 GNSS, and ST7735S 160x80 TFT companion display. The external SPI flash footprint is documented in the board notes but left disabled until the device parameters are confirmed.
|
||||
|
||||
## ESP32
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2026 ZephCore
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
description: |
|
||||
Goertek SPA06-003 barometric pressure and temperature sensor.
|
||||
|
||||
The driver runs the part in continuous background mode at 8x oversampling
|
||||
and 1 Hz for both channels, so sample_fetch never blocks. Channels are
|
||||
SENSOR_CHAN_PRESS (kPa) and SENSOR_CHAN_AMBIENT_TEMP (degrees C).
|
||||
|
||||
compatible: "goertek,spa06"
|
||||
|
||||
include: [sensor-device.yaml, i2c-device.yaml]
|
||||
@@ -1 +1,2 @@
|
||||
goertek Goertek Inc.
|
||||
zephcore ZephCore
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "CommonCLI.h"
|
||||
#include "battery_curve.h"
|
||||
#include "led_gate.h"
|
||||
#include "buzzer_gate.h"
|
||||
#include <helpers/ui/ui_task.h>
|
||||
#include <helpers/MeshTimeSync.h>
|
||||
#include <helpers/time_sync.h>
|
||||
#include <adapters/clock/ZephyrRTCDiscover.h>
|
||||
@@ -471,6 +473,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %u", (uint32_t)_prefs->interference_threshold);
|
||||
} else if (memcmp(config, "leds", 4) == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %s", _prefs->leds_disabled ? "off" : "on");
|
||||
#ifndef ZEPHCORE_REPEATER
|
||||
} else if (memcmp(config, "buzzer", 6) == 0) {
|
||||
uint8_t mode = zephcore_buzzer_mode_from_prefs(_prefs->buzzer_quiet);
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %u (%s)", mode,
|
||||
zephcore_buzzer_mode_name(mode));
|
||||
#endif
|
||||
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
|
||||
strcpy(reply, "Removed - use rxduty instead");
|
||||
} else if (memcmp(config, "multi.acks", 10) == 0) {
|
||||
@@ -668,6 +676,37 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
#ifndef ZEPHCORE_REPEATER
|
||||
} else if (memcmp(config, "buzzer ", 7) == 0) {
|
||||
/* 0 = silent, 1 = sound + vibration, 2 = vibration only,
|
||||
* 3 = sound only. Modes 2 and 3 only mean something on a board
|
||||
* with a vibration motor, which most boards don't have. */
|
||||
const char* val = &config[7];
|
||||
int mode;
|
||||
if (memcmp(val, "vibrate", 7) == 0 || val[0] == '2') {
|
||||
mode = ZEPHCORE_BUZZER_VIBRATE;
|
||||
} else if (memcmp(val, "sound", 5) == 0 || val[0] == '3') {
|
||||
mode = ZEPHCORE_BUZZER_SOUND;
|
||||
} else if (memcmp(val, "on", 2) == 0 || val[0] == '1') {
|
||||
mode = ZEPHCORE_BUZZER_ON;
|
||||
} else if (memcmp(val, "off", 3) == 0 || val[0] == '0') {
|
||||
mode = ZEPHCORE_BUZZER_OFF;
|
||||
} else {
|
||||
mode = -1;
|
||||
}
|
||||
if (mode < 0) {
|
||||
strcpy(reply, "Error: 0 (silent), 1 (sound+vib), 2 (vibrate) or 3 (sound)");
|
||||
} else if ((mode == ZEPHCORE_BUZZER_VIBRATE || mode == ZEPHCORE_BUZZER_SOUND) &&
|
||||
!zephcore_buzzer_has_vibrate()) {
|
||||
strcpy(reply, "Error: no vibration motor on this board - use 0 or 1");
|
||||
} else {
|
||||
_prefs->buzzer_quiet = zephcore_buzzer_prefs_from_mode((uint8_t)mode);
|
||||
zephcore_buzzer_set_mode((uint8_t)mode, false);
|
||||
ui_set_buzzer_mode((uint8_t)mode);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
#endif
|
||||
} else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
|
||||
/* Periodic AGC recalibration was removed: it reset the noise floor
|
||||
* to its unseeded sentinel on every fire, forcing a fresh seed and
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* ZephCore - Notification mode (buzzer + vibration)
|
||||
* Copyright (c) 2026 ZephCore
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "buzzer_gate.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/atomic.h>
|
||||
|
||||
/* Written from the main thread (boot / CLI) and the UI task, read by the
|
||||
* render path — same split as led_gate. */
|
||||
static atomic_t s_mode = ATOMIC_INIT(ZEPHCORE_BUZZER_ON);
|
||||
|
||||
uint8_t zephcore_buzzer_mode(void)
|
||||
{
|
||||
return (uint8_t)atomic_get(&s_mode);
|
||||
}
|
||||
|
||||
void zephcore_buzzer_set_mode(uint8_t mode, bool deferred)
|
||||
{
|
||||
if (mode > ZEPHCORE_BUZZER_MODE_MAX) {
|
||||
mode = ZEPHCORE_BUZZER_ON;
|
||||
}
|
||||
atomic_set(&s_mode, (atomic_val_t)mode);
|
||||
zephcore_buzzer_apply(mode, deferred);
|
||||
}
|
||||
|
||||
uint8_t zephcore_buzzer_mode_from_prefs(uint8_t prefs_byte)
|
||||
{
|
||||
switch (prefs_byte) {
|
||||
case 0: return ZEPHCORE_BUZZER_ON;
|
||||
case 2: return ZEPHCORE_BUZZER_VIBRATE;
|
||||
case 3: return ZEPHCORE_BUZZER_SOUND;
|
||||
default: return ZEPHCORE_BUZZER_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t zephcore_buzzer_prefs_from_mode(uint8_t mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case ZEPHCORE_BUZZER_ON: return 0;
|
||||
case ZEPHCORE_BUZZER_VIBRATE: return 2;
|
||||
case ZEPHCORE_BUZZER_SOUND: return 3;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t zephcore_buzzer_next_mode(uint8_t mode)
|
||||
{
|
||||
if (!zephcore_buzzer_has_vibrate()) {
|
||||
/* VIBRATE is dead and SOUND is just ON, so it is a plain toggle */
|
||||
return (mode == ZEPHCORE_BUZZER_ON) ? ZEPHCORE_BUZZER_OFF
|
||||
: ZEPHCORE_BUZZER_ON;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case ZEPHCORE_BUZZER_ON: return ZEPHCORE_BUZZER_VIBRATE;
|
||||
case ZEPHCORE_BUZZER_VIBRATE: return ZEPHCORE_BUZZER_OFF;
|
||||
case ZEPHCORE_BUZZER_OFF: return ZEPHCORE_BUZZER_SOUND;
|
||||
default: return ZEPHCORE_BUZZER_ON;
|
||||
}
|
||||
}
|
||||
|
||||
bool zephcore_buzzer_mode_audible(uint8_t mode)
|
||||
{
|
||||
return mode == ZEPHCORE_BUZZER_ON || mode == ZEPHCORE_BUZZER_SOUND;
|
||||
}
|
||||
|
||||
const char *zephcore_buzzer_mode_name(uint8_t mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case ZEPHCORE_BUZZER_ON: return "sound+vib";
|
||||
case ZEPHCORE_BUZZER_VIBRATE: return "vibrate";
|
||||
case ZEPHCORE_BUZZER_SOUND: return "sound";
|
||||
default: return "silent";
|
||||
}
|
||||
}
|
||||
|
||||
/* Overridden in helpers/ui/buzzer.c on boards that have a buzzer. */
|
||||
__weak void zephcore_buzzer_apply(uint8_t mode, bool deferred)
|
||||
{
|
||||
ARG_UNUSED(mode);
|
||||
ARG_UNUSED(deferred);
|
||||
}
|
||||
|
||||
__weak bool zephcore_buzzer_has_vibrate(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* ZephCore - Notification mode (buzzer + vibration)
|
||||
* Copyright (c) 2026 ZephCore
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Always linked, like led_gate — the CLI needs these symbols on boards that
|
||||
* compile no buzzer at all.
|
||||
*/
|
||||
|
||||
#ifndef ZEPHCORE_BUZZER_GATE_H
|
||||
#define ZEPHCORE_BUZZER_GATE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* User-facing mode, as accepted by "set buzzer". */
|
||||
#define ZEPHCORE_BUZZER_OFF 0 /* silent */
|
||||
#define ZEPHCORE_BUZZER_ON 1 /* sound + vibration */
|
||||
#define ZEPHCORE_BUZZER_VIBRATE 2 /* vibration only */
|
||||
#define ZEPHCORE_BUZZER_SOUND 3 /* sound only */
|
||||
#define ZEPHCORE_BUZZER_MODE_MAX 3
|
||||
|
||||
/*
|
||||
* NodePrefs still stores the original buzzer_quiet byte: 0 = not quiet,
|
||||
* 1 = quiet. Vibration-only and sound-only are the new values 2 and 3, which
|
||||
* older firmware reads as "quiet" — so a downgrade silences a node left on
|
||||
* sound-only, which is the safe direction. Conversion lives here so the two
|
||||
* numberings never leak into the rest of the tree.
|
||||
*/
|
||||
uint8_t zephcore_buzzer_mode_from_prefs(uint8_t prefs_byte);
|
||||
uint8_t zephcore_buzzer_prefs_from_mode(uint8_t mode);
|
||||
|
||||
/* ON -> VIBRATE -> OFF -> SOUND -> ON. With no motor fitted only ON and OFF
|
||||
* are reachable — the other two would be indistinguishable from them. */
|
||||
uint8_t zephcore_buzzer_next_mode(uint8_t mode);
|
||||
|
||||
/** @return true if the mode lets the buzzer make noise */
|
||||
bool zephcore_buzzer_mode_audible(uint8_t mode);
|
||||
|
||||
/** @return short label for a mode, for the CLI and the display page */
|
||||
const char *zephcore_buzzer_mode_name(uint8_t mode);
|
||||
|
||||
/** @return the mode currently in effect */
|
||||
uint8_t zephcore_buzzer_mode(void);
|
||||
|
||||
/**
|
||||
* Record a mode and apply it to the hardware.
|
||||
* @param deferred when muting, let an in-flight melody finish first
|
||||
*/
|
||||
void zephcore_buzzer_set_mode(uint8_t mode, bool deferred);
|
||||
|
||||
/* Hardware half of zephcore_buzzer_set_mode(). Weak no-op with no buzzer;
|
||||
* overridden in helpers/ui/buzzer.c. Call the setter, not this. */
|
||||
void zephcore_buzzer_apply(uint8_t mode, bool deferred);
|
||||
|
||||
/** @return true if the board has a vibration motor. Weak false. */
|
||||
bool zephcore_buzzer_has_vibrate(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHCORE_BUZZER_GATE_H */
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "ui_pages.h"
|
||||
#include "ui_task.h"
|
||||
#include "display.h"
|
||||
#include <helpers/buzzer_gate.h>
|
||||
|
||||
#include <time_sync.h>
|
||||
#include <ZephyrSensorManager.h>
|
||||
@@ -1266,13 +1267,15 @@ static void render_buzzer(void)
|
||||
char buf[24];
|
||||
int y = CONTENT_Y;
|
||||
|
||||
snprintf(buf, sizeof(buf), "Buzzer: %s",
|
||||
state.buzzer_quiet ? "off" : "on");
|
||||
uint8_t next = zephcore_buzzer_next_mode(state.buzzer_mode);
|
||||
|
||||
snprintf(buf, sizeof(buf), "Alert: %s",
|
||||
zephcore_buzzer_mode_name(state.buzzer_mode));
|
||||
mc_display_text(0, y, buf, false);
|
||||
y += LINE_H;
|
||||
|
||||
draw_centered(y + 8,
|
||||
state.buzzer_quiet ? "Press to Enable" : "Press to Disable");
|
||||
snprintf(buf, sizeof(buf), "Press for %s", zephcore_buzzer_mode_name(next));
|
||||
draw_centered(y + 8, buf);
|
||||
}
|
||||
|
||||
static void render_leds_mono(void)
|
||||
|
||||
@@ -91,7 +91,7 @@ struct ui_state {
|
||||
uint32_t gps_next_search_s; /* seconds until next search (0=now/off) */
|
||||
|
||||
/* Buzzer page */
|
||||
bool buzzer_quiet; /* true = muted */
|
||||
uint8_t buzzer_mode; /* ZEPHCORE_BUZZER_{OFF,ON,VIBRATE} */
|
||||
|
||||
/* LEDs page */
|
||||
bool leds_disabled; /* true = LEDs off */
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "ui_task.h"
|
||||
#include "ui_pages.h"
|
||||
#include <helpers/buzzer_gate.h>
|
||||
#include <time_sync.h>
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_UI_BUZZER
|
||||
@@ -408,26 +409,28 @@ static void action_flood_advert(void)
|
||||
schedule_render();
|
||||
}
|
||||
|
||||
/* Cycles sound -> vibrate -> silent -> sound. Boards with no motor skip the
|
||||
* middle step, so they keep the plain on/off toggle they always had. */
|
||||
static void action_buzzer_toggle(void)
|
||||
{
|
||||
#ifdef CONFIG_ZEPHCORE_UI_BUZZER
|
||||
bool was_quiet = buzzer_is_quiet();
|
||||
uint8_t mode = zephcore_buzzer_next_mode(get_state()->buzzer_mode);
|
||||
|
||||
if (was_quiet) {
|
||||
/* Unmuting: enable first, then play ascending confirmation */
|
||||
buzzer_set_quiet(false);
|
||||
if (zephcore_buzzer_mode_audible(mode)) {
|
||||
/* Enable first, then play the ascending confirmation */
|
||||
zephcore_buzzer_set_mode(mode, false);
|
||||
buzzer_play(MELODY_BUZZER_ON);
|
||||
} else {
|
||||
/* Muting: play descending confirmation while still enabled.
|
||||
* Use deferred mute so the "off" melody plays out fully
|
||||
* before the quiet flag suppresses future sounds. */
|
||||
/* Play the descending confirmation while still audible — the
|
||||
* deferred mute lets it finish before sounds are suppressed. */
|
||||
buzzer_play(MELODY_BUZZER_OFF);
|
||||
buzzer_set_quiet_deferred(true);
|
||||
zephcore_buzzer_set_mode(mode, true);
|
||||
}
|
||||
/* Persist mute state across reboots */
|
||||
mesh_set_buzzer_quiet(!was_quiet);
|
||||
get_state()->buzzer_quiet = !was_quiet;
|
||||
LOG_INF("buzzer %s", buzzer_is_quiet() ? "muted" : "unmuted");
|
||||
|
||||
/* Persist across reboots */
|
||||
mesh_set_buzzer_mode(mode);
|
||||
get_state()->buzzer_mode = mode;
|
||||
LOG_INF("buzzer mode=%u", mode);
|
||||
#endif
|
||||
schedule_render();
|
||||
}
|
||||
@@ -1088,11 +1091,11 @@ void ui_set_ble_enabled(bool enabled)
|
||||
s->ble_enabled = enabled;
|
||||
}
|
||||
|
||||
void ui_set_buzzer_quiet(bool quiet)
|
||||
void ui_set_buzzer_mode(uint8_t mode)
|
||||
{
|
||||
struct ui_state *s = get_state();
|
||||
|
||||
s->buzzer_quiet = quiet;
|
||||
s->buzzer_mode = mode;
|
||||
}
|
||||
|
||||
void ui_set_offgrid_mode(bool enabled)
|
||||
|
||||
@@ -275,6 +275,6 @@ extern "C" void ui_set_sensor_data(int16_t, uint32_t, uint16_t, uint16_t) {}
|
||||
extern "C" void ui_set_gps_available(bool) {}
|
||||
extern "C" void ui_set_gps_enabled(bool) {}
|
||||
extern "C" void ui_set_gps_state(uint8_t, uint32_t, uint32_t) {}
|
||||
extern "C" void ui_set_buzzer_quiet(bool) {}
|
||||
extern "C" void ui_set_buzzer_mode(uint8_t) {}
|
||||
extern "C" void ui_set_offgrid_mode(bool) {}
|
||||
extern "C" void ui_set_msg_count(uint16_t count) {}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "joystick_defs.h"
|
||||
#include "joystick_ui_hooks.h"
|
||||
#include <helpers/input/zephcore_input_ascii.h>
|
||||
#include <helpers/buzzer_gate.h>
|
||||
#include <helpers/ui/ui_mesh_actions.h>
|
||||
#include <helpers/ui/ui_task.h>
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
@@ -958,15 +959,16 @@ bool JoystickUITask::isBuzzerQuiet() const
|
||||
void JoystickUITask::toggleBuzzer()
|
||||
{
|
||||
#ifdef CONFIG_ZEPHCORE_UI_BUZZER
|
||||
bool was_quiet = buzzer_is_quiet();
|
||||
if (was_quiet) {
|
||||
buzzer_set_quiet(false);
|
||||
uint8_t mode = zephcore_buzzer_next_mode(zephcore_buzzer_mode());
|
||||
|
||||
if (zephcore_buzzer_mode_audible(mode)) {
|
||||
zephcore_buzzer_set_mode(mode, false);
|
||||
buzzer_play("bon:d=16,o=7,b=200:c,p,c,p,c,p,p,8e");
|
||||
} else {
|
||||
buzzer_play("bof:d=16,o=7,b=200:c,p,c,p,c,p,p,8g5");
|
||||
buzzer_set_quiet_deferred(true);
|
||||
zephcore_buzzer_set_mode(mode, true);
|
||||
}
|
||||
mesh_set_buzzer_quiet(!was_quiet);
|
||||
mesh_set_buzzer_mode(mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
|
||||
#include "buzzer.h"
|
||||
#include "haptic.h"
|
||||
#include "buzzer_gate.h"
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/pwm.h>
|
||||
@@ -364,6 +366,8 @@ static void note_work_handler(struct k_work *work)
|
||||
|
||||
int buzzer_init(void)
|
||||
{
|
||||
haptic_init();
|
||||
|
||||
/* Check for buzzer alias in devicetree */
|
||||
const struct device *pwm_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(pwm0));
|
||||
|
||||
@@ -420,6 +424,10 @@ int buzzer_init(void)
|
||||
|
||||
void buzzer_play(const char *rtttl)
|
||||
{
|
||||
if (rtttl && *rtttl) {
|
||||
haptic_pulse();
|
||||
}
|
||||
|
||||
if (!ctx.initialized) {
|
||||
return;
|
||||
}
|
||||
@@ -492,6 +500,27 @@ void buzzer_set_quiet_deferred(bool quiet)
|
||||
LOG_INF("buzzer %s (deferred)", quiet ? "muted" : "enabled");
|
||||
}
|
||||
|
||||
/* Strong overrides of the weak stubs in helpers/buzzer_gate.c */
|
||||
|
||||
void zephcore_buzzer_apply(uint8_t mode, bool deferred)
|
||||
{
|
||||
bool quiet = !zephcore_buzzer_mode_audible(mode);
|
||||
|
||||
if (quiet && deferred) {
|
||||
buzzer_set_quiet_deferred(true);
|
||||
} else {
|
||||
buzzer_set_quiet(quiet);
|
||||
}
|
||||
|
||||
haptic_set_enabled(mode == ZEPHCORE_BUZZER_ON ||
|
||||
mode == ZEPHCORE_BUZZER_VIBRATE);
|
||||
}
|
||||
|
||||
bool zephcore_buzzer_has_vibrate(void)
|
||||
{
|
||||
return haptic_available();
|
||||
}
|
||||
|
||||
bool buzzer_is_quiet(void)
|
||||
{
|
||||
return ctx.quiet;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* ZephCore - Haptic feedback (DRV2605)
|
||||
* Copyright (c) 2026 ZephCore
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "haptic.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(zephcore_haptic, CONFIG_ZEPHCORE_SENSORS_LOG_LEVEL);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(ti_drv2605)
|
||||
|
||||
#include <zephyr/drivers/haptics.h>
|
||||
#include <zephyr/drivers/haptics/drv2605.h>
|
||||
|
||||
#define HAPTIC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(ti_drv2605)
|
||||
|
||||
static const struct device *haptic_dev;
|
||||
static bool haptic_enabled = true;
|
||||
|
||||
/* Library effect 1 = "Strong Click - 100%", short enough to sit under a
|
||||
* notification melody without outlasting it. */
|
||||
static struct drv2605_rom_data rom_data = {
|
||||
.trigger = DRV2605_MODE_INTERNAL_TRIGGER,
|
||||
.library = DRV2605_LIBRARY_LRA,
|
||||
.seq_regs = { 1 },
|
||||
};
|
||||
|
||||
int haptic_init(void)
|
||||
{
|
||||
const union drv2605_config_data cfg = { .rom_data = &rom_data };
|
||||
int rc;
|
||||
|
||||
haptic_dev = DEVICE_DT_GET(HAPTIC_NODE);
|
||||
if (!device_is_ready(haptic_dev)) {
|
||||
LOG_INF("no haptic driver found");
|
||||
haptic_dev = NULL;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rc = drv2605_haptic_config(haptic_dev, DRV2605_HAPTICS_SOURCE_ROM, &cfg);
|
||||
if (rc < 0) {
|
||||
LOG_WRN("haptic config failed: %d", rc);
|
||||
haptic_dev = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
LOG_INF("haptic driver ready");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void haptic_pulse(void)
|
||||
{
|
||||
if (!haptic_dev || !haptic_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)haptics_start_output(haptic_dev);
|
||||
}
|
||||
|
||||
void haptic_set_enabled(bool enabled)
|
||||
{
|
||||
haptic_enabled = enabled;
|
||||
|
||||
if (!enabled && haptic_dev) {
|
||||
(void)haptics_stop_output(haptic_dev);
|
||||
}
|
||||
}
|
||||
|
||||
bool haptic_available(void)
|
||||
{
|
||||
return haptic_dev != NULL;
|
||||
}
|
||||
|
||||
#else /* no ti,drv2605 node — link-compatible stubs */
|
||||
|
||||
int haptic_init(void) { return -ENODEV; }
|
||||
void haptic_pulse(void) { }
|
||||
void haptic_set_enabled(bool enabled) { ARG_UNUSED(enabled); }
|
||||
bool haptic_available(void) { return false; }
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* ZephCore - Haptic feedback
|
||||
* Copyright (c) 2026 ZephCore
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Vibration alerts on boards carrying a DRV2605 haptic driver. Every call
|
||||
* is a no-op on boards without one.
|
||||
*/
|
||||
|
||||
#ifndef ZEPHCORE_HAPTIC_H
|
||||
#define ZEPHCORE_HAPTIC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize the haptic driver from devicetree (ti,drv2605).
|
||||
* @return 0 on success, -ENODEV if the board has no haptic driver
|
||||
*/
|
||||
int haptic_init(void);
|
||||
|
||||
/** Play a short notification buzz. No-op if disabled or uninitialized. */
|
||||
void haptic_pulse(void);
|
||||
|
||||
/** Enable or disable vibration. Driven by the notification mode. */
|
||||
void haptic_set_enabled(bool enabled);
|
||||
|
||||
/** @return true if the board has a working vibration motor */
|
||||
bool haptic_available(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHCORE_HAPTIC_H */
|
||||
@@ -115,7 +115,7 @@ WEAK void ui_set_ble_enabled(bool enabled)
|
||||
ARG_UNUSED(enabled);
|
||||
}
|
||||
|
||||
WEAK void ui_set_buzzer_quiet(bool quiet)
|
||||
WEAK void ui_set_buzzer_mode(uint8_t mode)
|
||||
{
|
||||
ARG_UNUSED(quiet);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <helpers/buzzer_gate.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/drivers/regulator.h>
|
||||
@@ -61,7 +63,7 @@ static atomic_t pending_ui_actions;
|
||||
* Written before atomic_or on pending_ui_actions, read after atomic_clear,
|
||||
* so the atomic provides ordering. Using atomic_t for portability. */
|
||||
static atomic_t pending_gps_enabled;
|
||||
static atomic_t pending_buzzer_quiet;
|
||||
static atomic_t pending_buzzer_mode;
|
||||
static atomic_t pending_offgrid_enabled;
|
||||
static atomic_t pending_leds_disabled;
|
||||
static atomic_t pending_ble_disabled;
|
||||
@@ -164,10 +166,10 @@ extern "C" void mesh_save_gps_duty_sec(uint32_t sec)
|
||||
k_event_post(s_mesh_events, s_mesh_event_ui_action);
|
||||
}
|
||||
|
||||
extern "C" void mesh_set_buzzer_quiet(bool quiet)
|
||||
extern "C" void mesh_set_buzzer_mode(uint8_t mode)
|
||||
{
|
||||
/* Defer the flash write (savePrefs) to mesh thread */
|
||||
atomic_set(&pending_buzzer_quiet, quiet ? 1 : 0);
|
||||
atomic_set(&pending_buzzer_mode, (atomic_val_t)mode);
|
||||
atomic_or(&pending_ui_actions, UI_ACTION_BUZZER_TOGGLE);
|
||||
k_event_post(s_mesh_events, s_mesh_event_ui_action);
|
||||
}
|
||||
@@ -248,9 +250,9 @@ extern "C" void mesh_handle_ui_actions(void)
|
||||
}
|
||||
|
||||
if (actions & UI_ACTION_BUZZER_TOGGLE) {
|
||||
bool bq = atomic_get(&pending_buzzer_quiet) != 0;
|
||||
s_mesh->prefs.buzzer_quiet = bq ? 1 : 0;
|
||||
LOG_INF("buzzer_quiet=%d (button)", bq);
|
||||
uint8_t mode = (uint8_t)atomic_get(&pending_buzzer_mode);
|
||||
s_mesh->prefs.buzzer_quiet = zephcore_buzzer_prefs_from_mode(mode);
|
||||
LOG_INF("buzzer mode=%u (button)", mode);
|
||||
need_save = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ void mesh_send_flood_advert(void);
|
||||
void mesh_send_zerohop_advert(void);
|
||||
void mesh_gps_set_enabled(bool enable);
|
||||
void mesh_ble_set_enabled(bool enable);
|
||||
void mesh_set_buzzer_quiet(bool quiet);
|
||||
void mesh_set_buzzer_mode(uint8_t mode);
|
||||
void mesh_set_offgrid_mode(bool enable);
|
||||
void mesh_set_leds_disabled(bool disabled);
|
||||
void mesh_disable_power_regulators(void);
|
||||
|
||||
@@ -17,7 +17,7 @@ __attribute__((weak)) void mesh_send_flood_advert(void) {}
|
||||
__attribute__((weak)) void mesh_send_zerohop_advert(void) {}
|
||||
__attribute__((weak)) void mesh_gps_set_enabled(bool enable) { ARG_UNUSED(enable); }
|
||||
__attribute__((weak)) void mesh_ble_set_enabled(bool enable) { ARG_UNUSED(enable); }
|
||||
__attribute__((weak)) void mesh_set_buzzer_quiet(bool quiet) { ARG_UNUSED(quiet); }
|
||||
__attribute__((weak)) void mesh_set_buzzer_mode(uint8_t mode) { ARG_UNUSED(mode); }
|
||||
__attribute__((weak)) void mesh_set_offgrid_mode(bool enable) { ARG_UNUSED(enable); }
|
||||
__attribute__((weak)) void mesh_set_leds_disabled(bool disabled) { ARG_UNUSED(disabled); }
|
||||
__attribute__((weak)) void mesh_disable_power_regulators(void) {}
|
||||
|
||||
@@ -163,9 +163,9 @@ void ui_set_gps_state(uint8_t state, uint32_t last_fix_age_s, uint32_t next_sear
|
||||
void ui_set_ble_enabled(bool enabled);
|
||||
|
||||
/**
|
||||
* Set buzzer quiet state (for display page).
|
||||
* Set notification mode (for display page).
|
||||
*/
|
||||
void ui_set_buzzer_quiet(bool quiet);
|
||||
void ui_set_buzzer_mode(uint8_t mode);
|
||||
|
||||
/**
|
||||
* Set LEDs disabled state (for display page).
|
||||
|
||||
@@ -46,6 +46,8 @@ struct lr20xx_config {
|
||||
uint16_t tcxo_voltage_mv;
|
||||
uint32_t tcxo_startup_delay_ms;
|
||||
bool rx_boosted;
|
||||
/* Chip-side DIO wired to the MCU IRQ line (5..11) */
|
||||
uint8_t irq_dio;
|
||||
/* RF switch DIO bitmasks (bit 0 = DIO5, bit 1 = DIO6, ...) */
|
||||
uint8_t rfswitch_enable;
|
||||
uint8_t rfswitch_standby;
|
||||
@@ -194,6 +196,22 @@ static float bw_enum_to_khz(enum lora_signal_bandwidth bw)
|
||||
}
|
||||
}
|
||||
|
||||
/* ── IRQ DIO ────────────────────────────────────────────────────────── */
|
||||
|
||||
static inline lr20xx_system_dio_t lr20xx_irq_dio(const struct lr20xx_config *cfg)
|
||||
{
|
||||
return (lr20xx_system_dio_t)cfg->irq_dio;
|
||||
}
|
||||
|
||||
/* DIO5 can only be pulled up; every other DIO gets a pull-down. */
|
||||
static inline lr20xx_system_dio_drive_t
|
||||
lr20xx_irq_dio_pull(const struct lr20xx_config *cfg)
|
||||
{
|
||||
return cfg->irq_dio == LR20XX_SYSTEM_DIO_5
|
||||
? LR20XX_SYSTEM_DIO_DRIVE_PULL_UP
|
||||
: LR20XX_SYSTEM_DIO_DRIVE_PULL_DOWN;
|
||||
}
|
||||
|
||||
/* ── Configure RF switch DIOs ───────────────────────────────────────── */
|
||||
|
||||
static void lr20xx_configure_rfswitch(void *ctx, const struct lr20xx_config *cfg)
|
||||
@@ -341,10 +359,9 @@ static void lr20xx_hardware_reset(struct lr20xx_data *data,
|
||||
|
||||
lr20xx_configure_rfswitch(ctx, cfg);
|
||||
|
||||
/* DIO9 is the physical IRQ line (pin 15 on NiceRF module → MCU P0.10) */
|
||||
lr20xx_system_set_dio_function(ctx, LR20XX_SYSTEM_DIO_9,
|
||||
lr20xx_system_set_dio_function(ctx, lr20xx_irq_dio(cfg),
|
||||
LR20XX_SYSTEM_DIO_FUNC_IRQ,
|
||||
LR20XX_SYSTEM_DIO_DRIVE_NONE);
|
||||
lr20xx_irq_dio_pull(cfg));
|
||||
|
||||
lr20xx_radio_common_set_rx_tx_fallback_mode(ctx,
|
||||
LR20XX_RADIO_FALLBACK_STDBY_RC);
|
||||
@@ -417,16 +434,19 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data,
|
||||
: LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_NONE);
|
||||
data->rx_boost_applied = data->rx_boost_enabled;
|
||||
|
||||
/* LR20xx uses PPM offset instead of explicit LDRO.
|
||||
* PPM_1_4 (1 bin every 4) is equivalent to LDRO for high-SF
|
||||
* wide-time-on-air configurations. Use recommended value. */
|
||||
/* PPM offset is this chip's name for LDRO and occupies the same wire
|
||||
* field. The Semtech "recommended offset" helper only ever enables it
|
||||
* for SF11/SF12, which disagrees with the rest of the mesh below
|
||||
* 125 kHz — every other stack switches on symbol time > 16.38 ms. */
|
||||
uint32_t bw_hz = (uint32_t)(bw_enum_to_khz(mc->bandwidth) * 1000.0f);
|
||||
uint32_t symbol_time_us = ((1U << (uint8_t)mc->datarate) * 1000000U) / bw_hz;
|
||||
|
||||
lr20xx_radio_lora_mod_params_t mod = {
|
||||
.sf = (lr20xx_radio_lora_sf_t)mc->datarate,
|
||||
.bw = bw_enum_to_lr20xx(mc->bandwidth),
|
||||
.cr = cr_enum_to_lr20xx(mc->coding_rate),
|
||||
.ppm = lr20xx_radio_lora_get_recommended_ppm_offset(
|
||||
(lr20xx_radio_lora_sf_t)mc->datarate,
|
||||
bw_enum_to_lr20xx(mc->bandwidth)),
|
||||
.ppm = (symbol_time_us > 16380) ? LR20XX_RADIO_LORA_PPM_1_4
|
||||
: LR20XX_RADIO_LORA_NO_PPM,
|
||||
};
|
||||
rc = lr20xx_radio_lora_set_modulation_params(ctx, &mod);
|
||||
LOG_DBG("modem_cfg: set_mod(SF%d BW%d CR%d PPM%d)=%d",
|
||||
@@ -459,11 +479,7 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data,
|
||||
lr20xx_radio_common_pa_cfg_t pa;
|
||||
int8_t pa_val;
|
||||
|
||||
/* DEBUG/TEST: force MINIMUM power (-9dBm, 1 PA slice) to probe the
|
||||
* supply-sag hypothesis. If TX keys here (mode=5 + carrier) but
|
||||
* not at +22dBm (7 slices), the rail can't source PA current →
|
||||
* hardware power limit. REVERT to mc->tx_power after testing. */
|
||||
lr20xx_get_pa_cfg_for_power(-9, &pa, &pa_val);
|
||||
lr20xx_get_pa_cfg_for_power(mc->tx_power, &pa, &pa_val);
|
||||
rc = lr20xx_radio_common_set_pa_cfg(ctx, &pa);
|
||||
LOG_DBG("modem_cfg: set_pa_cfg(sel=%d mode=%d duty=%d slices=%d hf_duty=%d)=%d",
|
||||
pa.pa_sel, pa.pa_lf_mode, pa.pa_lf_duty_cycle,
|
||||
@@ -475,7 +491,7 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data,
|
||||
pa_val, rc);
|
||||
}
|
||||
|
||||
rc = lr20xx_system_set_dio_irq_cfg(ctx, LR20XX_SYSTEM_DIO_9,
|
||||
rc = lr20xx_system_set_dio_irq_cfg(ctx, lr20xx_irq_dio(cfg),
|
||||
LR20XX_SYSTEM_IRQ_ALL_MASK &
|
||||
~(LR20XX_SYSTEM_IRQ_FIFO_RX | LR20XX_SYSTEM_IRQ_FIFO_TX));
|
||||
LOG_DBG("modem_cfg: set_dio_irq=%d", rc);
|
||||
@@ -952,28 +968,9 @@ static int lr20xx_lora_send_async(const struct device *dev,
|
||||
|
||||
lr20xx_radio_common_set_tx(ctx, 5000);
|
||||
|
||||
#if IS_ENABLED(CONFIG_LOG)
|
||||
/* DEBUG: poll chip state right after SET_TX. Non-destructive
|
||||
* (get_status does NOT clear IRQs). We want to see:
|
||||
* - cmd= on the FIRST poll == SET_TX's own command status
|
||||
* (2=OK accepted, 1=PERR rejected, 0=FAIL not executed)
|
||||
* - mode transitions 1(STBY)->5(TX)->1(fallback) if it really TXes
|
||||
* - irq gaining TX_DONE (bit19, 0x00080000) at the chip level
|
||||
* - whether DIO9 physically asserts (the MCU IRQ line)
|
||||
* Diagnostic only — the spi_mutex is held, so the DIO9 work handler
|
||||
* blocks until we unlock, then processes TX_DONE normally. */
|
||||
for (int i = 0; i < 20; i++) {
|
||||
lr20xx_system_stat1_t s1 = {0};
|
||||
lr20xx_system_stat2_t s2 = {0};
|
||||
lr20xx_system_irq_mask_t dbgirq = 0;
|
||||
lr20xx_system_get_status(ctx, &s1, &s2, &dbgirq);
|
||||
LOG_INF("TXpoll[%2d] cmd=%d mode=%d irq=0x%08x BUSY=%d DIO9=%d",
|
||||
i, s1.command_status, s2.chip_mode, dbgirq,
|
||||
gpio_pin_get_dt(&data->hal_ctx.busy),
|
||||
gpio_pin_get_dt(&data->hal_ctx.dio1));
|
||||
k_msleep(20);
|
||||
}
|
||||
#endif
|
||||
/* Command status here is SET_TX's own (2=accepted, 1=rejected,
|
||||
* 0=not executed) and the mode should have left standby. */
|
||||
DUMP_CHIP_STATE(ctx, &data->hal_ctx, "post-SET_TX");
|
||||
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
|
||||
@@ -1447,26 +1444,12 @@ static int lr20xx_hw_init(struct lr20xx_data *data,
|
||||
|
||||
LOG_INF("LR20xx SDK get_version: major=%u minor=%u", ver.major, ver.minor);
|
||||
|
||||
/* CRITICAL: Read raw 4 bytes from GET_VERSION (opcode 0x0101) to
|
||||
* determine if this is LR11x0 or LR20xx silicon.
|
||||
* LR11x0 returns: [hw_type, device_use, fw_major, fw_minor] (4 bytes)
|
||||
* LR20xx returns: [major, minor] (2 bytes, extra bytes would be 0x00)
|
||||
* If byte[0]=0x01/0x02/0x03, it's LR1110/LR1120/LR1121 (LR11x0!) */
|
||||
{
|
||||
const uint8_t cmd[2] = { 0x01, 0x01 };
|
||||
uint8_t raw[4] = { 0 };
|
||||
lr20xx_hal_read(ctx, cmd, 2, raw, 4);
|
||||
LOG_DBG("GET_VERSION raw bytes: 0x%02x 0x%02x 0x%02x 0x%02x",
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
if (raw[0] == 0x01) {
|
||||
LOG_WRN("*** CHIP IDENTIFIES AS LR1110 (LR11x0 family!) ***");
|
||||
} else if (raw[0] == 0x02) {
|
||||
LOG_WRN("*** CHIP IDENTIFIES AS LR1120 (LR11x0 family!) ***");
|
||||
} else if (raw[0] == 0x03) {
|
||||
LOG_WRN("*** CHIP IDENTIFIES AS LR1121 (LR11x0 family!) ***");
|
||||
} else {
|
||||
LOG_DBG("Chip type byte=0x%02x (LR20xx if not 0x01-0x03)", raw[0]);
|
||||
}
|
||||
/* The only base FW version the datasheet documents is 1.24 (0x01/0x18).
|
||||
* Anything else still runs — the mismatch is worth a line in the log
|
||||
* when a bring-up goes sideways, not a hard failure. */
|
||||
if (ver.major != 0x01 || ver.minor != 0x18) {
|
||||
LOG_WRN("Unexpected LR2021 FW %u.%u (datasheet documents 1.24)",
|
||||
ver.major, ver.minor);
|
||||
}
|
||||
|
||||
DUMP_CHIP_STATE(ctx, &data->hal_ctx, "post-reset");
|
||||
@@ -1497,22 +1480,13 @@ static int lr20xx_hw_init(struct lr20xx_data *data,
|
||||
cfg->rfswitch_enable, cfg->rfswitch_standby, cfg->rfswitch_rx,
|
||||
cfg->rfswitch_tx, cfg->rfswitch_tx_hp);
|
||||
|
||||
st = lr20xx_system_set_dio_function(ctx, LR20XX_SYSTEM_DIO_9,
|
||||
st = lr20xx_system_set_dio_function(ctx, lr20xx_irq_dio(cfg),
|
||||
LR20XX_SYSTEM_DIO_FUNC_IRQ,
|
||||
LR20XX_SYSTEM_DIO_DRIVE_NONE);
|
||||
lr20xx_irq_dio_pull(cfg));
|
||||
|
||||
st = lr20xx_radio_common_set_rx_tx_fallback_mode(ctx,
|
||||
LR20XX_RADIO_FALLBACK_STDBY_RC);
|
||||
|
||||
/* DEBUG/TEST: disable the low-battery / EoL detector. The chip was
|
||||
* raising LOW_BATTERY (IRQ bit10, 0x400) during FE cal and TX and
|
||||
* refusing to enter TX. Disabling it disambiguates:
|
||||
* - TX now keys → it was a FALSE VBAT reading (sense pin), done.
|
||||
* - TX still dead → genuine supply brownout under RF (hardware).
|
||||
* Default trim is 1.88V; we both disable AND set the lowest (1.60V). */
|
||||
st = lr20xx_system_set_lbd_cfg(ctx, false, LR20XX_SYSTEM_LBD_TRIM_1_60_V);
|
||||
LOG_DBG("init: disable LBD (low-battery detect)=%d", st);
|
||||
|
||||
DUMP_CHIP_STATE(ctx, &data->hal_ctx, "pre-cal");
|
||||
|
||||
lr20xx_system_clear_errors(ctx);
|
||||
@@ -1661,6 +1635,7 @@ static DEVICE_API(lora, lr20xx_lora_api) = {
|
||||
.tcxo_startup_delay_ms = \
|
||||
DT_INST_PROP_OR(n, tcxo_startup_delay_ms, 5), \
|
||||
.rx_boosted = DT_INST_PROP(n, rx_boosted), \
|
||||
.irq_dio = DT_INST_PROP_OR(n, irq_dio, 9), \
|
||||
.rfswitch_enable = DT_INST_PROP_OR(n, rfswitch_enable, 0), \
|
||||
.rfswitch_standby = DT_INST_PROP_OR(n, rfswitch_standby, 0),\
|
||||
.rfswitch_rx = DT_INST_PROP_OR(n, rfswitch_rx, 0), \
|
||||
|
||||
@@ -42,6 +42,13 @@ properties:
|
||||
type: boolean
|
||||
description: Enable RX boosted mode for better sensitivity (+~3dB, +~2mA).
|
||||
|
||||
irq-dio:
|
||||
type: int
|
||||
default: 9
|
||||
description: |
|
||||
Chip-side DIO wired to the MCU interrupt line, 5..11. This is the DIO
|
||||
number on the LR2021 itself, not the SoC pin — the SoC side is dio1-gpios.
|
||||
|
||||
rfswitch-enable:
|
||||
type: int
|
||||
default: 0
|
||||
|
||||
@@ -31,6 +31,7 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
#include "ui_mesh_actions.h"
|
||||
#include "oled_power.h"
|
||||
#include "led_gate.h"
|
||||
#include "buzzer_gate.h"
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_BUZZER)
|
||||
#include "buzzer.h"
|
||||
#endif
|
||||
@@ -1506,15 +1507,16 @@ int main(void)
|
||||
LOG_INF("offgrid mode: %s (from prefs)",
|
||||
companion_mesh.prefs.client_repeat ? "on" : "off");
|
||||
|
||||
/* Restore buzzer mute state from persisted prefs, then play
|
||||
* startup chime only if buzzer is not muted.
|
||||
* buzzer_init() defaults to quiet=true, so we must always
|
||||
* call buzzer_set_quiet() to apply the saved preference. */
|
||||
/* Restore the notification mode from persisted prefs, then play the
|
||||
* startup chime only if sound is on. buzzer_init() defaults to
|
||||
* quiet=true, so the saved preference must always be applied. */
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_BUZZER)
|
||||
buzzer_set_quiet(companion_mesh.prefs.buzzer_quiet);
|
||||
ui_set_buzzer_quiet(companion_mesh.prefs.buzzer_quiet);
|
||||
LOG_INF("buzzer: quiet=%s (from prefs)",
|
||||
companion_mesh.prefs.buzzer_quiet ? "true" : "false");
|
||||
{
|
||||
uint8_t bmode = zephcore_buzzer_mode_from_prefs(companion_mesh.prefs.buzzer_quiet);
|
||||
zephcore_buzzer_set_mode(bmode, false);
|
||||
ui_set_buzzer_mode(bmode);
|
||||
LOG_INF("buzzer: mode=%u (from prefs)", bmode);
|
||||
}
|
||||
ui_play_startup_chime();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -422,6 +422,10 @@ static uint16_t get_battery_mv(void)
|
||||
/* LR1110 via Zephyr LoRa driver */
|
||||
static const struct device *const lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
|
||||
static mesh::LR1110Radio lora_radio(lora_dev, zephyr_board);
|
||||
#elif IS_ENABLED(CONFIG_ZEPHCORE_RADIO_LR2021)
|
||||
/* LR2021 via Zephyr LoRa driver */
|
||||
static const struct device *const lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
|
||||
static mesh::LR2021Radio lora_radio(lora_dev, zephyr_board);
|
||||
#elif IS_ENABLED(CONFIG_ZEPHCORE_RADIO_SX127X)
|
||||
/* SX127x via Zephyr loramac-node driver */
|
||||
static const struct device *const lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
|
||||
|
||||
@@ -356,6 +356,10 @@ static uint16_t get_battery_mv(void)
|
||||
/* LR1110 via Zephyr LoRa driver */
|
||||
static const struct device *const lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
|
||||
static mesh::LR1110Radio lora_radio(lora_dev, zephyr_board);
|
||||
#elif IS_ENABLED(CONFIG_ZEPHCORE_RADIO_LR2021)
|
||||
/* LR2021 via Zephyr LoRa driver */
|
||||
static const struct device *const lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
|
||||
static mesh::LR2021Radio lora_radio(lora_dev, zephyr_board);
|
||||
#elif IS_ENABLED(CONFIG_ZEPHCORE_RADIO_SX127X)
|
||||
/* SX127x via Zephyr loramac-node driver */
|
||||
static const struct device *const lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
|
||||
|
||||
Reference in New Issue
Block a user