PM debug v1

This commit is contained in:
liquidraver
2026-07-31 15:17:14 +02:00
parent 3f2a4f762d
commit 5f35d8e013
3 changed files with 131 additions and 4 deletions
+8
View File
@@ -49,6 +49,14 @@
# excellent on a current meter. Check that log line before trusting any
# measurement.
#
# PAD HOLD — light sleep powers down RTC_PERIPH, so every RTC-capable pad
# (GPIO 0-21, which is all of them on these boards) loses its level unless
# held. The paired overlay holds the SX1262 NSS and RESET; without that, a
# floating NSS lets the sleeping radio clock garbage into its own retention
# RAM and the node transmits ~30 dB down until the next reboot. The overlay
# hardcodes NSS GPIO8 / RESET GPIO12 / &spi2 — verify a new board matches
# before adding it to the allowlist below.
#
# Also required and supplied by the paired pm_esp32.overlay:
# - &rtc_timer enabled. soc/espressif/common/power.c refuses to sleep without
# it, logging "Sleep skipped. Make sure '&rtc_timer' is enabled as a wakeup
+57
View File
@@ -5,6 +5,9 @@
* enough; this file does not need to be listed separately.
*/
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/gpio/espressif-esp32-gpio.h>
/*
* The RTC timer is the wake source for every timed sleep. Upstream leaves it
* status = "disabled" (zephyr/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi),
@@ -34,3 +37,57 @@
&gpio0 {
wakeup-source;
};
/*
* Hold the SX1262 chip-select across light sleep.
*
* Light sleep powers down RTC_PERIPH on this SoC, and every LoRa pin on the
* allowlisted boards is an RTC-capable pad (GPIO 0-21), so their levels are
* NOT retained unless explicitly held. The chain:
*
* 1. patches/zephyr/0012 passes GPIO_INT_WAKEUP on DIO1. On the S3 that
* lands on esp_sleep_enable_ext1_wakeup_io() (drivers/gpio/gpio_esp32.c,
* SOC_PM_SUPPORT_EXT1_WAKEUP), i.e. RTC_EXT1_TRIG_EN.
* 2. esp_sleep_start() only forces ESP_PD_DOMAIN_RTC_PERIPH on for
* RTC_EXT0_TRIG_EN | RTC_GPIO_TRIG_EN. EXT1 is not in that mask, so the
* domain is left AUTO and powered OFF for the whole sleep.
* 3. ext1_wakeup_prepare() then calls rtcio_hal_hold_enable() for the EXT1
* pin *only*, and only because RTC_PERIPH is off — IDF's own code
* treating "RTC_PERIPH down" as "this pad cannot hold its own state".
* DIO1 survives; nothing else does.
* 4. Zephyr's general mechanism, esp32_sleep_gpio_prepare(), holds only pins
* carrying ESP32_GPIO_SLEEP_HOLD_EN. Hence this override.
*
* NSS is the pin that matters. Undriven, it floats, and the SX1262 samples
* SPI commands whenever NSS is low — so a sleeping chip sitting in warm-start
* sleep with its entire configuration (PA config, OCP, SetTxParams) in
* retention RAM is exposed to whatever the floating bus does. Nothing in the
* driver ever re-issues SetTxParams: lora_config() is its only writer and
* LoRaRadioBase::configure() short-circuits on every transmit after the first,
* so a corrupted TX power persists until reboot or a `set tx <new value>`.
* Symptom is a node that still relays but transmits ~30 dB down.
*
* Holding NSS high for the duration deselects the chip, which also makes SCK
* and MOSI harmless — nothing can be clocked in while CS is deasserted, so
* those pins do not need the pinctrl-side `sleep-hold-en`.
*
* All five allowlisted boards (heltec_wifi_lora32_v3/v4/v43,
* heltec_wireless_tracker/_v2) wire the radio identically — &spi2, NSS GPIO8,
* RESET GPIO12 — which is what lets this live in one shared overlay. Re-check
* that before adding a board to the allowlist in CMakeLists.txt.
*/
&spi2 {
cs-gpios = <&gpio0 8 (GPIO_ACTIVE_LOW | ESP32_GPIO_SLEEP_HOLD_EN)>;
};
/*
* RESET is open-drain active-low against the SX1262's internal pull-up, so an
* unheld pad going high-Z already reads as "not reset" and this is belt and
* braces rather than a known fault. It costs nothing and removes the question
* of what the pad does on the way into and out of power-down — a spurious low
* here would reset the radio outright.
*/
&lora0 {
reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW |
ESP32_GPIO_SLEEP_HOLD_EN)>;
};
@@ -71,7 +71,7 @@ diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx
index 30243ba5dc7..f0794661515 100644
--- a/drivers/lora/native/sx126x/sx126x.c
+++ b/drivers/lora/native/sx126x/sx126x.c
@@ -6,14 +6,21 @@
@@ -6,14 +6,52 @@
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/lora.h>
@@ -89,6 +89,37 @@ index 30243ba5dc7..f0794661515 100644
+ * system work queue so USB/BLE/timer work items cannot delay packet RX. */
+#define SX126X_DIO1_WQ_STACK_SIZE 2560
+K_THREAD_STACK_DEFINE(sx126x_dio1_wq_stack, SX126X_DIO1_WQ_STACK_SIZE);
+
+/* PA ramp time handed to SetTxParams at every site that programs TX power.
+ *
+ * Deliberately 800 us where RadioLib, loramac-node and Arduino MeshCore all
+ * use 200 us -- do not "fix" this back to match them without reading the
+ * following, and do not change one call site alone: two writers disagreeing
+ * on the ramp would make TX spectral behaviour depend on which ran last.
+ *
+ * What it buys: a slower PA envelope puts less spectral energy at the burst
+ * edges, which is mask margin (ETSI EN 300 220 / FCC) at TX start and stop.
+ * That is worth most on the boards carrying an external PA or FEM, where
+ * splatter gets amplified along with the wanted signal.
+ *
+ * What it does NOT buy, despite being an easy assumption: any link-budget or
+ * noise-immunity benefit. Ramp time shapes our own transmitter's skirts. It
+ * does not raise TX power, change what a distant receiver hears in-band, or
+ * help its demodulator. The only way it helps anyone's reception is by not
+ * desensitising a CO-SITED or adjacent-channel radio -- real on a hilltop
+ * with several co-located nodes, negligible between nodes kilometres apart.
+ *
+ * What it costs: 600 us more TX startup than the 200 us default, and nothing
+ * else. The ramp completes before modulation begins -- the chip holds BUSY
+ * after SetTx until ramp-up is done, which is why RadioLib waits on exactly
+ * that edge -- so it consumes no preamble symbols at any SF/BW and does not
+ * eat into the RX duty-cycle preamble budget. Airtime is unchanged; both
+ * reference stacks likewise exclude ramp from their time-on-air maths. The
+ * one behavioural side effect is that the gap between "LBT reports the
+ * channel clear" and "we are actually radiating" grows by those 600 us,
+ * which is negligible against a contention window measured in milliseconds.
+ */
+#define SX126X_TX_RAMP_TIME SX126X_RAMP_800_US
+
#define SX126X_REST_STATE \
(IS_ENABLED(CONFIG_LORA_SX126X_NATIVE_SLEEP) \
@@ -728,7 +759,7 @@ index 30243ba5dc7..f0794661515 100644
ret = sx126x_hal_configure_tx_params(dev, config->tx_power,
config->frequency,
- SX126X_RAMP_200_US);
+ SX126X_RAMP_800_US);
+ SX126X_TX_RAMP_TIME);
if (ret < 0) {
goto out;
}
@@ -862,10 +893,41 @@ index 30243ba5dc7..f0794661515 100644
data->tx_async_signal = async;
k_msgq_purge(&data->tx_msgq);
@@ -777,6 +1349,31 @@ static int sx126x_lora_send_async(const struct device *dev,
@@ -777,6 +1349,62 @@ static int sx126x_lora_send_async(const struct device *dev,
/* Enable antenna and set TX path */
sx126x_set_rf_path(dev, true, true);
+ /* Re-assert PA config, OCP and TX power immediately before SetTx.
+ *
+ * sx126x_config() is otherwise the only writer of these three, and the
+ * C++ adapter's config cache (LoRaRadioBase::configure) short-circuits
+ * on every transmit after the first -- so between two lora_config()
+ * calls nothing re-verifies what the chip is actually holding. The
+ * chip keeps them in warm-start retention RAM across its sleep cycles,
+ * which makes them precisely the state a disturbed SPI bus or a stray
+ * command corrupts silently: the node goes on relaying, because
+ * restart_rx re-issues SetRx constantly, while transmitting tens of dB
+ * down until someone reboots it. That is not hypothetical -- ESP32
+ * light sleep leaving NSS undriven produced exactly it; the cause is
+ * fixed at source in boards/common/pm_esp32.overlay and this is the
+ * belt-and-braces half that stops the class of fault rather than the
+ * one instance.
+ *
+ * Costs no receive time. It runs after LBT has passed, with the chip
+ * already committed to TX, and is ~15 bytes of SPI -- unlike forcing a
+ * full lora_config(), which would have to tear RX down first (its
+ * entry CAS rejects SX126X_STATE_RX) and re-run image calibration.
+ *
+ * Keep the ramp time identical to the sx126x_config() call site: two
+ * writers disagreeing on it would make TX spectral behaviour depend on
+ * which one ran last. */
+ ret = sx126x_hal_configure_tx_params(dev, data->config.tx_power,
+ data->config.frequency,
+ SX126X_TX_RAMP_TIME);
+ if (ret < 0) {
+ goto out_error;
+ }
+
+#ifdef CONFIG_LORA_SX126X_NATIVE_STANDALONE /* STM32WL HAL clamps in its own configure_tx_params; its config has no is_sx1261 */
+ /* Sec 15.2 TX Clamp workaround (datasheet errata) -- SX1262 only.
+ * Set bits [4:1] of register 0x08D8 before SetTx to prevent
@@ -986,7 +1048,7 @@ index 30243ba5dc7..f0794661515 100644
/* Set PA config and TX power */
ret = sx126x_hal_configure_tx_params(dev, tx_power, frequency,
- SX126X_RAMP_200_US);
+ SX126X_RAMP_800_US);
+ SX126X_TX_RAMP_TIME);
if (ret < 0) {
sx126x_set_sleep(dev);
k_mutex_unlock(&data->lock);