mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-15 10:19:57 +00:00
upstream sync + wio tracker 1w
This commit is contained in:
@@ -9,6 +9,7 @@ nRF_boards=(
|
||||
rak4631
|
||||
rak3401_1watt
|
||||
wio_tracker_l1
|
||||
wio_tracker_l1_1w
|
||||
t1000_e
|
||||
meshtracker_x1
|
||||
thinknode_m1
|
||||
|
||||
@@ -322,8 +322,8 @@ four radio parameters together, since they are one interop-critical set.
|
||||
| `set tx <dbm>` | −9 to board max (default 30) | Set TX power |
|
||||
| `set lat <latitude>` | | Set stored latitude |
|
||||
| `set lon <longitude>` | | Set stored longitude |
|
||||
| `set dutycycle <pct>` | 1–100 | Set duty cycle percentage (converted to airtime factor internally) |
|
||||
| `set af <value>` | float | Set raw airtime factor directly |
|
||||
| `set dutycycle <pct>` | 10–100 | Set duty cycle percentage (converted to airtime factor internally as `(100/pct)-1`). The floor is 10%, not Arduino's 1%: the stored airtime factor is bounded to 0–9 on load, so a lower duty cycle would run until the next reboot and then silently become 10%. |
|
||||
| `set af <value>` | float, 0–9 | Set raw airtime factor directly. Out-of-range values are rejected: the duty cycle is computed as `100/(af+1)`, so `af = -1` is a divide-by-zero and `af < -1` wraps negative. |
|
||||
| `set txdelay <value>` | | Accepted for prefs compatibility — **ignored** (txdelay is adaptive) |
|
||||
| `set rxdelay <value>` | | Accepted for prefs compatibility — **ignored** (rxdelay is adaptive) |
|
||||
| `set direct.txdelay <value>` | | Accepted for prefs compatibility — **ignored** (direct.txdelay is adaptive) |
|
||||
|
||||
@@ -8,6 +8,7 @@ Board strings for use with `west build -b <board> zephcore`.
|
||||
rak4631
|
||||
rak3401_1watt
|
||||
wio_tracker_l1
|
||||
wio_tracker_l1_1w
|
||||
t1000_e
|
||||
meshtracker_x1
|
||||
thinknode_m1
|
||||
|
||||
@@ -3709,8 +3709,14 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
sendPacketError(ERR_NOT_FOUND);
|
||||
} else if (data_type == DATA_TYPE_RESERVED) {
|
||||
sendPacketError(ERR_ILLEGAL_ARG);
|
||||
} else if (payload_len > MAX_CHANNEL_DATA_LENGTH) {
|
||||
LOG_WRN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_CHANNEL_DATA_LENGTH);
|
||||
} else if (payload_len > MAX_GROUP_DATA_LENGTH) {
|
||||
/* Radio-side bound (165), NOT the host-frame bound (167). These
|
||||
* disagree, and sendGroupData() enforces the smaller one, so gating
|
||||
* here on MAX_CHANNEL_DATA_LENGTH let a 166/167-byte payload pass the
|
||||
* frame check, fail the radio check, and come back to the app as
|
||||
* ERR_TABLE_FULL — the "outbound queue full, retry later" code — for a
|
||||
* send that can never succeed at any queue depth. Upstream 0a82fcd2. */
|
||||
LOG_WRN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_GROUP_DATA_LENGTH);
|
||||
sendPacketError(ERR_ILLEGAL_ARG);
|
||||
} else if (sendGroupData(channel.channel, path, path_len, data_type, payload, payload_len)) {
|
||||
sendPacketOk();
|
||||
|
||||
@@ -12,6 +12,7 @@ Supported Boards
|
||||
| RAK4631 | `west build -b rak4631 zephcore` | UF2 drag-drop or `west flash` |
|
||||
| RAK3401 1W | `west build -b rak3401_1watt zephcore` | UF2 drag-drop or `west flash` |
|
||||
| Wio Tracker L1 | `west build -b wio_tracker_l1 zephcore` | UF2 drag-drop or `west flash` |
|
||||
| Wio Tracker L1 Pro 1W | `west build -b wio_tracker_l1_1w zephcore` | UF2 drag-drop or `west flash` |
|
||||
| SenseCAP Solar | `west build -b sensecap_solar zephcore` | UF2 drag-drop or `west flash` |
|
||||
| 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` |
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) 2025 MeshCore
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
config BOARD_WIO_TRACKER_L1_1W
|
||||
select SOC_NRF52840_QIAA
|
||||
select ZEPHCORE_UI_JOYSTICK
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Wio Tracker L1 Pro 1W battery curve — single-cell LiPo, 21 points at 5% steps.
|
||||
*
|
||||
* Inherited verbatim from boards/nrf52840/wio_tracker_l1. The divider and
|
||||
* multiplier are identical on the Pro 1W (BAT_CTL P0.04, VBAT P0.31,
|
||||
* ADC_MULTIPLIER 2.0 in both MeshCore's and Meshtastic's variants, matching
|
||||
* vbat-mv-multiplier = 7236 here), so the same curve applies -- but the
|
||||
* 4190 mV ceiling was OBSERVED on the stock L1, not on this board. The Pro 1W
|
||||
* charges through a BQ25616 rather than the stock part, so re-measure the
|
||||
* ceiling before treating the top of this table as this board's.
|
||||
*
|
||||
* The discharge shape below 4190 mV follows the generic LiPo profile.
|
||||
*/
|
||||
|
||||
#include "battery_curve.h"
|
||||
|
||||
static const uint16_t ocv_wio_tracker_l1_1w[21] = {
|
||||
4190, 4120, 4050, 4020, 3990, /* 100 .. 80% */
|
||||
3940, 3890, 3845, 3800, 3760, /* 75 .. 55% */
|
||||
3720, 3675, 3630, 3580, 3530, /* 50 .. 30% */
|
||||
3475, 3420, 3360, 3300, 3200, /* 25 .. 5% */
|
||||
3100, /* 0% */
|
||||
};
|
||||
|
||||
const battery_curve_t battery_curve_default = {
|
||||
.ocv_mv = ocv_wio_tracker_l1_1w,
|
||||
.num_points = 21,
|
||||
.num_cells = 1,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2025 ZephCore
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
|
||||
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
|
||||
|
||||
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
|
||||
|
||||
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
|
||||
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
|
||||
@@ -0,0 +1,34 @@
|
||||
# Wio Tracker L1 Pro 1W (nRF52840 + SX1262 + 1 W external PA)
|
||||
# Board-specific configuration - pins and unique features only
|
||||
#
|
||||
# Hardware:
|
||||
# - SX1262 LoRa on SPI2 (P1.14 CS, P1.07 RST, P1.10 BUSY, P0.07 DIO1)
|
||||
# NO external RXEN on this board - DIO2 drives the whole antenna switch
|
||||
# - 1 W PA behind an LDO on P0.14 (LORA_PWR_EN), held on by a devicetree
|
||||
# regulator; see the DTS for why it is never switched off
|
||||
# - L76KB GPS on UART0 (P0.26/P0.27)
|
||||
# - P25Q16H 2MB QSPI flash
|
||||
# - Battery ADC on AIN7 (P0.31), enable on P0.04
|
||||
# - SH1106 OLED on I2C0 (P0.05/P0.06, addr 0x3D)
|
||||
# - Grove I2C1 on P1.11/P1.12, 5 V boost on P0.13 (parked off)
|
||||
# - Joystick + user button + buzzer (P1.00)
|
||||
|
||||
# Board identification
|
||||
CONFIG_ZEPHCORE_BOARD_NAME="Wio Tracker L1 Pro 1W"
|
||||
|
||||
# Device Information Service model name
|
||||
CONFIG_BT_DIS_MODEL_NUMBER_STR="Wio Tracker L1 Pro 1W"
|
||||
|
||||
# SoftDevice firmware ID (s140 v7.3.0 - Seeed bootloader, same as the stock L1)
|
||||
CONFIG_ZEPHCORE_SD_FWID=0x0123
|
||||
|
||||
# TX power is left at the 22 dBm default, i.e. the full chip output into the
|
||||
# PA. Both upstreams do the same on this board and neither states a PA input
|
||||
# spec: MeshCore's env sets LORA_TX_POWER=22, Meshtastic sets
|
||||
# SX126X_MAX_POWER 22 (plus SX126X_NO_POWER_OPTIMIZATION_TABLE, which only
|
||||
# turns off RadioLib's own current-limit table and has no ZephCore analogue).
|
||||
# UNVERIFIED on hardware. If a datasheet or a measurement later shows the PA
|
||||
# saturates below 22 - the shape the W12 measurement found, see
|
||||
# memory/meshnology-w12-port.md - set CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM
|
||||
# and CONFIG_ZEPHCORE_MAX_TX_POWER_DBM here rather than leaving the knob at
|
||||
# the ceiling.
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Wio Tracker L1 Pro 1W overlay for ZephCore.
|
||||
* SoftDevice + UF2 bootloader layout.
|
||||
* Console: USB CDC ACM.
|
||||
* Battery: AIN7 = P0.31, voltage divider 2:1.
|
||||
*/
|
||||
|
||||
/* GPS standby control — P1.09 = the L76K's WAKEUP pin (schematic net
|
||||
* GNSS_WAKEUP). This is NOT a supply switch: per the L76K hardware design the
|
||||
* pin only enters/exits Standby, where the RF is off but the core and I/O
|
||||
* domain stay powered. VCC is never removed, so ephemeris/almanac/RTC are
|
||||
* retained and each wake is a warm start. Active low with an internal
|
||||
* pull-up, which is why the module runs even with this pin unconfigured.
|
||||
*
|
||||
* The luatos,air530z driver also manages this pin via on-off-gpios, but we
|
||||
* need our own alias so ZephyrSensorManager can toggle it independently
|
||||
* (driver PM suspend/resume hangs on modem_pipe ops). */
|
||||
/ {
|
||||
gps_en: gps-enable {
|
||||
compatible = "gpio-leds";
|
||||
gps_enable_pin: gps_enable {
|
||||
gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS Enable";
|
||||
};
|
||||
};
|
||||
|
||||
aliases {
|
||||
gps-enable = &gps_enable_pin;
|
||||
};
|
||||
};
|
||||
|
||||
/* 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"
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2025 MeshCore
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
board:
|
||||
name: wio_tracker_l1_1w
|
||||
full_name: Seeed Wio Tracker L1 Pro 1W
|
||||
vendor: seeed
|
||||
socs:
|
||||
- name: nrf52840
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Seeed Wio Tracker L1 pin control definitions
|
||||
* Copyright (c) 2025 ZephCore
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Pin mappings from Arduino variant.cpp:
|
||||
* - SPI2 (LoRa): SCK=P0.30, MISO=P0.03, MOSI=P0.28, CS=P1.14
|
||||
* - I2C0 (OLED): SDA=P0.06, SCL=P0.05
|
||||
* - I2C1 (Grove): SDA=P1.12 (D18), SCL=P1.11 (D17)
|
||||
* - UART (GPS): TX=P0.27, RX=P0.26
|
||||
* - QSPI: SCK=P0.21, CS=P0.25, IO0=P0.20, IO1=P0.24, IO2=P0.22, IO3=P0.23
|
||||
*/
|
||||
|
||||
&pinctrl {
|
||||
/* SPI2 (SPIM2) for LoRa SX1262 — dedicated SPI-only instance so it
|
||||
* doesn't collide with I2C0 (instance 0) or the Grove I2C1 (instance 1). */
|
||||
spi2_default: spi2_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 0, 30)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 0, 28)>,
|
||||
<NRF_PSEL(SPIM_MISO, 0, 3)>;
|
||||
};
|
||||
};
|
||||
|
||||
spi2_sleep: spi2_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(SPIM_SCK, 0, 30)>,
|
||||
<NRF_PSEL(SPIM_MOSI, 0, 28)>,
|
||||
<NRF_PSEL(SPIM_MISO, 0, 3)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* I2C0 for OLED display */
|
||||
i2c0_default: i2c0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 0, 6)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 5)>;
|
||||
};
|
||||
};
|
||||
|
||||
i2c0_sleep: i2c0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 0, 6)>,
|
||||
<NRF_PSEL(TWIM_SCL, 0, 5)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* I2C1 for Grove connector — SDA=P1.12 (D18), SCL=P1.11 (D17)
|
||||
* to match the physical Grove pinout (Arduino Wire1 / Meshtastic). */
|
||||
i2c1_default: i2c1_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 1, 12)>,
|
||||
<NRF_PSEL(TWIM_SCL, 1, 11)>;
|
||||
};
|
||||
};
|
||||
|
||||
i2c1_sleep: i2c1_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(TWIM_SDA, 1, 12)>,
|
||||
<NRF_PSEL(TWIM_SCL, 1, 11)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* UART0 for GPS L76KB at 9600 baud
|
||||
* P0.27 = GNSS_TX (GPS sends to MCU)
|
||||
* P0.26 = GNSS_RX (MCU sends to GPS)
|
||||
*/
|
||||
uart0_default: uart0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(UART_TX, 0, 27)>,
|
||||
<NRF_PSEL(UART_RX, 0, 26)>;
|
||||
bias-pull-up;
|
||||
};
|
||||
};
|
||||
|
||||
uart0_sleep: uart0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(UART_TX, 0, 27)>,
|
||||
<NRF_PSEL(UART_RX, 0, 26)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* PWM0 for buzzer on P1.00 */
|
||||
pwm0_default: pwm0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(PWM_OUT0, 1, 0)>;
|
||||
};
|
||||
};
|
||||
|
||||
pwm0_sleep: pwm0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(PWM_OUT0, 1, 0)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
|
||||
/* QSPI for external flash */
|
||||
qspi_default: qspi_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(QSPI_SCK, 0, 21)>,
|
||||
<NRF_PSEL(QSPI_IO0, 0, 20)>,
|
||||
<NRF_PSEL(QSPI_IO1, 0, 24)>,
|
||||
<NRF_PSEL(QSPI_IO2, 0, 22)>,
|
||||
<NRF_PSEL(QSPI_IO3, 0, 23)>,
|
||||
<NRF_PSEL(QSPI_CSN, 0, 25)>;
|
||||
};
|
||||
};
|
||||
|
||||
qspi_sleep: qspi_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(QSPI_SCK, 0, 21)>,
|
||||
<NRF_PSEL(QSPI_IO0, 0, 20)>,
|
||||
<NRF_PSEL(QSPI_IO1, 0, 24)>,
|
||||
<NRF_PSEL(QSPI_IO2, 0, 22)>,
|
||||
<NRF_PSEL(QSPI_IO3, 0, 23)>,
|
||||
<NRF_PSEL(QSPI_CSN, 0, 25)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Seeed Wio Tracker L1 - nRF52840 + SX1262
|
||||
* Copyright (c) 2025 ZephCore
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Hardware:
|
||||
* - nRF52840 SoC with BLE
|
||||
* - SX1262 LoRa radio + 1 W external PA (DIO2 as RF switch, TCXO 1.8V)
|
||||
* - P25Q16H 2MB QSPI flash
|
||||
* - SH1106 OLED display (I2C 0x3D)
|
||||
* - L76KB GPS module
|
||||
* - Joystick + user button
|
||||
* - Battery voltage ADC
|
||||
*
|
||||
* Differences from the stock wio_tracker_l1, all confirmed against BOTH
|
||||
* MeshCore's variants/wio-tracker-l1-1w and Meshtastic's
|
||||
* variants/nrf52840/seeed_wio_tracker_L1_Pro_1W pin maps:
|
||||
*
|
||||
* P0.14 LORA_PWR_EN new: LDO enable for the SX1262 + 1 W PA (hog, below)
|
||||
* P0.13 BOOST_EN new: Grove 5 V boost enable (hog low, below)
|
||||
* P1.15 BQ25616 STAT new: charge status, active LOW - see note below
|
||||
* P0.29 LORA_VDET replaces the stock board's P1.08 LORA_SW - NOT an
|
||||
* RF-switch line, see the &spi2 lora node
|
||||
* (no EInk: the stock board's EInk header pins D31..D37 are reused above)
|
||||
*
|
||||
* The orange mesh LED stays on P1.01. MeshCore's variant.cpp annotates it
|
||||
* "Pro 1W: P1.01 (not P1.15)", which reads like a move, but the pin-map VALUE
|
||||
* is 33 in both files - only the net NAME changed (User LED -> Mesh_LED).
|
||||
* Meshtastic has 33 as well. Nothing to change here.
|
||||
*
|
||||
* P1.15 (BQ25616 STAT) is deliberately not bound. ZephCore has no charge-state
|
||||
* consumer: mesh::ZephyrBoard::isExternalPowered() reads the nRF52 POWER
|
||||
* peripheral's VBUS detect directly, which works on this board without a pin.
|
||||
* Same treatment as thinknode_m9's charge-DONE line.
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
#include <nordic/nrf52840_qiaa.dtsi>
|
||||
#include <nordic/nrf52840_partition.dtsi>
|
||||
#include <zephyr/dt-bindings/lora/sx126x.h>
|
||||
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
|
||||
#include <zephyr/dt-bindings/input/input-event-codes.h>
|
||||
#include <zephyr/dt-bindings/pwm/pwm.h>
|
||||
#include "wio_tracker_l1_1w_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 Wio Tracker L1 Pro 1W";
|
||||
compatible = "seeed,wio-tracker-l1-1w";
|
||||
|
||||
chosen {
|
||||
zephyr,code-partition = &code_partition;
|
||||
zephyr,console = &cdc_acm_uart;
|
||||
zephyr,shell-uart = &cdc_acm_uart;
|
||||
zephyr,display = &sh1106;
|
||||
};
|
||||
|
||||
leds {
|
||||
compatible = "gpio-leds";
|
||||
|
||||
user_led: led_0 {
|
||||
gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>;
|
||||
label = "User LED";
|
||||
};
|
||||
};
|
||||
|
||||
buttons: buttons {
|
||||
compatible = "gpio-keys";
|
||||
|
||||
user_button: button_0 {
|
||||
gpios = <&gpio0 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_0>;
|
||||
label = "User Button";
|
||||
};
|
||||
|
||||
joystick_up: button_1 {
|
||||
gpios = <&gpio1 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_UP>;
|
||||
label = "Joystick Up";
|
||||
};
|
||||
|
||||
joystick_down: button_2 {
|
||||
gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_DOWN>;
|
||||
label = "Joystick Down";
|
||||
};
|
||||
|
||||
joystick_left: button_3 {
|
||||
gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_LEFT>;
|
||||
label = "Joystick Left";
|
||||
};
|
||||
|
||||
joystick_right: button_4 {
|
||||
gpios = <&gpio1 3 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_RIGHT>;
|
||||
label = "Joystick Right";
|
||||
};
|
||||
|
||||
joystick_press: button_5 {
|
||||
gpios = <&gpio1 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_ENTER>;
|
||||
label = "Joystick Press";
|
||||
};
|
||||
};
|
||||
|
||||
/* Input filter: user button long-press detection (1000ms) */
|
||||
user_btn_longpress {
|
||||
compatible = "zephyr,input-longpress";
|
||||
input = <&buttons>;
|
||||
input-codes = <INPUT_KEY_0>;
|
||||
short-codes = <INPUT_KEY_A>;
|
||||
long-codes = <INPUT_KEY_POWER>;
|
||||
long-delay-ms = <1000>;
|
||||
};
|
||||
|
||||
/* Input filter: user button multi-tap detection
|
||||
* 1 tap (400ms wait) → KEY_1 = page next on OLED
|
||||
* 2 taps (400ms wait) → KEY_B = LED heartbeat toggle
|
||||
* 3 taps (400ms wait) → KEY_D = buzzer mute toggle
|
||||
* 4 taps (400ms wait) → KEY_C = GPS on/off
|
||||
* 5 taps (immediate) → KEY_E = flood advert */
|
||||
user_btn_multitap {
|
||||
compatible = "zephcore,input-multi-tap";
|
||||
/* No 'input' phandle — listen to ALL devices.
|
||||
* KEY_A is emitted by the longpress pseudo-device,
|
||||
* not by &buttons, so we must not filter by device. */
|
||||
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>;
|
||||
};
|
||||
|
||||
/* P0.14 LORA_PWR_EN - external LDO feeding the SX1262 AND the 1 W PA.
|
||||
* The radio is dead without it, so it must be up before the LoRa driver
|
||||
* probes: REGULATOR_FIXED_INIT_PRIORITY is 75, LORA_INIT_PRIORITY is 90.
|
||||
* Same shape as rak3401_1watt's lora_pwr_enable, which gates a SKY66122
|
||||
* the same way.
|
||||
*
|
||||
* Never turned off. ZephCore keeps the radio in RX 24/7 and deliberately
|
||||
* does not PM-suspend it, and there is no fem_txgain pref in this tree to
|
||||
* expose the rail as a runtime knob (MeshCore's variant drives it from
|
||||
* `set radio.fem.txgain`; ZephCore only has fem_rxgain, and that acts on
|
||||
* lna-bypass-gpios, a different pin class entirely). */
|
||||
lora_pwr_enable: lora-pwr-enable {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "lora-pwr-enable";
|
||||
enable-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
|
||||
regulator-boot-on;
|
||||
/* Documents the LDO + PA settle requirement but does not enforce
|
||||
* it: regulator-boot-on takes regulator_common_init()'s
|
||||
* refcount-only branch, so regulator_delay() never runs. The real
|
||||
* settle time comes from the SX126x driver's own reset (5 ms
|
||||
* NRESET pulse + 5 ms wait + BUSY poll) 15 init priorities later.
|
||||
* See the identical note on rak3401_1watt. */
|
||||
startup-delay-us = <10000>;
|
||||
};
|
||||
|
||||
/* Battery voltage enable and ADC - on-demand only to save power */
|
||||
vbat_enable: vbat-enable {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "vbat-enable";
|
||||
enable-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
|
||||
/* NOT regulator-boot-on - enabled on-demand in ZephyrBoard.cpp */
|
||||
};
|
||||
|
||||
/* Battery ADC channel reference for ZephyrBoard */
|
||||
zephyr,user {
|
||||
io-channels = <&adc 7>;
|
||||
vbat-mv-multiplier = <7236>; /* 2:1 divider, 3.6V ref; +0.5% for nRF SAADC gain error */
|
||||
};
|
||||
|
||||
/* Buzzer on PWM0 channel 0 (P1.00) - no enable pin needed */
|
||||
pwmbuzzer {
|
||||
compatible = "pwm-leds";
|
||||
buzzer: buzzer {
|
||||
pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
|
||||
};
|
||||
};
|
||||
|
||||
aliases {
|
||||
led0 = &user_led;
|
||||
sw0 = &user_button;
|
||||
lora0 = &lora;
|
||||
watchdog0 = &wdt0;
|
||||
buzzer = &buzzer;
|
||||
};
|
||||
};
|
||||
|
||||
®0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
®1 {
|
||||
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
|
||||
};
|
||||
|
||||
&adc {
|
||||
status = "okay";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* Battery voltage on P0.31 (AIN7) */
|
||||
channel@7 {
|
||||
reg = <7>;
|
||||
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_AIN7>;
|
||||
zephyr,resolution = <12>;
|
||||
};
|
||||
};
|
||||
|
||||
&uicr {
|
||||
nfct-pins-as-gpios;
|
||||
};
|
||||
|
||||
&gpiote {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&gpio0 {
|
||||
status = "okay";
|
||||
|
||||
/* P0.13 BOOST_EN - Grove 5 V boost converter. Parked OFF, matching
|
||||
* Meshtastic's initVariant(); nothing in ZephCore drives 5 V Grove
|
||||
* peripherals, and the reset state of an nRF GPIO is disconnected, so
|
||||
* leaving it unbound would float the converter's enable input. The
|
||||
* Grove I2C bus itself (&i2c1, P1.11/P1.12) is unaffected - it runs at
|
||||
* 3V3 and does not need the boost. */
|
||||
grove_boost_off {
|
||||
gpio-hog;
|
||||
gpios = <13 GPIO_ACTIVE_HIGH>;
|
||||
output-low;
|
||||
line-name = "grove-boost-en";
|
||||
};
|
||||
};
|
||||
|
||||
&gpio1 {
|
||||
status = "okay";
|
||||
|
||||
/* NOTE: buzzer-off gpio-hog REMOVED - buzzer pin is now managed by PWM
|
||||
* driver. When PWM is not driving, the pin is low (no current draw). */
|
||||
|
||||
/* NOTE: GPS enable hog REMOVED — GPS works fine without it, and the
|
||||
* reason is now confirmed rather than guessed: P1.09 is the L76K's
|
||||
* WAKEUP pin, which the L76K hardware design specifies as active low
|
||||
* "with pull-up internally". Left unconfigured it floats high and the
|
||||
* module stays in Continuous mode. */
|
||||
};
|
||||
|
||||
/* PWM0 for buzzer */
|
||||
&pwm0 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&pwm0_default>;
|
||||
pinctrl-1 = <&pwm0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
};
|
||||
|
||||
/* GPS L76KB module - NOTE: Feb 6 working version had no GPIO control nodes.
|
||||
* The GPS just worked without any power control! Keeping it simple. */
|
||||
|
||||
/* Quectel L76KB GNSS on UART0 - 9600 baud (PCAS/CASIC protocol)
|
||||
* Uses Zephyr luatos,air530z driver (same PCAS command set for
|
||||
* constellation config via PCAS04, fix rate via PCAS02, etc.)
|
||||
* on-off-gpios = P1.09, the module's WAKEUP/standby pin (see &gpio1 note and
|
||||
* board.overlay) — driver drives it high for Continuous, low for Standby. */
|
||||
&uart0 {
|
||||
compatible = "nordic,nrf-uarte";
|
||||
status = "okay";
|
||||
current-speed = <9600>;
|
||||
pinctrl-0 = <&uart0_default>;
|
||||
pinctrl-1 = <&uart0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
/* No hw-flow-control - GPS doesn't use RTS/CTS */
|
||||
|
||||
gnss: gnss {
|
||||
compatible = "luatos,air530z";
|
||||
on-off-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
|
||||
/* I2C0 for onboard OLED display only.
|
||||
* External environment/power sensors live on the Grove bus (&i2c1). */
|
||||
&i2c0 {
|
||||
compatible = "nordic,nrf-twim";
|
||||
status = "okay";
|
||||
clock-frequency = <I2C_BITRATE_FAST>;
|
||||
pinctrl-0 = <&i2c0_default>;
|
||||
pinctrl-1 = <&i2c0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
/* SH1106 sends 1 cmd + 128 data = 129 bytes per page write.
|
||||
* nRF TWIM needs an internal buffer >= this size. */
|
||||
zephyr,concat-buf-size = <256>;
|
||||
zephyr,flash-buf-max-size = <256>;
|
||||
|
||||
sh1106: sh1106@3d {
|
||||
compatible = "sinowealth,sh1106";
|
||||
reg = <0x3d>;
|
||||
width = <128>;
|
||||
height = <64>;
|
||||
segment-offset = <2>; /* SH1106 has 2-column offset */
|
||||
page-offset = <0>;
|
||||
display-offset = <0>;
|
||||
multiplex-ratio = <63>;
|
||||
segment-remap;
|
||||
com-invdir;
|
||||
prechargep = <0x22>;
|
||||
};
|
||||
};
|
||||
|
||||
/* I2C1 for Grove connector — external environment/power sensors.
|
||||
* Auto-detected at runtime; absent sensors fail device_is_ready() and
|
||||
* are skipped harmlessly by ZephyrEnvSensors. */
|
||||
&i2c1 {
|
||||
compatible = "nordic,nrf-twim";
|
||||
status = "okay";
|
||||
clock-frequency = <I2C_BITRATE_FAST>;
|
||||
pinctrl-0 = <&i2c1_default>;
|
||||
pinctrl-1 = <&i2c1_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
/* All supported environment & power sensors — auto-detected at runtime */
|
||||
#include "../../common/sensors-i2c.dtsi"
|
||||
};
|
||||
|
||||
/* SPI2 (SPIM2) for LoRa SX1262.
|
||||
* Must NOT use SPIM0 (shares instance 0 with I2C0/OLED) or SPIM1 (shares
|
||||
* instance 1 with TWIM1, which drives the Grove I2C bus &i2c1). SPIM2 is a
|
||||
* dedicated SPI-only instance, so the radio and both I2C buses coexist. */
|
||||
&spi2 {
|
||||
compatible = "nordic,nrf-spim";
|
||||
status = "okay";
|
||||
cs-gpios = <&gpio1 14 GPIO_ACTIVE_LOW>;
|
||||
pinctrl-0 = <&spi2_default>;
|
||||
pinctrl-1 = <&spi2_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
lora: lora@0 {
|
||||
compatible = "semtech,sx1262";
|
||||
reg = <0>;
|
||||
reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
|
||||
busy-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>;
|
||||
dio1-gpios = <&gpio0 7 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
|
||||
|
||||
/* NO rx-enable-gpios on this board, unlike the stock Wio Tracker
|
||||
* L1 where RXEN is P1.08. DIO2 drives the whole antenna switch
|
||||
* here. The pin the stock board calls LORA_SW (P1.08) is replaced
|
||||
* on the Pro 1W by LORA_VDET on P0.29 - a PA power detector on
|
||||
* AIN5, an ANALOG INPUT, not a switch control.
|
||||
*
|
||||
* MeshCore's variants/wio-tracker-l1-1w/variant.h keeps
|
||||
* `#define SX126X_RXEN (5)` from the stock variant while
|
||||
* remapping D5 to P0.29, so it drives the detector line as an
|
||||
* RXEN output. Meshtastic's variant for the same board sets
|
||||
* SX126X_RXEN / SX126X_TXEN to RADIOLIB_NC and comments
|
||||
* "DIO2 controls antenna switch (no external RXEN/TXEN)".
|
||||
* Meshtastic is followed here. Binding P0.29 as rx-enable-gpios
|
||||
* would drive an analog detector node from a push-pull output. */
|
||||
dio2-tx-enable;
|
||||
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_1V8>;
|
||||
tcxo-power-startup-delay-ms = <5>;
|
||||
spi-max-frequency = <8000000>;
|
||||
rx-boosted;
|
||||
};
|
||||
};
|
||||
|
||||
/* QSPI Flash - P25Q16H 2MB - single-bit mode at 16MHz (matches Arduino) */
|
||||
&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 = "pp"; /* Single-bit page program (universal) */
|
||||
readoc = "fastread"; /* Single-bit fast read (universal) */
|
||||
sck-frequency = <16000000>; /* 16 MHz (matches Arduino) */
|
||||
jedec-id = [85 60 15]; /* P25Q16H */
|
||||
sfdp-bfp = [
|
||||
e5 20 f1 ff ff ff ff 01 44 eb 08 6b 08 3b 04 bb
|
||||
ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52
|
||||
10 d8 00 ff
|
||||
];
|
||||
size = <16777216>; /* 2MB = 16Mbit */
|
||||
has-dpd;
|
||||
t-enter-dpd = <3000>;
|
||||
t-exit-dpd = <50000>; /* 50ms for reliable wakeup */
|
||||
/* quad-enable not needed for single-bit mode */
|
||||
};
|
||||
};
|
||||
|
||||
zephyr_udc0: &usbd {
|
||||
compatible = "nordic,nrf-usbd";
|
||||
status = "okay";
|
||||
|
||||
cdc_acm_uart: cdc_acm_uart {
|
||||
compatible = "zephyr,cdc-acm-uart";
|
||||
};
|
||||
};
|
||||
|
||||
/* Arduino MeshCore compatible partition layout (SoftDevice v7) */
|
||||
#include "../../common/nrf52_partitions_sdv7.dtsi"
|
||||
|
||||
/* External QSPI flash partitions */
|
||||
&qspi_flash {
|
||||
partitions {
|
||||
compatible = "fixed-partitions";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
/* Full 2MB for LittleFS storage */
|
||||
qspi_storage_partition: partition@0 {
|
||||
label = "qspi_storage";
|
||||
reg = <0x00000000 0x00200000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
#include "../../common/nrf52_wakeup.dtsi"
|
||||
@@ -0,0 +1,20 @@
|
||||
identifier: wio_tracker_l1_1w/nrf52840
|
||||
name: Seeed Wio Tracker L1 Pro 1W
|
||||
type: mcu
|
||||
arch: arm
|
||||
toolchain:
|
||||
- zephyr
|
||||
- gnuarmemb
|
||||
- xtools
|
||||
ram: 256
|
||||
flash: 1024
|
||||
supported:
|
||||
- adc
|
||||
- ble
|
||||
- gpio
|
||||
- i2c
|
||||
- spi
|
||||
- usb_device
|
||||
- qspi
|
||||
- lora
|
||||
vendor: seeed
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2025 MeshCore
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Enable MPU
|
||||
CONFIG_ARM_MPU=y
|
||||
|
||||
# Enable hardware stack protection
|
||||
CONFIG_HW_STACK_PROTECTION=y
|
||||
|
||||
# Enable GPIO
|
||||
CONFIG_GPIO=y
|
||||
|
||||
# Enable UART driver
|
||||
CONFIG_SERIAL=y
|
||||
|
||||
# Enable console
|
||||
CONFIG_CONSOLE=y
|
||||
|
||||
# Build UF2 by default
|
||||
CONFIG_BUILD_OUTPUT_UF2=y
|
||||
CONFIG_USE_DT_CODE_PARTITION=y
|
||||
@@ -713,11 +713,18 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (memcmp(command, "set ", 4) == 0) {
|
||||
const char* config = &command[4];
|
||||
if (memcmp(config, "dutycycle ", 10) == 0) {
|
||||
/* Floor is 10%, not Arduino's 1%: this writes airtime_factor as
|
||||
* (100/dc)-1, and sanitizeNodePrefs() clamps that field to 0..9.
|
||||
* A 1% duty cycle stores af=99, survives until the next load, and
|
||||
* is then silently rewritten to 9 (=10%) — the node would run at
|
||||
* one duty cycle and boot into another. 10% is the lowest value
|
||||
* that round-trips, and it matches the 0..9 bound `set af` now
|
||||
* enforces directly. */
|
||||
float dc;
|
||||
if (!cliFloat(&config[10], 100.0f / (cliDefaults()->airtime_factor + 1.0f), &dc)) {
|
||||
strcpy(reply, "ERROR: dutycycle must be 1-100, or default");
|
||||
} else if (dc < 1 || dc > 100) {
|
||||
strcpy(reply, "ERROR: dutycycle must be 1-100");
|
||||
strcpy(reply, "ERROR: dutycycle must be 10-100, or default");
|
||||
} else if (dc < 10 || dc > 100) {
|
||||
strcpy(reply, "ERROR: dutycycle must be 10-100");
|
||||
} else {
|
||||
_prefs->airtime_factor = (100.0f / dc) - 1.0f;
|
||||
savePrefs();
|
||||
@@ -727,9 +734,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
snprintf(reply, CLI_REPLY_SIZE, "OK - %d.%d%%", a_int, a_frac);
|
||||
}
|
||||
} else if (memcmp(config, "af ", 3) == 0) {
|
||||
/* Range-checked (upstream f722794e), and not only for tidiness:
|
||||
* every role computes its duty cycle as 100/(af+1), so af=-1 is a
|
||||
* live divide-by-zero and af<-1 yields a negative percentage that
|
||||
* wraps through uint8_t. sanitizeNodePrefs() bounds the field to
|
||||
* 0..9 but only on load, so an out-of-range value set at runtime
|
||||
* is in force until the next reboot. */
|
||||
float af;
|
||||
if (!cliFloat(&config[3], cliDefaults()->airtime_factor, &af)) {
|
||||
strcpy(reply, "Error: expected a number or default");
|
||||
} else if (af < 0.0f || af > 9.0f) {
|
||||
strcpy(reply, "Error: af must be 0-9");
|
||||
} else {
|
||||
_prefs->airtime_factor = af;
|
||||
savePrefs();
|
||||
|
||||
@@ -131,11 +131,22 @@ Packet *StaticPoolPacketManager::allocNew()
|
||||
|
||||
void StaticPoolPacketManager::free(Packet *packet)
|
||||
{
|
||||
if (packet) _unused.add(packet, 0, 0);
|
||||
if (packet == nullptr) return;
|
||||
|
||||
/* _unused is sized to hold the whole pool, so the only way this add can
|
||||
* fail is a slot being returned twice. That is silent corruption — the
|
||||
* second free drops the packet on the floor and the pool shrinks by one
|
||||
* for the rest of the run — so say so rather than ignoring the return.
|
||||
* Upstream 1d5283de. */
|
||||
if (!_unused.add(packet, 0, 0)) {
|
||||
LOG_WRN("free: unused queue full, possible double-free");
|
||||
}
|
||||
}
|
||||
|
||||
void StaticPoolPacketManager::queueOutbound(Packet *packet, uint8_t priority, uint32_t scheduled_for)
|
||||
{
|
||||
if (packet == nullptr) return;
|
||||
|
||||
if (_send_queue.add(packet, priority, scheduled_for)) return;
|
||||
|
||||
/* Queue full — evict the least-important entry to make room.
|
||||
|
||||
Reference in New Issue
Block a user