mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-16 05:52:36 +00:00
lr2021 2.4ghz and seeed evk button fix
This commit is contained in:
@@ -204,7 +204,7 @@ Caveats: the `sys=` tally needs `CONFIG_ZEPHCORE_GPS_SAT_DIAG` (default on for r
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `tempradio <freq>,<bw>,<sf>,<cr>,<timeout_mins>` | Apply temporary radio parameters; automatically reverts after `timeout_mins`. Constraints: freq 150–2500 MHz, bw 7–500 kHz, sf 5–12, cr 5–8. Saved prefs are never mutated — concurrent `set` commands and reboots both restore the real saved values. That now covers adaptive CAD too: the visited preset runs at its own base detPeak (the learned offset addresses a different ladder there), the staircase is suspended for the duration so no visitor value can reach prefs, and the revert restores the learned offset. `get cad` shows `a:tmp` while the window is open. The anti-mute TX-starvation safety still runs, on the visitor's offset. |
|
||||
| `tempradio <freq>,<bw>,<sf>,<cr>,<timeout_mins>` | Apply temporary radio parameters; automatically reverts after `timeout_mins`. Constraints: freq 150–2500 MHz, bw 7–500 kHz (**7–1000 on LR2021 boards**), sf 5–12, cr 5–8. Saved prefs are never mutated — concurrent `set` commands and reboots both restore the real saved values. That now covers adaptive CAD too: the visited preset runs at its own base detPeak (the learned offset addresses a different ladder there), the staircase is suspended for the duration so no visitor value can reach prefs, and the revert restores the learned offset. `get cad` shows `a:tmp` while the window is open. The anti-mute TX-starvation safety still runs, on the visitor's offset. |
|
||||
|
||||
---
|
||||
|
||||
@@ -317,8 +317,8 @@ four radio parameters together, since they are one interop-critical set.
|
||||
|---------|-------------|-------------|
|
||||
| `set name <name>` | No `[ ] \ : , ? *` | Set node name |
|
||||
| `set repeat <on\|off>` | | Enable or disable packet forwarding |
|
||||
| `set radio <freq>,<bw>,<sf>,<cr>` | freq 150–2500, bw 7–500, sf 5–12, cr 5–8 | **Comma-separated**, not space-separated — spaces parse as a single argument and the command is rejected. Set radio params *(reboot required)*. If freq, bw or sf actually changed, this also performs a full `set cad.reset` and replies `OK - reboot to apply (CAD reset)` — the learned detPeak offset and its probe statistics belong to the old preset. The radio keeps running the old preset, at its learned offset, until the reboot; CAD adaptation is suspended in the meantime so it cannot re-dirty the reset. A cr-only change resets nothing. |
|
||||
| `set freq <mhz>` | 150–2500 *(USB only)* | Set frequency alone *(reboot required)*. Same CAD reset as `set radio` when the frequency actually changes. |
|
||||
| `set radio <freq>,<bw>,<sf>,<cr>` | freq 150–2500, bw 7–500 (7–1000 on LR2021), sf 5–12, cr 5–8 | **Comma-separated**, not space-separated — spaces parse as a single argument and the command is rejected. Set radio params *(reboot required)*. If freq, bw or sf actually changed, this also performs a full `set cad.reset` and replies `OK - reboot to apply (CAD reset)` — the learned detPeak offset and its probe statistics belong to the old preset. The radio keeps running the old preset, at its learned offset, until the reboot; CAD adaptation is suspended in the meantime so it cannot re-dirty the reset. A cr-only change resets nothing. |
|
||||
| `set freq <mhz>` | 150–2500 *(USB only)* | Set frequency alone *(reboot required)*. Same CAD reset as `set radio` when the frequency actually changes. On LR2021 boards a frequency at or above 1500 MHz also moves the radio to its 2.4 GHz front end — see the note below the table. |
|
||||
| `set tx <dbm>` | −9 to board max (default 30) | Set TX power |
|
||||
| `set lat <latitude>` | | Set stored latitude |
|
||||
| `set lon <longitude>` | | Set stored longitude |
|
||||
@@ -363,6 +363,14 @@ four radio parameters together, since they are one interop-critical set.
|
||||
| `set extra.sf <sf> [sf] [sf]` | up to 3 SFs, `0`/`off` clears | **LR2021 only** (`Error: unsupported` elsewhere) — LoRa *side detectors*: demodulate up to three extra spreading factors concurrently with `sf`, on the same bandwidth, so one repeater can serve several SF communities. Which SF a packet arrived on is a chip-side readout, not a guess. Chip constraints, enforced in the driver and reported as `Error: unsupported or invalid extra SF config`: every extra SF must be **greater** than `sf`, all distinct, highest−lowest ≤ 4, and at BW ≥ 500 kHz at most 2 (only 1 when `sf` ≥ 10). **Receive only, and the bridge it creates is one-way.** TX always uses the single configured `sf`, and all detectors share one bandwidth, so this is multi-SF, not multi-channel. A node with `sf 7` + `extra.sf 8` hears SF8 traffic and *does* forward it — but the forward goes out at SF7, so traffic moves SF8 -> SF7 only and nothing comes back. An SF8 node's direct messages are delivered while its ACKs never arrive, so it retries to its limit every time; adverts and one-way flood traffic propagate fine. Because every extra SF must be **greater** than `sf`, the main SF is always the lowest in the set and TX always uses it — so the bridge direction is fixed at high-SF-in / low-SF-out and **cannot be reversed**. Two nodes back to back both point the same way; there is no configuration that carries SF7 -> SF8. Treat it as a collector for slower-SF stragglers, not as a link between two SF islands. Applied live and restored on every RX entry. **Interaction with CAD:** the chip's SF constraint for CAD is the inverse of the one for RX, so the driver switches side detectors off for each LBT CAD and back on when RX re-arms — two extra SPI commands per TX, no configuration required. Persisted; a set that no longer fits after an `sf`/`bw` change is refused at boot and logged. |
|
||||
| `set prv.key <hex>` | **128-char hex** (64-byte expanded Ed25519 key) | Replace private key; derive new identity *(reboot to apply)*. The length must be exact — `fromHex` rejects anything else with `Error, bad key`. `get prv.key` returns the same 128-char form. Not USB-gated. |
|
||||
|
||||
> **2.4 GHz and the wide bandwidths — LR2021 only.** The LR2021 has a second RF path covering 1.9–2.5 GHz, and the driver picks the band automatically from the frequency: at or above **1500 MHz** it selects the high-band power amplifier, the high-band receive path and high-band front-end calibration (the same split Semtech's own reference BSP uses). Below that nothing changes.
|
||||
>
|
||||
> Transmit power is clamped to the active band's ceiling — **+22 dBm sub-GHz, +12 dBm on 2.4 GHz** — so a node carrying `set tx 22` into the 2.4 GHz band transmits at 12 while `get tx` still reports what you asked for.
|
||||
>
|
||||
> Bandwidths above 500 kHz (**203, 406, 812, 1000 kHz**, accepted as either the round number or the exact one) are the ordinary 2.4 GHz LoRa steps and exist only on this chip. The CLI refuses them on every other radio, because those drivers silently fall back to 125 kHz for a bandwidth they do not implement — which would put the node on a channel width it never reported.
|
||||
>
|
||||
> **Both ends must be on the same band.** This is a separate network, not a bridge to sub-GHz, and range is far shorter at equal power. On a board with two antenna ports (the LR2021 LoRa Plus EVK) the 2.4 GHz signal leaves the **HF** socket, so that antenna has to be connected.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -131,6 +131,57 @@ traffic.
|
||||
|
||||
---
|
||||
|
||||
## LR2021 boards can now use the 2.4 GHz band
|
||||
|
||||
The LR2021 has two radio front ends — the sub-GHz one everything has always used, and a second covering
|
||||
1.9–2.5 GHz. ZephCore only ever drove the first. Setting a 2.4 GHz frequency was accepted and then
|
||||
quietly transmitted down the sub-GHz path into a sub-GHz antenna, which radiates essentially nothing.
|
||||
|
||||
The band is now chosen automatically from the frequency. At or above 1500 MHz the driver switches to the
|
||||
high-band amplifier, the high-band receive path and high-band calibration; below it, nothing changes from
|
||||
before. The amplifier settings come from Semtech's own published measurements for each path.
|
||||
|
||||
Transmit power follows the band, because the two paths have different ceilings: **+22 dBm below
|
||||
1500 MHz, +12 dBm above it.** A node carrying a sub-GHz power setting into the 2.4 GHz band is turned
|
||||
down to 12 rather than being asked for something the hardware cannot do.
|
||||
|
||||
The wider channels that 2.4 GHz LoRa normally runs on — 203, 406, 812 and 1000 kHz — are available too,
|
||||
on LR2021 boards only. Type either the round number or the exact one. Every other radio ZephCore supports
|
||||
silently falls back to 125 kHz when handed a channel width it does not implement, so those boards still
|
||||
stop at 500 and say so.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **This is a separate network, not a bridge.** Both ends of a link must be on the same band; a 2.4 GHz
|
||||
> node cannot hear sub-GHz traffic or be heard by it. Range is also far shorter than sub-GHz at the same
|
||||
> power. Treat it as something to experiment with rather than a drop-in upgrade.
|
||||
|
||||
> [!NOTE]
|
||||
> **Connect the right antenna.** On the LR2021 LoRa Plus EVK the two bands leave through different
|
||||
> sockets — 2.4 GHz uses the HF port. On a board with only a sub-GHz antenna, 2.4 GHz has nowhere to go.
|
||||
|
||||
A first attempt at this shipped with the setters widened but the *loaders* left alone, so a 2.4 GHz
|
||||
frequency was accepted, saved, and then thrown away on the next boot — the node came back on factory
|
||||
defaults. The accepted ranges now live in one place shared by the USB CLI, the phone app's protocol,
|
||||
the observer CLI and both preference loaders, so a setting that is accepted is a setting that survives
|
||||
a reboot.
|
||||
|
||||
> [!NOTE]
|
||||
> **1625 kHz is not an LR2021 bandwidth.** Some apps list it alongside the 2.4 GHz channel widths
|
||||
> because it exists on older Semtech 2.4 GHz parts. This chip stops at 1000 kHz, so picking 1625 is
|
||||
> refused rather than quietly run at something else.
|
||||
|
||||
> [!NOTE]
|
||||
> **"Client repeat" stays sub-GHz.** That option is restricted to 433, 869.495 and 918 MHz for
|
||||
> regulatory reasons, so turning it on while tuned to 2.4 GHz is refused. Ordinary companion and
|
||||
> repeater operation is unaffected.
|
||||
|
||||
> [!NOTE]
|
||||
> **Not yet tested on air.** The band switching follows Semtech's reference implementation and the
|
||||
> amplifier tables are their published measurements, but no 2.4 GHz link has been run here yet. Reports
|
||||
> welcome.
|
||||
|
||||
---
|
||||
|
||||
## Also in this release
|
||||
|
||||
*To be filled in as further changes land.*
|
||||
|
||||
@@ -497,9 +497,12 @@ bool ZephyrDataStore::prefsLookLikeArduino() const
|
||||
memcpy(&freq, &buf[56], sizeof(float));
|
||||
sf = buf[60];
|
||||
memcpy(&bw, &buf[64], sizeof(float));
|
||||
return (freq < 300.0f || freq > 960.0f ||
|
||||
/* The Arduino misread lands at freq≈0 and sf≤1, so the sf test is what
|
||||
* actually catches it — the frequency range can safely span both RF
|
||||
* bands without weakening the detection. */
|
||||
return (freq < ZC_RADIO_FREQ_MIN_MHZ || freq > ZC_RADIO_FREQ_MAX_MHZ ||
|
||||
sf < 5 || sf > 12 ||
|
||||
bw < 6.0f || bw > 510.0f);
|
||||
bw < ZC_RADIO_BW_MIN_KHZ || bw > ZC_RADIO_BW_MAX_KHZ);
|
||||
}
|
||||
|
||||
/* Returns true if the old file-based BLE bonds file exists.
|
||||
@@ -648,9 +651,9 @@ void ZephyrDataStore::loadPrefs(NodePrefs &prefs)
|
||||
* outside the physical RF ranges below. Revert to the caller's defaults
|
||||
* so the radio starts on the correct channel and the user can pair via
|
||||
* BLE and reconfigure. */
|
||||
if (prefs.freq < 300.0f || prefs.freq > 960.0f ||
|
||||
if (prefs.freq < ZC_RADIO_FREQ_MIN_MHZ || prefs.freq > ZC_RADIO_FREQ_MAX_MHZ ||
|
||||
prefs.sf < 5 || prefs.sf > 12 ||
|
||||
prefs.bw < 6.0f || prefs.bw > 510.0f) {
|
||||
prefs.bw < ZC_RADIO_BW_MIN_KHZ || prefs.bw > ZC_RADIO_BW_MAX_KHZ) {
|
||||
LOG_WRN("loadPrefs: radio params out of range "
|
||||
"(freq=%.1f sf=%d bw=%.1f) — ignoring prefs (incompatible format?)",
|
||||
(double)prefs.freq, (int)prefs.sf, (double)prefs.bw);
|
||||
|
||||
@@ -213,6 +213,14 @@ static inline uint32_t bandwidth_to_hz(enum lora_signal_bandwidth bw)
|
||||
case BW_125_KHZ: return 125000;
|
||||
case BW_250_KHZ: return 250000;
|
||||
case BW_500_KHZ: return 500000;
|
||||
/* The wide set — 2.4 GHz territory, LR2021 only today. These return the
|
||||
* chip's TRUE bandwidths, not the enum's round names: airtime and the
|
||||
* noise-floor clamp both hang off this number, so 203 must not be
|
||||
* reported as 200. */
|
||||
case BW_200_KHZ: return 203000;
|
||||
case BW_400_KHZ: return 406000;
|
||||
case BW_800_KHZ: return 812000;
|
||||
case BW_1000_KHZ: return 1000000;
|
||||
default: return 125000;
|
||||
}
|
||||
}
|
||||
@@ -231,6 +239,18 @@ static inline enum lora_signal_bandwidth bw_khz_to_enum(uint16_t bw_khz)
|
||||
case 125: return BW_125_KHZ;
|
||||
case 250: return BW_250_KHZ;
|
||||
case 500: return BW_500_KHZ;
|
||||
/* Wide bandwidths, accepted under both spellings: the round name people
|
||||
* type and the chip's true value they may read off a datasheet. Both
|
||||
* select the same modem setting.
|
||||
*
|
||||
* Only the LR2021 implements these; every other driver here falls back
|
||||
* to 125 kHz for an unmapped enum, which would be a silent mismatch.
|
||||
* That is why the CLI only accepts a bandwidth above 500 on an LR2021
|
||||
* build — see the bw range check in CommonCLI.cpp. */
|
||||
case 200: case 203: return BW_200_KHZ;
|
||||
case 400: case 406: return BW_400_KHZ;
|
||||
case 800: case 812: return BW_800_KHZ;
|
||||
case 1000: return BW_1000_KHZ;
|
||||
default: return BW_125_KHZ;
|
||||
}
|
||||
}
|
||||
@@ -264,6 +284,10 @@ static inline int16_t noise_floor_min_dbm(uint16_t bw_khz)
|
||||
case 125: return -123; /* 10*log10(125000) = 51.0 */
|
||||
case 250: return -120; /* 10*log10(250000) = 54.0 */
|
||||
case 500: return -117; /* 10*log10(500000) = 57.0 */
|
||||
case 200: case 203: return -121; /* 10*log10(203000) = 53.1 */
|
||||
case 400: case 406: return -118; /* 10*log10(406000) = 56.1 */
|
||||
case 800: case 812: return -115; /* 10*log10(812000) = 59.1 */
|
||||
case 1000: return -114; /* 10*log10(1000000) = 60.0 */
|
||||
default: return -123; /* matches bw_khz_to_enum's 125 kHz fallback */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2805,8 +2805,14 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
// If repeat requested, validate frequency against allowed bands
|
||||
if (repeat && !isValidClientRepeatFreq(freq)) {
|
||||
sendPacketError(ERR_ILLEGAL_ARG);
|
||||
} else if (freq >= 150000 && freq <= 2500000 &&
|
||||
bw >= 7000 && bw <= 500000 &&
|
||||
/* freq arrives in kHz, bw in Hz. Same limits as the USB
|
||||
* CLI and the prefs loader — see NodePrefs.h. Accepting
|
||||
* here what loadPrefs() later rejects is how a 2.4 GHz
|
||||
* setting used to vanish on reboot. */
|
||||
} else if (freq >= (uint32_t)(ZC_RADIO_FREQ_MIN_MHZ * 1000.0f) &&
|
||||
freq <= (uint32_t)(ZC_RADIO_FREQ_MAX_MHZ * 1000.0f) &&
|
||||
bw >= (uint32_t)(ZC_RADIO_BW_MIN_KHZ * 1000.0f) &&
|
||||
bw <= (uint32_t)(ZC_RADIO_BW_MAX_KHZ * 1000.0f) &&
|
||||
sf >= 5 && sf <= 12 &&
|
||||
cr >= 5 && cr <= 8) {
|
||||
/* Coding rate is deliberately not in this test:
|
||||
|
||||
@@ -470,9 +470,11 @@ bool ObserverMesh::handleCLI(const char *command, char *reply, int reply_size)
|
||||
} else {
|
||||
bw = (float)atof(val);
|
||||
}
|
||||
/* Lower bound 7 kHz mirrors loadPrefs()'s validator — see the freq
|
||||
* case above for why the CLI must not accept what it will reject. */
|
||||
if (bw >= 7.0f && bw <= 500.0f) {
|
||||
/* Bounds mirror loadPrefs()'s validator — see the freq
|
||||
* case above for why the CLI must not accept what it will reject.
|
||||
* Shared definition in NodePrefs.h; the upper bound is 1000 on
|
||||
* LR2021 builds, which are the only ones with the wide set. */
|
||||
if (bw >= ZC_RADIO_BW_MIN_KHZ && bw <= ZC_RADIO_BW_MAX_KHZ) {
|
||||
_prefs.bw = bw;
|
||||
_store->savePrefs(_prefs);
|
||||
((LoRaRadioBase *)_radio)->reconfigureWithParams(
|
||||
|
||||
@@ -320,9 +320,9 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) {
|
||||
prefs.node_name, (double)prefs.freq, prefs.sf, (double)prefs.bw, prefs.tx_power_dbm);
|
||||
|
||||
/* Validate radio params - use defaults if garbage */
|
||||
if (prefs.freq < 300.0f || prefs.freq > 1000.0f ||
|
||||
if (prefs.freq < ZC_RADIO_FREQ_MIN_MHZ || prefs.freq > ZC_RADIO_FREQ_MAX_MHZ ||
|
||||
prefs.sf < 5 || prefs.sf > 12 ||
|
||||
prefs.bw < 7.0f || prefs.bw > 500.0f) {
|
||||
prefs.bw < ZC_RADIO_BW_MIN_KHZ || prefs.bw > ZC_RADIO_BW_MAX_KHZ) {
|
||||
LOG_WRN("Invalid radio params in prefs, using defaults: freq=%.3f sf=%u bw=%.1f",
|
||||
(double)prefs.freq, prefs.sf, (double)prefs.bw);
|
||||
prefs.freq = 869.618f;
|
||||
|
||||
@@ -39,9 +39,19 @@ CONFIG_LORA_LOG_LEVEL_DBG=y
|
||||
CONFIG_I2C_LOG_LEVEL_INF=y
|
||||
CONFIG_DISPLAY_LOG_LEVEL_DBG=y
|
||||
|
||||
# K1 on P2.09 has no external pull-up and no hardware debounce — confirm the
|
||||
# presses arrive and that the longpress/multitap chain sees them.
|
||||
CONFIG_INPUT_LOG_LEVEL_DBG=y
|
||||
# Buttons: log real input EVENTS, not the polling.
|
||||
#
|
||||
# CONFIG_INPUT_LOG_LEVEL_DBG was the obvious choice and is the wrong one here.
|
||||
# The buttons are polled (P2 has no GPIOTE — see the DTS), and at DBG the
|
||||
# gpio_keys driver prints "gpio_keys_poll_pin: pin_state=0, new_pressed=0" on
|
||||
# every single poll: 33 lines a second, drowning the log the rest of this file
|
||||
# exists to produce.
|
||||
#
|
||||
# INPUT_EVENT_DUMP instead logs one info line per actual input event, so a
|
||||
# press shows up and silence means silence. If pressing a button produces
|
||||
# nothing at all, that is a wiring or contact answer, not a filter-chain one.
|
||||
CONFIG_INPUT_LOG_LEVEL_INF=y
|
||||
CONFIG_INPUT_EVENT_DUMP=y
|
||||
|
||||
# This board's RRAM partition layout is new: app links at 0x0, LittleFS at
|
||||
# 0x14E000. Show why a mount failed, not just that it did.
|
||||
|
||||
@@ -90,20 +90,78 @@
|
||||
buttons: buttons {
|
||||
compatible = "gpio-keys";
|
||||
|
||||
/*
|
||||
* POLLED, not interrupt-driven, and this is forced by the SoC.
|
||||
*
|
||||
* On the nRF54L15 only P0 and P1 have a GPIOTE instance behind
|
||||
* them — `nrf54l_05_10_15.dtsi` gives gpio0 `gpiote30` and gpio1
|
||||
* `gpiote20`, and gpio2 gets nothing. The nRF GPIO driver turns
|
||||
* that into a hard refusal: `gpio_nrfx_pin_interrupt_configure()`
|
||||
* opens with `if (!use_gpiote(cfg)) return -ENOTSUP;`. So no pin
|
||||
* on P2 can raise a GPIO interrupt, `gpio_keys_init()` fails on
|
||||
* it, and the button is simply dead.
|
||||
*
|
||||
* K1 is hardwired to D14 = P2.09 on the expansion board (the XIAO
|
||||
* schematic labels the pin "P2.09/D14"), so it cannot be moved.
|
||||
* Polling is the only way to use it, and `debounce-interval-ms`
|
||||
* doubles as the poll period in this mode. 30 ms costs ~33 short
|
||||
* wakes a second — nothing beside continuous LoRa RX — and sits
|
||||
* far inside the 400 ms multi-tap window and 1 s long-press hold.
|
||||
*
|
||||
* Cost us the first bring-up: display worked, buttons did not.
|
||||
*
|
||||
* Nothing else on this board is affected. The LR2021 IRQ is on
|
||||
* P1.04 (gpiote20, and proven working), and the other P2 pins here
|
||||
* are either outputs (LED P2.00, rfsw P2.03/P2.05) or owned by
|
||||
* pinctrl (spi00 on P2.01/02/04) — none of which need GPIOTE.
|
||||
*
|
||||
* Both buttons live in this one node, so both are polled. That
|
||||
* costs nothing extra — the driver walks every key from a single
|
||||
* work item — and it keeps one `input` phandle for the long-press
|
||||
* filter below, which takes exactly one.
|
||||
*/
|
||||
polling-mode;
|
||||
debounce-interval-ms = <30>;
|
||||
|
||||
/*
|
||||
* K1 on the expansion board: a bare TS-1111A between P2.09 and
|
||||
* GND, no external pull-up and no hardware debounce, so the
|
||||
* internal pull-up is required.
|
||||
*
|
||||
* The XIAO core board carries a second user button of its own on
|
||||
* P0.00 (also pull-up / active low). It is deliberately left out:
|
||||
* the UI is built around one button, and K1 is the one the user
|
||||
* can actually reach with the boards stacked.
|
||||
* Both ends of this net are confirmed against the schematics —
|
||||
* expansion board socket pin 25 carries K1_USER_PB and is
|
||||
* labelled D14, and the XIAO's test-point table on sheet 5 reads
|
||||
* "D14 TP16 -> P2.09". But note HOW it gets there: D11..D15 are
|
||||
* not part of the XIAO's soldered castellation. They reach the
|
||||
* core board through test pads that the expansion board touches
|
||||
* with pogo pins ("2x 1x4 Pogo Pins" on the schematic). That is a
|
||||
* contact interface, and a XIAO that is not fully seated reads
|
||||
* exactly like a button nobody is pressing: the internal pull-up
|
||||
* holds the pin high forever.
|
||||
*/
|
||||
user_button: button_0 {
|
||||
gpios = <&gpio2 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_0>;
|
||||
label = "User Button";
|
||||
label = "K1 (expansion board)";
|
||||
};
|
||||
|
||||
/*
|
||||
* The XIAO core board's own USR button, P0.00 — a normally
|
||||
* soldered part with no pogo-pin interface in the way, which
|
||||
* makes it the one that works regardless of how the stack is
|
||||
* seated. Same key code as K1, so either drives the UI and the
|
||||
* user never has to care which they pressed.
|
||||
*
|
||||
* It sits on gpio0/gpiote30 and so could be interrupt-driven,
|
||||
* but polling-mode is a property of the whole gpio-keys node.
|
||||
* Splitting it into a second node to save 33 short wakes a second
|
||||
* would also need a second long-press filter, which is not worth
|
||||
* it next to continuous LoRa RX.
|
||||
*/
|
||||
xiao_button: button_1 {
|
||||
gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
zephyr,code = <INPUT_KEY_0>;
|
||||
label = "USR (XIAO core board)";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
#include "wifi_ota.h"
|
||||
#endif
|
||||
|
||||
/* Radio parameter ranges come from NodePrefs.h so the CLI, the BLE companion
|
||||
* protocol and the prefs loader cannot drift apart. */
|
||||
|
||||
LOG_MODULE_REGISTER(zephcore_cli, CONFIG_ZEPHCORE_DATASTORE_LOG_LEVEL);
|
||||
|
||||
// Helper: robust atoi
|
||||
@@ -428,12 +431,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
|
||||
uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
|
||||
int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0;
|
||||
if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 &&
|
||||
cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) {
|
||||
if (freq >= ZC_RADIO_FREQ_MIN_MHZ && freq <= ZC_RADIO_FREQ_MAX_MHZ && sf >= 5 && sf <= 12 &&
|
||||
cr >= 5 && cr <= 8 && bw >= ZC_RADIO_BW_MIN_KHZ && bw <= ZC_RADIO_BW_MAX_KHZ && temp_timeout_mins > 0) {
|
||||
_callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins);
|
||||
snprintf(reply, CLI_REPLY_SIZE, "OK - temp params for %d mins", temp_timeout_mins);
|
||||
} else {
|
||||
strcpy(reply, "Error: freq 150-2500, bw 7-500, sf 5-12, cr 5-8, timeout>0");
|
||||
snprintf(reply, CLI_REPLY_SIZE, "Error: freq 150-2500, bw %s, sf 5-12, cr 5-8, timeout>0", ZC_RADIO_BW_RANGE_STR);
|
||||
}
|
||||
} else if (memcmp(command, "password ", 9) == 0) {
|
||||
StrHelper::strzcpy(_prefs->password, &command[9], sizeof(_prefs->password));
|
||||
@@ -1017,8 +1020,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
float bw = want_def ? cliDefaults()->bw : (num > 1 ? strtof(parts[1], nullptr) : 0.0f);
|
||||
uint8_t sf = want_def ? cliDefaults()->sf : (num > 2 ? (uint8_t)atoi(parts[2]) : 0);
|
||||
uint8_t cr = want_def ? cliDefaults()->cr : (num > 3 ? (uint8_t)atoi(parts[3]) : 0);
|
||||
if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 &&
|
||||
cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) {
|
||||
if (freq >= ZC_RADIO_FREQ_MIN_MHZ && freq <= ZC_RADIO_FREQ_MAX_MHZ && sf >= 5 && sf <= 12 &&
|
||||
cr >= 5 && cr <= 8 && bw >= ZC_RADIO_BW_MIN_KHZ && bw <= ZC_RADIO_BW_MAX_KHZ) {
|
||||
/* Snapshot old params, then mutate _prefs and save so later
|
||||
* savePrefs() calls (set af, set name, ...) don't clobber
|
||||
* the new values with stale RAM. Freeze the running radio on
|
||||
|
||||
@@ -16,6 +16,38 @@
|
||||
* and this header is C++; see the note there. */
|
||||
#include "led_gate.h"
|
||||
|
||||
/* ── Accepted radio parameter ranges ──────────────────────────────────
|
||||
*
|
||||
* One definition, used by everything that accepts or validates a radio
|
||||
* preset: the USB CLI, the BLE companion protocol, the observer CLI, and the
|
||||
* load-time sanity checks in both datastores.
|
||||
*
|
||||
* It lives here because these ranges were previously copy-pasted into six
|
||||
* places and drifted. That drift was not theoretical: when 2.4 GHz support
|
||||
* landed, the setters were widened and the *loaders* were not, so a node
|
||||
* accepted `set freq 2450`, saved it, and then silently reverted to factory
|
||||
* defaults on the next boot because the load-time guard still capped at
|
||||
* 960 MHz. A setter must never accept what the loader will throw away.
|
||||
*
|
||||
* FREQ spans both LR2021 RF paths — the sub-GHz one (150-960 MHz) and the
|
||||
* high band (1.9-2.5 GHz). Boards whose radio cannot reach the high band are
|
||||
* limited by their own driver, not by this range.
|
||||
*
|
||||
* BW_MAX is the one value that is per-build. Only the LR2021 implements the
|
||||
* wide 203/406/812/1000 kHz set; every other driver here maps an unknown
|
||||
* bandwidth to 125 kHz instead of refusing it, so accepting 812 on those
|
||||
* boards would put a node on a channel width it never reported. */
|
||||
#define ZC_RADIO_FREQ_MIN_MHZ 150.0f
|
||||
#define ZC_RADIO_FREQ_MAX_MHZ 2500.0f
|
||||
#define ZC_RADIO_BW_MIN_KHZ 7.0f
|
||||
#ifdef CONFIG_ZEPHCORE_RADIO_LR2021
|
||||
#define ZC_RADIO_BW_MAX_KHZ 1000.0f
|
||||
#define ZC_RADIO_BW_RANGE_STR "7-1000"
|
||||
#else
|
||||
#define ZC_RADIO_BW_MAX_KHZ 500.0f
|
||||
#define ZC_RADIO_BW_RANGE_STR "7-500"
|
||||
#endif
|
||||
|
||||
#define TELEM_MODE_DENY 0
|
||||
#define TELEM_MODE_ALLOW_FLAGS 1
|
||||
#define TELEM_MODE_ALLOW_ALL 2
|
||||
|
||||
@@ -273,6 +273,13 @@ static lr20xx_radio_lora_bw_t bw_enum_to_lr20xx(enum lora_signal_bandwidth bw)
|
||||
case BW_125_KHZ: return LR20XX_RADIO_LORA_BW_125;
|
||||
case BW_250_KHZ: return LR20XX_RADIO_LORA_BW_250;
|
||||
case BW_500_KHZ: return LR20XX_RADIO_LORA_BW_500;
|
||||
/* The wide set. Nothing in the sub-GHz plans reaches these, but they are
|
||||
* the ordinary bandwidths for 2.4 GHz LoRa — 203/406/812 are the
|
||||
* SX128x-compatible steps, which is what other 2.4 GHz gear speaks. */
|
||||
case BW_200_KHZ: return LR20XX_RADIO_LORA_BW_203;
|
||||
case BW_400_KHZ: return LR20XX_RADIO_LORA_BW_406;
|
||||
case BW_800_KHZ: return LR20XX_RADIO_LORA_BW_812;
|
||||
case BW_1000_KHZ: return LR20XX_RADIO_LORA_BW_1000;
|
||||
default: return LR20XX_RADIO_LORA_BW_125;
|
||||
}
|
||||
}
|
||||
@@ -327,6 +334,13 @@ static float bw_enum_to_khz(enum lora_signal_bandwidth bw)
|
||||
case BW_125_KHZ: return 125.0f;
|
||||
case BW_250_KHZ: return 250.0f;
|
||||
case BW_500_KHZ: return 500.0f;
|
||||
/* Real chip bandwidths, not the enum's round names — these feed the LDRO
|
||||
* symbol-time test and the airtime maths, so they have to be the values
|
||||
* the modem actually runs at (DS Table: 203, 406, 812, 1000 kHz). */
|
||||
case BW_200_KHZ: return 203.0f;
|
||||
case BW_400_KHZ: return 406.0f;
|
||||
case BW_800_KHZ: return 812.0f;
|
||||
case BW_1000_KHZ: return 1000.0f;
|
||||
default: return 125.0f;
|
||||
}
|
||||
}
|
||||
@@ -496,14 +510,118 @@ BUILD_ASSERT(ARRAY_SIZE(pa_lf_table) ==
|
||||
"PA LF table must span [LR20XX_LF_MIN_PWR, LR20XX_LF_MAX_PWR] "
|
||||
"exactly — an off-by-one here silently mis-sets every power level");
|
||||
|
||||
/* ── PA power lookup table (HF / 2.4 GHz + S-band) ────────────────────
|
||||
*
|
||||
* Semtech's LR20XX_PA_HF_CFG_TABLE from the same header, indexed -17..+12 dBm.
|
||||
* Transcribed mechanically, and cross-checked against the independently
|
||||
* published `tx-power-cfg-hf` in Semtech's Wio-LR2021 shield overlay
|
||||
* (usp_zephyr, measured @2445 MHz) — the two agree entry for entry.
|
||||
*
|
||||
* Field reuse is Semtech's, not ours: on the HF path `pa_duty_cycle` goes to
|
||||
* pa_hf_duty_cycle, and `pa_lf_slices` carries the LF PA's "unused" default of
|
||||
* 7 (DS §7.4.1). pa_lf_duty_cycle takes its own unused default of 6. */
|
||||
#define LR20XX_HF_MIN_PWR (-17)
|
||||
#define LR20XX_HF_MAX_PWR 12
|
||||
|
||||
static const struct lr20xx_pa_pwr_entry pa_hf_table[] = {
|
||||
{ -39, 29, 7 }, /* -17 dBm */
|
||||
{ -39, 16, 7 }, /* -16 dBm */
|
||||
{ -35, 19, 7 }, /* -15 dBm */
|
||||
{ -32, 19, 7 }, /* -14 dBm */
|
||||
{ -29, 19, 7 }, /* -13 dBm */
|
||||
{ -27, 16, 7 }, /* -12 dBm */
|
||||
{ -24, 17, 7 }, /* -11 dBm */
|
||||
{ -22, 16, 7 }, /* -10 dBm */
|
||||
{ -19, 18, 7 }, /* -9 dBm */
|
||||
{ -17, 16, 7 }, /* -8 dBm */
|
||||
{ -14, 21, 7 }, /* -7 dBm */
|
||||
{ -12, 18, 7 }, /* -6 dBm */
|
||||
{ -7, 30, 7 }, /* -5 dBm */
|
||||
{ -8, 16, 7 }, /* -4 dBm */
|
||||
{ -5, 24, 7 }, /* -3 dBm */
|
||||
{ -2, 27, 7 }, /* -2 dBm */
|
||||
{ 1, 29, 7 }, /* -1 dBm */
|
||||
{ 4, 30, 7 }, /* 0 dBm */
|
||||
{ 6, 30, 7 }, /* 1 dBm */
|
||||
{ 7, 28, 7 }, /* 2 dBm */
|
||||
{ 8, 25, 7 }, /* 3 dBm */
|
||||
{ 10, 25, 7 }, /* 4 dBm */
|
||||
{ 15, 31, 7 }, /* 5 dBm */
|
||||
{ 16, 30, 7 }, /* 6 dBm */
|
||||
{ 18, 30, 7 }, /* 7 dBm */
|
||||
{ 21, 31, 7 }, /* 8 dBm */
|
||||
{ 22, 30, 7 }, /* 9 dBm */
|
||||
{ 24, 30, 7 }, /* 10 dBm */
|
||||
{ 24, 26, 7 }, /* 11 dBm */
|
||||
{ 24, 16, 7 }, /* 12 dBm */
|
||||
};
|
||||
|
||||
BUILD_ASSERT(ARRAY_SIZE(pa_hf_table) ==
|
||||
(size_t)(LR20XX_HF_MAX_PWR - LR20XX_HF_MIN_PWR + 1),
|
||||
"PA HF table must span [LR20XX_HF_MIN_PWR, LR20XX_HF_MAX_PWR] "
|
||||
"exactly — an off-by-one here silently mis-sets every power level");
|
||||
|
||||
/* DS §7.4.1: "Only values from 16-31 are authorized… If the HF PA is not used,
|
||||
* set the parameter to 16 (default)." Semtech's BSP writes 16 here too. */
|
||||
#define LR20XX_PA_HF_DUTY_CYCLE_UNUSED 16
|
||||
|
||||
static void lr20xx_get_pa_cfg_for_power(int8_t power_dbm,
|
||||
/* Unused-LF defaults, for when the HF PA is the one driving. Same source. */
|
||||
#define LR20XX_PA_LF_DUTY_CYCLE_UNUSED 6
|
||||
#define LR20XX_PA_LF_SLICES_UNUSED 7
|
||||
|
||||
/* Which of the chip's two RF paths a frequency belongs to.
|
||||
*
|
||||
* 1.5 GHz is Semtech's own split, taken from ral_lr20xx_bsp.c, which uses the
|
||||
* identical test in get_tx_cfg and get_rx_cfg. It sits in the dead zone
|
||||
* between the LF path (150-960 MHz) and the HF path (1.9-2.5 GHz), so the exact
|
||||
* value never has to be argued about — anything a board can legally tune to
|
||||
* lands unambiguously on one side. */
|
||||
#define LR20XX_HF_BAND_THRESHOLD_HZ 1500000000U
|
||||
|
||||
static inline bool lr20xx_freq_is_hf(uint32_t freq_hz)
|
||||
{
|
||||
return freq_hz >= LR20XX_HF_BAND_THRESHOLD_HZ;
|
||||
}
|
||||
|
||||
static inline lr20xx_radio_common_rx_path_t lr20xx_rx_path_for(uint32_t freq_hz)
|
||||
{
|
||||
return lr20xx_freq_is_hf(freq_hz) ? LR20XX_RADIO_COMMON_RX_PATH_HF
|
||||
: LR20XX_RADIO_COMMON_RX_PATH_LF;
|
||||
}
|
||||
|
||||
/* Build the PA configuration for a power level on whichever path the frequency
|
||||
* selects. Mirrors ral_lr20xx_bsp_get_tx_cfg(): band first, then clamp to that
|
||||
* band's own limits, then index that band's own table.
|
||||
*
|
||||
* The clamp matters as much as the table. The two paths have different
|
||||
* ceilings — +22 dBm on LF, +12 dBm on HF — so a node carrying the sub-GHz
|
||||
* default of 22 into the 2.4 GHz band must be pulled down to 12 rather than
|
||||
* indexed off the end of the HF table. */
|
||||
static void lr20xx_get_pa_cfg_for_power(int8_t power_dbm, uint32_t freq_hz,
|
||||
lr20xx_radio_common_pa_cfg_t *pa,
|
||||
int8_t *half_power_out)
|
||||
{
|
||||
if (lr20xx_freq_is_hf(freq_hz)) {
|
||||
if (power_dbm < LR20XX_HF_MIN_PWR) {
|
||||
power_dbm = LR20XX_HF_MIN_PWR;
|
||||
}
|
||||
if (power_dbm > LR20XX_HF_MAX_PWR) {
|
||||
power_dbm = LR20XX_HF_MAX_PWR;
|
||||
}
|
||||
|
||||
const struct lr20xx_pa_pwr_entry *e =
|
||||
&pa_hf_table[power_dbm - LR20XX_HF_MIN_PWR];
|
||||
|
||||
pa->pa_sel = LR20XX_RADIO_COMMON_PA_SEL_HF;
|
||||
pa->pa_lf_mode = LR20XX_RADIO_COMMON_PA_LF_MODE_FSM;
|
||||
pa->pa_lf_duty_cycle = LR20XX_PA_LF_DUTY_CYCLE_UNUSED;
|
||||
pa->pa_lf_slices = e->pa_lf_slices;
|
||||
pa->pa_hf_duty_cycle = e->pa_duty_cycle;
|
||||
|
||||
*half_power_out = e->half_power;
|
||||
return;
|
||||
}
|
||||
|
||||
if (power_dbm < LR20XX_LF_MIN_PWR) {
|
||||
power_dbm = LR20XX_LF_MIN_PWR;
|
||||
}
|
||||
@@ -720,13 +838,12 @@ static lr20xx_status_t lr20xx_calibrate_front_end(void *ctx, uint32_t freq_hz)
|
||||
* the CMD_PERR observed across exactly this call, with a clean error
|
||||
* word and the chip in STBY_RC (so not a mode violation). Zeroed slots
|
||||
* are documented as no-ops (DS §6.4.2). */
|
||||
const lr20xx_radio_common_rx_path_t cal_path =
|
||||
lr20xx_rx_path_for(freq_hz);
|
||||
lr20xx_radio_common_front_end_calibration_value_t fe_cal[3] = {
|
||||
{ .rx_path = LR20XX_RADIO_COMMON_RX_PATH_LF,
|
||||
.frequency_in_hertz = first },
|
||||
{ .rx_path = LR20XX_RADIO_COMMON_RX_PATH_LF,
|
||||
.frequency_in_hertz = second },
|
||||
{ .rx_path = LR20XX_RADIO_COMMON_RX_PATH_LF,
|
||||
.frequency_in_hertz = 0 },
|
||||
{ .rx_path = cal_path, .frequency_in_hertz = first },
|
||||
{ .rx_path = cal_path, .frequency_in_hertz = second },
|
||||
{ .rx_path = cal_path, .frequency_in_hertz = 0 },
|
||||
};
|
||||
|
||||
for (int i = 0; i < LR20XX_MAX_CAL_ATTEMPTS; i++) {
|
||||
@@ -913,9 +1030,13 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data,
|
||||
CHECK_CMD(ctx, "set_rf_freq");
|
||||
|
||||
/* Always configure the RX path after setting frequency
|
||||
* (reference does this on every set_rf_freq call). */
|
||||
* (reference does this on every set_rf_freq call).
|
||||
*
|
||||
* The path follows the frequency: ral_lr20xx_bsp_get_rx_cfg() picks HF
|
||||
* at or above 1.5 GHz and LF below. Getting this wrong is not subtle —
|
||||
* receiving 2.4 GHz down the sub-GHz path is simply deaf. */
|
||||
rc = lr20xx_radio_common_set_rx_path(
|
||||
ctx, LR20XX_RADIO_COMMON_RX_PATH_LF,
|
||||
ctx, lr20xx_rx_path_for(mc->frequency),
|
||||
data->rx_boost_enabled
|
||||
? LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_7
|
||||
: LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_NONE);
|
||||
@@ -963,12 +1084,14 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data,
|
||||
CHECK_CMD(ctx, "set_syncword");
|
||||
|
||||
if (tx_mode) {
|
||||
/* PA config + TX params from Semtech's reference LF table.
|
||||
/* PA config + TX params from Semtech's reference tables, picked
|
||||
* per band from the frequency just programmed above.
|
||||
* DS §7.4.3: SetPaConfig must precede SetTxParams. */
|
||||
lr20xx_radio_common_pa_cfg_t pa;
|
||||
int8_t half_power;
|
||||
|
||||
lr20xx_get_pa_cfg_for_power(mc->tx_power, &pa, &half_power);
|
||||
lr20xx_get_pa_cfg_for_power(mc->tx_power, mc->frequency,
|
||||
&pa, &half_power);
|
||||
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,
|
||||
@@ -1081,7 +1204,7 @@ static void lr20xx_recalibrate_locked(struct lr20xx_data *data)
|
||||
* for the same reason. One command. */
|
||||
if (data->rx_boost_enabled) {
|
||||
lr20xx_radio_common_set_rx_path(
|
||||
ctx, LR20XX_RADIO_COMMON_RX_PATH_LF,
|
||||
ctx, lr20xx_rx_path_for(data->modem_cfg.frequency),
|
||||
LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_7);
|
||||
data->rx_boost_applied = true;
|
||||
}
|
||||
@@ -3055,8 +3178,12 @@ void lr20xx_set_rx_boost(const struct device *dev, bool enable)
|
||||
|
||||
if (data->in_rx_mode && data->configured) {
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
/* Keep the band: SetRxPath carries both, so re-sending it with
|
||||
* a hardcoded LF would quietly drop a 2.4 GHz node onto the
|
||||
* sub-GHz path the moment `set rx.boost` was toggled. */
|
||||
lr20xx_radio_common_set_rx_path(
|
||||
&data->hal_ctx, LR20XX_RADIO_COMMON_RX_PATH_LF,
|
||||
&data->hal_ctx,
|
||||
lr20xx_rx_path_for(data->modem_cfg.frequency),
|
||||
enable ? LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_7
|
||||
: LR20XX_RADIO_COMMON_RX_PATH_BOOST_MODE_NONE);
|
||||
data->rx_boost_applied = enable;
|
||||
|
||||
Reference in New Issue
Block a user