mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 20:09:17 +00:00
t1000 gps fix
This commit is contained in:
@@ -108,9 +108,13 @@ static void kick_tx_drain(void);
|
||||
/* ========== GATT Service ========== */
|
||||
|
||||
/*
|
||||
* Secure NUS service with MITM-authenticated permissions.
|
||||
* Unlike the default NUS service, this requires PIN pairing before
|
||||
* any NUS communication is allowed (matches Arduino's SECMODE_ENC_WITH_MITM).
|
||||
* NUS service — secured with AUTHEN permissions.
|
||||
* Matches Arduino's SECMODE_ENC_WITH_MITM on bleuart.
|
||||
*
|
||||
* When the phone tries to subscribe (CCC write) or send data (RX write),
|
||||
* Zephyr returns ATT_ERR_AUTHENTICATION. The phone's BLE stack should
|
||||
* then initiate pairing (PIN dialog). After pairing succeeds,
|
||||
* security_changed() fires at L3+ and the phone retries the operation.
|
||||
*/
|
||||
BT_GATT_SERVICE_DEFINE(secure_nus_svc,
|
||||
BT_GATT_PRIMARY_SERVICE(BT_UUID_NUS_SERVICE),
|
||||
@@ -161,7 +165,7 @@ static void ble_tx_complete_cb(struct bt_conn *conn, void *user_data)
|
||||
static int secure_nus_send(struct bt_conn *conn, const void *data, uint16_t len)
|
||||
{
|
||||
struct bt_gatt_notify_params params = {
|
||||
.attr = &secure_nus_svc.attrs[1], /* TX characteristic */
|
||||
.attr = &secure_nus_svc.attrs[2], /* TX characteristic value (not declaration) */
|
||||
.data = data,
|
||||
.len = len,
|
||||
.func = ble_tx_complete_cb,
|
||||
@@ -224,7 +228,7 @@ static void connected(struct bt_conn *conn, uint8_t err)
|
||||
LOG_WRN("connection failed: %s err 0x%02x", addr, err);
|
||||
return;
|
||||
}
|
||||
LOG_DBG("%s", addr);
|
||||
LOG_INF("connected: %s", addr);
|
||||
current_conn = bt_conn_ref(conn);
|
||||
|
||||
/* Cancel slow advertising work - we're connected now */
|
||||
@@ -249,25 +253,27 @@ static void connected(struct bt_conn *conn, uint8_t err)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Proactively request authenticated encryption (MITM).
|
||||
* - Bonded device: re-encrypts with stored keys (instant, no user interaction)
|
||||
* - New device: triggers passkey pairing dialog on the central
|
||||
/* Do NOT proactively request security here.
|
||||
*
|
||||
* L3 = authenticated pairing with MITM protection. iOS/Android will
|
||||
* negotiate Secure Connections (LESC) automatically when supported.
|
||||
* L4 (SC-only) is NOT used because Windows never shows the passkey
|
||||
* dialog for SC pairing — it only works with Legacy passkey entry.
|
||||
* Arduino reference: the SoftDevice never sends SMP Security Request
|
||||
* on connection. Pairing is triggered naturally when the phone tries
|
||||
* to access a GATT characteristic with AUTHEN permissions — the stack
|
||||
* returns "Insufficient Authentication" and the phone's BLE stack
|
||||
* initiates pairing (PIN dialog).
|
||||
*
|
||||
* This is needed because some BLE stacks (Windows) don't automatically
|
||||
* initiate pairing when they get "Insufficient Authentication" from a
|
||||
* protected characteristic. By requesting security here, all platforms
|
||||
* (iOS, Android, Windows) get the pairing prompt immediately.
|
||||
* Our NUS service has BT_GATT_PERM_*_AUTHEN on CCC and RX, so
|
||||
* pairing triggers automatically when the MeshCore app accesses them.
|
||||
*
|
||||
* security_changed() callback handles the rest once encryption is up. */
|
||||
int sec_err = bt_conn_set_security(conn, BT_SECURITY_L3);
|
||||
if (sec_err && sec_err != -EALREADY) {
|
||||
LOG_WRN("Failed to request security: %d", sec_err);
|
||||
}
|
||||
* The old bt_conn_set_security(L3) call sent a proactive SMP Security
|
||||
* Request that the MeshCore app doesn't handle — phone would connect
|
||||
* but never show a PIN dialog, causing a "freeze."
|
||||
*
|
||||
* For bonded reconnects, Zephyr auto-encrypts with stored keys when
|
||||
* CONFIG_BT_SMP and CONFIG_BT_BONDABLE are enabled.
|
||||
*
|
||||
* NOTE: If Windows support is needed later, Windows may not initiate
|
||||
* pairing from "Insufficient Authentication" — add a platform-specific
|
||||
* Security Request path for Windows clients only. */
|
||||
|
||||
/* Notify main of BLE connection */
|
||||
if (ble_cbs && ble_cbs->on_connected) {
|
||||
@@ -279,7 +285,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
char addr[BT_ADDR_LE_STR_LEN];
|
||||
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
||||
LOG_DBG("%s reason 0x%02x", addr, reason);
|
||||
LOG_INF("disconnected: %s reason 0x%02x", addr, reason);
|
||||
|
||||
if (conn == current_conn) {
|
||||
bt_conn_unref(current_conn);
|
||||
@@ -332,19 +338,27 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_
|
||||
}
|
||||
LOG_INF("%s level %u", addr, level);
|
||||
|
||||
/* For bonded reconnects, pairing_complete is NOT called - only security_changed.
|
||||
* Enable TX when we have sufficient security (level 2+ = encrypted).
|
||||
* This handles both fresh pairing (pairing_complete also sets it) and
|
||||
* reconnects with existing bond.
|
||||
/* Enable TX when we have sufficient security (level 2+ = encrypted).
|
||||
* This is the ONLY place that sets ble_tx_ready — security_changed is
|
||||
* the authority. CCC subscription (secure_nus_ccc_changed) only kicks
|
||||
* the TX drain; it never sets ble_tx_ready.
|
||||
*/
|
||||
if (level >= BT_SECURITY_L2 && !ble_tx_ready) {
|
||||
LOG_INF("security established, enabling TX");
|
||||
ble_tx_ready = true;
|
||||
active_iface = ZEPHCORE_IFACE_BLE;
|
||||
|
||||
/* Request our preferred connection parameters.
|
||||
* This is the single place for conn param requests -
|
||||
* fires for both fresh pairing and bonded reconnects. */
|
||||
/* If CCC was already subscribed (bonded reconnect — phone writes
|
||||
* CCC before security_changed fires), kick TX now. On fresh
|
||||
* pairing CCC hasn't been written yet, so this is a no-op and
|
||||
* TX starts when CCC fires later. */
|
||||
if (nus_notif_enabled) {
|
||||
kick_tx_drain();
|
||||
}
|
||||
}
|
||||
|
||||
if (level >= BT_SECURITY_L2) {
|
||||
/* Request our preferred connection parameters. */
|
||||
struct bt_le_conn_param conn_param = {
|
||||
.interval_min = BLE_DEFAULT_MIN_INTERVAL,
|
||||
.interval_max = BLE_DEFAULT_MAX_INTERVAL,
|
||||
@@ -360,10 +374,6 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_
|
||||
BLE_DEFAULT_MAX_INTERVAL * 5 / 4,
|
||||
BLE_DEFAULT_LATENCY);
|
||||
}
|
||||
|
||||
/* Don't kick TX here — MTU exchange may not be done yet.
|
||||
* TX starts when client subscribes to CCC (secure_nus_ccc_changed),
|
||||
* which is the last step and implies MTU is negotiated. */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +440,7 @@ static struct bt_conn_auth_cb auth_cb = {
|
||||
static void pairing_complete(struct bt_conn *conn, bool bonded)
|
||||
{
|
||||
ARG_UNUSED(conn);
|
||||
LOG_DBG("bonded=%d", bonded);
|
||||
LOG_INF("pairing complete: bonded=%d", bonded);
|
||||
|
||||
/* Switch to BLE interface (fresh pairing only).
|
||||
* Conn params and TX enable are handled in security_changed(),
|
||||
@@ -572,9 +582,13 @@ static void secure_nus_ccc_changed(const struct bt_gatt_attr *attr, uint16_t val
|
||||
{
|
||||
ARG_UNUSED(attr);
|
||||
bool enabled = (value == BT_GATT_CCC_NOTIFY);
|
||||
LOG_DBG("enabled=%d", enabled);
|
||||
LOG_INF("CCCD notif %s (value=0x%04x)", enabled ? "enabled" : "disabled", value);
|
||||
nus_notif_enabled = enabled;
|
||||
if (enabled) {
|
||||
/* Kick TX drain — if security_changed already set ble_tx_ready,
|
||||
* data starts flowing. If security hasn't fired yet (bonded
|
||||
* reconnect race), kick_tx_drain bails harmlessly and
|
||||
* security_changed will kick again once ble_tx_ready is set. */
|
||||
kick_tx_drain();
|
||||
}
|
||||
}
|
||||
@@ -594,7 +608,7 @@ static ssize_t secure_nus_rx_write(struct bt_conn *conn, const struct bt_gatt_at
|
||||
const uint8_t *data = (const uint8_t *)buf;
|
||||
uint8_t cmd = data[0];
|
||||
|
||||
LOG_DBG("len=%u cmd=0x%02x", len, cmd);
|
||||
LOG_INF("NUS RX: len=%u cmd=0x%02x", len, cmd);
|
||||
|
||||
/* Notify main via callback */
|
||||
if (ble_cbs && ble_cbs->on_rx_frame) {
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <nrfx.h>
|
||||
|
||||
LOG_MODULE_REGISTER(zephcore_gps, CONFIG_ZEPHCORE_GPS_LOG_LEVEL);
|
||||
|
||||
@@ -223,6 +225,20 @@ static void gnss_data_cb(const struct device *dev, const struct gnss_data *data)
|
||||
LOG_DBG("GPS: No fix, resetting counter");
|
||||
consecutive_good_fixes = 0;
|
||||
}
|
||||
|
||||
/* Periodic status at INF level so user knows NMEA is flowing.
|
||||
* Without this, GPS is completely silent until first fix (all
|
||||
* NMEA parsing is at DBG level in the driver). */
|
||||
if (gps_current_state == GPS_STATE_ACQUIRING) {
|
||||
static int64_t last_status_ms;
|
||||
int64_t now = k_uptime_get();
|
||||
if (now - last_status_ms >= 10000) {
|
||||
LOG_INF("GPS: Searching... sats=%d fix=%d",
|
||||
data->info.satellites_cnt,
|
||||
data->info.fix_status);
|
||||
last_status_ms = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
k_mutex_unlock(&gps_mutex);
|
||||
@@ -267,7 +283,20 @@ static void gnss_configure(void)
|
||||
if (ret == 0) {
|
||||
LOG_INF("GPS: Multi-constellation enabled");
|
||||
} else if (ret == -ENOSYS || ret == -ENOTSUP) {
|
||||
LOG_INF("GPS: Constellation config not supported by driver");
|
||||
/* gnss-nmea-generic is a passive listener — no GNSS API.
|
||||
* Send PMTK353 directly via UART for constellation config.
|
||||
* uart_poll_out is safe: gnss-nmea-generic has no TX activity. */
|
||||
const struct device *gnss_uart = DEVICE_DT_GET(DT_BUS(DT_NODELABEL(gnss)));
|
||||
if (device_is_ready(gnss_uart)) {
|
||||
/* GPS + GLONASS + Galileo + BeiDou (no QZSS) */
|
||||
static const char pmtk[] = "$PMTK353,1,1,1,1,0*2B\r\n";
|
||||
for (size_t i = 0; pmtk[i]; i++) {
|
||||
uart_poll_out(gnss_uart, pmtk[i]);
|
||||
}
|
||||
LOG_INF("GPS: Sent PMTK353 (GPS+GLONASS+Galileo+BeiDou)");
|
||||
} else {
|
||||
LOG_INF("GPS: Constellation config not supported (UART not ready)");
|
||||
}
|
||||
} else {
|
||||
LOG_WRN("GPS: Failed to set constellations: %d", ret);
|
||||
/* Will retry on next power-on cycle */
|
||||
@@ -329,6 +358,24 @@ static const struct gpio_dt_spec gps_sleep_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps_
|
||||
#define HAS_GPS_SLEEP 0
|
||||
#endif
|
||||
|
||||
/* GPS RTC interrupt pin — held LOW during normal operation */
|
||||
#if DT_NODE_EXISTS(DT_ALIAS(gps_rtc_int))
|
||||
static const struct gpio_dt_spec gps_rtcint_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps_rtc_int), gpios);
|
||||
#define HAS_GPS_RTCINT 1
|
||||
#else
|
||||
#define HAS_GPS_RTCINT 0
|
||||
#endif
|
||||
|
||||
/* GPS RESETB (active-LOW reset) — must be INPUT_PULLUP for normal operation.
|
||||
* Without the pull-up, this pin floats LOW and holds the AG3335 in permanent
|
||||
* reset, preventing any UART output. */
|
||||
#if DT_NODE_EXISTS(DT_ALIAS(gps_resetb))
|
||||
static const struct gpio_dt_spec gps_resetb_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps_resetb), gpios);
|
||||
#define HAS_GPS_RESETB 1
|
||||
#else
|
||||
#define HAS_GPS_RESETB 0
|
||||
#endif
|
||||
|
||||
/* T1000-E has extra GPS control pins that require a specific init sequence */
|
||||
#define HAS_T1000_GPS_CONTROL (HAS_GPS_VRTC || HAS_GPS_RESET || HAS_GPS_SLEEP)
|
||||
|
||||
@@ -348,8 +395,11 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
|
||||
/* Direct GPIO power control — works on all boards.
|
||||
* We toggle the GPS power pin ourselves rather than using driver PM
|
||||
* (driver PM can hang on modem_pipe_close / modem_chat_run_script).
|
||||
* The GNSS driver's modem pipe stays open; NMEA data simply stops
|
||||
* when power is cut and resumes when power is restored. */
|
||||
* The GNSS driver's modem pipe stays open.
|
||||
*
|
||||
* T1000-E (HAS_GPS_VRTC): Use warm standby (keep VRTC) for app toggle
|
||||
* so UART/chip state is preserved. Matches Arduino sleep_gps().
|
||||
* Simple boards (Wio etc.): Full power off/on via GPS_EN. */
|
||||
if (on) {
|
||||
#if HAS_T1000_GPS_CONTROL
|
||||
/* T1000-E power-on sequence (from Arduino target.cpp start_gps())
|
||||
@@ -384,6 +434,22 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
|
||||
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_HIGH);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_RTCINT
|
||||
/* GPS_RTC_INT (P0.15) — held LOW during normal operation */
|
||||
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_LOW);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_RESETB
|
||||
/* GPS_RESETB (P1.14) — active-LOW reset, must be pulled HIGH.
|
||||
* INPUT_PULLUP de-asserts reset so the AG3335 can boot.
|
||||
* Without this the pin floats LOW → chip stuck in reset → no UART. */
|
||||
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_INPUT | GPIO_PULL_UP);
|
||||
}
|
||||
#endif
|
||||
gps_gpio_configured = true;
|
||||
LOG_INF("GPS power ON (T1000-E sequence)");
|
||||
#else
|
||||
@@ -404,6 +470,15 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
|
||||
#endif
|
||||
} else {
|
||||
/* Power off sequence */
|
||||
#if HAS_GPS_RESET
|
||||
/* Hold GPS in reset during power-off — matches Arduino sleep_gps()/stop_gps().
|
||||
* Ensures chip sees RESET asserted when GPS_EN goes HIGH on next
|
||||
* power-on, preventing uncontrolled startup before the reset pulse. */
|
||||
if (gpio_is_ready_dt(&gps_reset_gpio)) {
|
||||
gpio_pin_set_dt(&gps_reset_gpio, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_VRTC
|
||||
if (!keep_vrtc) {
|
||||
/* Full power-off: VRTC off too (cold start on next wake) */
|
||||
@@ -426,6 +501,21 @@ static void gps_power_control(bool on, bool keep_vrtc = false)
|
||||
gpio_pin_set_dt(&gps_enable_gpio, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#if HAS_GPS_RESETB
|
||||
/* Drive RESETB LOW when GPS is off (Arduino sleep_gps/stop_gps) */
|
||||
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_LOW);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_RTCINT
|
||||
/* GPS_RTC_INT stays LOW during sleep/off (same as normal operation) */
|
||||
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_LOW);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_VRTC
|
||||
LOG_INF("GPS power OFF (%s)", keep_vrtc ?
|
||||
"standby — VRTC retained" : "full");
|
||||
@@ -463,6 +553,16 @@ void gps_power_off_for_shutdown(void)
|
||||
gpio_pin_configure_dt(&gps_sleep_gpio, GPIO_OUTPUT_LOW);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RTCINT
|
||||
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_rtcint_gpio, GPIO_OUTPUT_LOW);
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RESETB
|
||||
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
|
||||
gpio_pin_configure_dt(&gps_resetb_gpio, GPIO_OUTPUT_LOW);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAS_GNSS /* Resume GNSS-specific code */
|
||||
@@ -564,6 +664,95 @@ static void gps_timeout_work_fn(struct k_work *work)
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== GPS UART Diagnostics ========== */
|
||||
|
||||
/**
|
||||
* Dump nRF52840 UARTE0 hardware register state.
|
||||
* Reads PSEL (pin select), ENABLE, BAUDRATE, and ERRORSRC directly
|
||||
* from the peripheral registers — no assumptions, just facts.
|
||||
*/
|
||||
static void gps_uart_dump_hw_state(void)
|
||||
{
|
||||
#if defined(CONFIG_SOC_NRF52840)
|
||||
NRF_UARTE_Type *uart = NRF_UARTE0;
|
||||
|
||||
uint32_t psel_txd = uart->PSEL.TXD;
|
||||
uint32_t psel_rxd = uart->PSEL.RXD;
|
||||
uint32_t enable = uart->ENABLE;
|
||||
uint32_t baudrate = uart->BAUDRATE;
|
||||
uint32_t errorsrc = uart->ERRORSRC;
|
||||
|
||||
/* PSEL format: bit 31 = CONNECT (0=connected, 1=disconnected),
|
||||
* bits 4:0 = pin, bit 5 = port */
|
||||
bool txd_connected = !(psel_txd & (1U << 31));
|
||||
bool rxd_connected = !(psel_rxd & (1U << 31));
|
||||
uint8_t txd_port = (psel_txd >> 5) & 1;
|
||||
uint8_t txd_pin = psel_txd & 0x1F;
|
||||
uint8_t rxd_port = (psel_rxd >> 5) & 1;
|
||||
uint8_t rxd_pin = psel_rxd & 0x1F;
|
||||
|
||||
LOG_INF("UART0 HW state:");
|
||||
LOG_INF(" ENABLE=0x%02x (8=enabled)", enable);
|
||||
LOG_INF(" PSEL.TXD=0x%08x → P%d.%02d %s",
|
||||
psel_txd, txd_port, txd_pin,
|
||||
txd_connected ? "CONNECTED" : "DISCONNECTED");
|
||||
LOG_INF(" PSEL.RXD=0x%08x → P%d.%02d %s",
|
||||
psel_rxd, rxd_port, rxd_pin,
|
||||
rxd_connected ? "CONNECTED" : "DISCONNECTED");
|
||||
LOG_INF(" BAUDRATE=0x%08x ERRORSRC=0x%x", baudrate, errorsrc);
|
||||
|
||||
/* Clear any error flags */
|
||||
if (errorsrc) {
|
||||
uart->ERRORSRC = errorsrc;
|
||||
LOG_WRN(" UART errors cleared: overrun=%d parity=%d framing=%d break=%d",
|
||||
(errorsrc >> 0) & 1, (errorsrc >> 1) & 1,
|
||||
(errorsrc >> 2) & 1, (errorsrc >> 3) & 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAS_GPS_POWER_CONTROL
|
||||
/**
|
||||
* Log actual GPIO pin states after power-up sequence.
|
||||
* Reads back each configured pin to verify the hardware accepted our config.
|
||||
*/
|
||||
static void gps_dump_gpio_states(void)
|
||||
{
|
||||
LOG_INF("GPS GPIO states after power-up:");
|
||||
if (gpio_is_ready_dt(&gps_enable_gpio)) {
|
||||
LOG_INF(" GPS_EN (P1.11): %d", gpio_pin_get_dt(&gps_enable_gpio));
|
||||
}
|
||||
#if HAS_GPS_VRTC
|
||||
if (gpio_is_ready_dt(&gps_vrtc_gpio)) {
|
||||
LOG_INF(" GPS_VRTC_EN (P0.08): %d", gpio_pin_get_dt(&gps_vrtc_gpio));
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RESET
|
||||
if (gpio_is_ready_dt(&gps_reset_gpio)) {
|
||||
LOG_INF(" GPS_RESET (P1.15): %d", gpio_pin_get_dt(&gps_reset_gpio));
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_SLEEP
|
||||
if (gpio_is_ready_dt(&gps_sleep_gpio)) {
|
||||
LOG_INF(" GPS_SLEEP_INT (P1.12): %d", gpio_pin_get_dt(&gps_sleep_gpio));
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RTCINT
|
||||
if (gpio_is_ready_dt(&gps_rtcint_gpio)) {
|
||||
LOG_INF(" GPS_RTC_INT (P0.15): %d", gpio_pin_get_dt(&gps_rtcint_gpio));
|
||||
}
|
||||
#endif
|
||||
#if HAS_GPS_RESETB
|
||||
if (gpio_is_ready_dt(&gps_resetb_gpio)) {
|
||||
LOG_INF(" GPS_RESETB (P1.14): %d (INPUT_PULLUP, expect 1)",
|
||||
gpio_pin_get_dt(&gps_resetb_gpio));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif /* HAS_GPS_POWER_CONTROL */
|
||||
|
||||
/* ========== GNSS Init ========== */
|
||||
|
||||
static int gnss_init(void)
|
||||
{
|
||||
/* Try to find a GNSS device - prefer chip-specific drivers
|
||||
@@ -590,8 +779,38 @@ static int gnss_init(void)
|
||||
}
|
||||
|
||||
if (!device_is_ready(gnss_dev)) {
|
||||
LOG_ERR("GNSS device %s not ready", gnss_dev->name);
|
||||
return -ENODEV;
|
||||
/* Device not ready — deferred init. Power up GPS then init driver.
|
||||
* IMPORTANT: Do NOT use uart_poll_in() here — it corrupts the
|
||||
* nRF52840 UARTE DMA state and breaks modem_pipe async receive.
|
||||
* Previous diagnostic proved GPS IS transmitting (415 bytes/2s). */
|
||||
LOG_INF("GNSS device not ready — powering up for deferred init");
|
||||
gps_power_control(true);
|
||||
|
||||
#if HAS_GPS_POWER_CONTROL
|
||||
/* Verify GPIO states immediately after power-up */
|
||||
gps_dump_gpio_states();
|
||||
#endif
|
||||
|
||||
/* Dump UART0 hardware register state (read-only, non-destructive) */
|
||||
gps_uart_dump_hw_state();
|
||||
|
||||
/* Wait for AG3335 firmware boot before driver init.
|
||||
* GPS transmits boot messages + NMEA at 115200 baud during this
|
||||
* delay — the Zephyr UART driver handles any accumulated errors
|
||||
* internally when modem_pipe_open() enables the ISR. */
|
||||
k_msleep(500);
|
||||
|
||||
int ret = device_init(gnss_dev);
|
||||
if (ret != 0 && ret != -EALREADY) {
|
||||
LOG_ERR("GNSS device_init failed: %d", ret);
|
||||
/* Dump UART state after failure for debugging */
|
||||
gps_uart_dump_hw_state();
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!device_is_ready(gnss_dev)) {
|
||||
LOG_ERR("GNSS device still not ready after deferred init");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INF("GNSS device %s initialized", gnss_dev->name);
|
||||
@@ -724,8 +943,14 @@ void gps_enable(bool enable)
|
||||
k_work_cancel_delayable(&gps_wake_work);
|
||||
k_work_cancel_delayable(&gps_timeout_work);
|
||||
|
||||
/* Power off GPS */
|
||||
gps_power_control(false);
|
||||
/* Power off GPS — warm standby if VRTC available (Arduino sleep_gps),
|
||||
* full power off otherwise. Warm standby preserves ephemeris/RTC
|
||||
* in AG3335 backup RAM for fast re-acquisition (1-8s vs 15-45s). */
|
||||
#if HAS_GPS_VRTC
|
||||
gps_power_control(false, true); /* Warm standby — keep VRTC */
|
||||
#else
|
||||
gps_power_control(false); /* No VRTC — full power off */
|
||||
#endif
|
||||
gps_current_state = GPS_STATE_OFF;
|
||||
consecutive_good_fixes = 0;
|
||||
|
||||
|
||||
@@ -477,11 +477,15 @@ void LoRaRadioBase::triggerNoiseFloorCalibrate(int threshold)
|
||||
return;
|
||||
}
|
||||
|
||||
/* RX duty cycle: radio alternates HW RX/sleep autonomously.
|
||||
* During sleep windows hwGetCurrentRSSI() returns garbage (0 or very
|
||||
* high) — the sampling filter rejects those, and the count>=32
|
||||
* threshold ensures we only update when enough valid samples exist. */
|
||||
/* Skip when duty cycle is active — the radio alternates between
|
||||
* short RX windows and sleep. GetRssiInst sent during the sleep
|
||||
* phase hangs the SPI bus (BUSY stuck high for the full 3 s timeout)
|
||||
* because the chip cannot process commands while asleep. */
|
||||
if (_rx_duty_cycle_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t start = k_uptime_get();
|
||||
int sum = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < NUM_NOISE_FLOOR_SAMPLES; i++) {
|
||||
@@ -494,13 +498,15 @@ void LoRaRadioBase::triggerNoiseFloorCalibrate(int threshold)
|
||||
count++;
|
||||
}
|
||||
}
|
||||
int64_t elapsed = k_uptime_get() - start;
|
||||
|
||||
if (count >= NUM_NOISE_FLOOR_SAMPLES / 2) {
|
||||
_noise_floor = sum / count;
|
||||
if (_noise_floor < -120) _noise_floor = -120;
|
||||
if (_noise_floor > -50) _noise_floor = -50;
|
||||
LOG_DBG("noise floor: %d dBm (%d samples)", _noise_floor, count);
|
||||
}
|
||||
LOG_INF("noise_floor_cal: %d samples/%d total, floor=%d, took %lld ms",
|
||||
count, NUM_NOISE_FLOOR_SAMPLES, _noise_floor, elapsed);
|
||||
}
|
||||
|
||||
void LoRaRadioBase::resetAGC()
|
||||
|
||||
@@ -37,6 +37,10 @@ static void dio1_work_handler(struct k_work *work)
|
||||
}
|
||||
}
|
||||
|
||||
/* Track the last SPI opcode for debugging BUSY stuck */
|
||||
static uint16_t last_opcode;
|
||||
static int64_t last_cmd_time;
|
||||
|
||||
/**
|
||||
* @brief Wait until BUSY pin goes low or timeout
|
||||
*/
|
||||
@@ -47,8 +51,10 @@ static lr11xx_hal_status_t wait_on_busy(struct lr11xx_hal_context *ctx)
|
||||
|
||||
while (gpio_pin_get_dt(&ctx->busy)) {
|
||||
if ((k_uptime_get() - start) > LR11XX_BUSY_TIMEOUT_MS) {
|
||||
LOG_ERR("BUSY timeout after %d loops (%dms)!",
|
||||
loops, LR11XX_BUSY_TIMEOUT_MS);
|
||||
LOG_ERR("BUSY timeout! last_op=0x%04x sent_at=%lld (%lld ms ago) DIO1=%d",
|
||||
last_opcode, last_cmd_time,
|
||||
k_uptime_get() - last_cmd_time,
|
||||
gpio_pin_get_dt(&ctx->dio1));
|
||||
return LR11XX_HAL_STATUS_ERROR;
|
||||
}
|
||||
k_busy_wait(100); /* 100us */
|
||||
@@ -178,7 +184,14 @@ lr11xx_hal_status_t lr11xx_hal_write(const void *context, const uint8_t *command
|
||||
struct lr11xx_hal_context *ctx = (struct lr11xx_hal_context *)context;
|
||||
int ret;
|
||||
|
||||
/* Track opcode for BUSY timeout diagnostics */
|
||||
if (command_length >= 2) {
|
||||
last_opcode = ((uint16_t)command[0] << 8) | command[1];
|
||||
}
|
||||
last_cmd_time = k_uptime_get();
|
||||
|
||||
if (check_device_ready(ctx) != LR11XX_HAL_STATUS_OK) {
|
||||
LOG_ERR("hal_write: device not ready, op=0x%04x", last_opcode);
|
||||
return LR11XX_HAL_STATUS_ERROR;
|
||||
}
|
||||
|
||||
@@ -222,12 +235,19 @@ lr11xx_hal_status_t lr11xx_hal_read(const void *context, const uint8_t *command,
|
||||
struct lr11xx_hal_context *ctx = (struct lr11xx_hal_context *)context;
|
||||
int ret;
|
||||
|
||||
/* Track opcode for BUSY timeout diagnostics */
|
||||
if (command_length >= 2) {
|
||||
last_opcode = ((uint16_t)command[0] << 8) | command[1];
|
||||
}
|
||||
last_cmd_time = k_uptime_get();
|
||||
|
||||
/* Special case: crypto restore command needs delay */
|
||||
if (command_length >= 2 && command[0] == 0x05 && command[1] == 0x0B) {
|
||||
k_busy_wait(1000);
|
||||
}
|
||||
|
||||
if (check_device_ready(ctx) != LR11XX_HAL_STATUS_OK) {
|
||||
LOG_ERR("hal_read: device not ready, op=0x%04x", last_opcode);
|
||||
return LR11XX_HAL_STATUS_ERROR;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,16 @@ CONFIG_BT_BUF_ACL_RX_SIZE=251
|
||||
CONFIG_BT_BUF_ACL_TX_SIZE=251
|
||||
CONFIG_BT_L2CAP_TX_MTU=247
|
||||
|
||||
# BLE TX buffers — default 3 is too low, causes system workqueue deadlock
|
||||
# when BLE activity bursts (DLE + param update + ATT) exhaust all buffers
|
||||
# and bt_hci_cmd_alloc(K_FOREVER) blocks the cooperative syswq forever.
|
||||
CONFIG_BT_BUF_ACL_TX_COUNT=7
|
||||
CONFIG_BT_L2CAP_TX_BUF_COUNT=7
|
||||
CONFIG_BT_CONN_TX_MAX=7
|
||||
|
||||
# BLE RX thread stack — default 1200 too small for ATT handlers + logging + asserts
|
||||
CONFIG_BT_RX_STACK_SIZE=2048
|
||||
|
||||
# Data Length Extension update support (host side)
|
||||
CONFIG_BT_USER_DATA_LEN_UPDATE=y
|
||||
|
||||
@@ -116,6 +126,13 @@ CONFIG_BT_DIS_SW_REV=y
|
||||
CONFIG_BT_DIS_SW_REV_STR="Zephyr"
|
||||
CONFIG_BT_DIS_PNP=n
|
||||
|
||||
# Disable GATT Caching (Robust Caching / Database Hash)
|
||||
# With caching enabled (Kconfig default), Zephyr silently drops ATT requests
|
||||
# from "change-unaware" clients after the GATT database changes. This breaks
|
||||
# service discovery on phones that enable Robust Caching (modern iOS/Android).
|
||||
# Arduino SoftDevice has no GATT caching — disabling matches that behavior.
|
||||
CONFIG_BT_GATT_CACHING=n
|
||||
|
||||
# Persistent bonds (NVS)
|
||||
CONFIG_BT_SETTINGS=y
|
||||
CONFIG_SETTINGS_RUNTIME=y
|
||||
@@ -143,6 +160,16 @@ CONFIG_INPUT_EVENT_DUMP=n
|
||||
# instead of a dedicated 1KB input thread. Saves a thread stack.
|
||||
CONFIG_INPUT_MODE_SYNCHRONOUS=y
|
||||
|
||||
# ========== Logging ==========
|
||||
# RTT log backend: DROP mode (not BLOCK) — when no RTT host is connected the
|
||||
# buffer is always full; BLOCK mode sleeps 20ms per message with irq_lock held,
|
||||
# disrupting BLE timing and cascading into buffer exhaustion deadlocks.
|
||||
CONFIG_LOG_BACKEND_RTT_MODE_DROP=y
|
||||
|
||||
# Silence USB CH9 control-transfer spam (dozens of INF lines during enumeration).
|
||||
# WRN keeps real USB errors visible.
|
||||
CONFIG_USBD_LOG_LEVEL_WRN=y
|
||||
|
||||
# ========== Power Management ==========
|
||||
CONFIG_POWEROFF=y
|
||||
|
||||
|
||||
@@ -64,11 +64,41 @@ CONFIG_NORDIC_QSPI_NOR=n
|
||||
# 1. Add the display node to your board.overlay (see examples there)
|
||||
# 2. Set zephyr,display = &your_display; in the "chosen" block
|
||||
# 3. That's it — driver, CFB, and UI all auto-enable
|
||||
#
|
||||
# To DISABLE display on a board WITHOUT a screen (enables LED heartbeat):
|
||||
# CONFIG_ZEPHCORE_UI_DISPLAY=n
|
||||
# The heartbeat LED (led0 alias) only blinks when display is disabled
|
||||
# (HAS_HEARTBEAT_LED requires !CONFIG_ZEPHCORE_UI_DISPLAY).
|
||||
# CONFIG_ZEPHCORE_UI_DISPLAY=n
|
||||
|
||||
# ========== OPTIONAL: Buzzer ==========
|
||||
# Uncomment if your board has a piezo buzzer on a PWM pin.
|
||||
# CONFIG_PWM=y
|
||||
|
||||
# ========== OPTIONAL: UART Async API (nRF52840 GPS boards) ==========
|
||||
# Enable this if your board has a GPS module on UART with deferred-init
|
||||
# (i.e., GPS power is controlled by external GPIOs, not the GNSS driver).
|
||||
#
|
||||
# WHY: The nRF52840 UARTE is DMA-based. The default ISR modem backend
|
||||
# emulates byte-by-byte interrupts on top of DMA, which breaks when GPS
|
||||
# data arrives before the ISR handler is enabled (GPS transmits during
|
||||
# the boot delay → overrun → modem_chat sees nothing).
|
||||
#
|
||||
# The async backend uses native UARTE DMA, which is more robust for GPS
|
||||
# modules that start transmitting before the software is ready to listen.
|
||||
#
|
||||
# WHEN TO ENABLE:
|
||||
# - Your board uses quectel,lc76g with zephyr,deferred-init (e.g., T1000-E)
|
||||
# - GPS power is managed by external GPIOs in ZephyrGPSManager
|
||||
# - GPS module starts transmitting before modem_pipe_open() is called
|
||||
#
|
||||
# WHEN NOT NEEDED:
|
||||
# - luatos,air530z driver (manages power internally via on-off-gpios)
|
||||
# - GPS at 9600 baud (slow enough for ISR backend)
|
||||
# - Boards where the GNSS driver controls power (no deferred-init)
|
||||
#
|
||||
# CONFIG_UART_ASYNC_API=y
|
||||
|
||||
# ========== OPTIONAL: Platform-Specific Overrides ==========
|
||||
#
|
||||
# --- nRF52840 ---
|
||||
|
||||
@@ -679,6 +679,14 @@
|
||||
*
|
||||
* Supported: "luatos,air530z", "quectel,lc76g", "gnss-nmea-generic"
|
||||
*
|
||||
* Two patterns depending on who controls GPS power:
|
||||
*
|
||||
* PATTERN A: Driver-managed power (simple, GPS at 9600 baud)
|
||||
* Driver uses on-off-gpios to control power internally.
|
||||
* UART ISR backend works fine (driver enables power after ISR is ready).
|
||||
* No deferred-init needed, no CONFIG_UART_ASYNC_API needed.
|
||||
* Example: Wio Tracker L1 (L76K via air530z driver)
|
||||
*
|
||||
* &uart0 {
|
||||
* current-speed = <9600>;
|
||||
* gnss: gnss {
|
||||
@@ -686,6 +694,64 @@
|
||||
* on-off-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
|
||||
* };
|
||||
* };
|
||||
*
|
||||
*
|
||||
* PATTERN B: External GPIO power with deferred init (GPS at 115200 baud)
|
||||
* GPS power is managed by ZephyrGPSManager via multiple GPIOs.
|
||||
* Must use zephyr,deferred-init so driver init waits for GPS power-up.
|
||||
* Must use CONFIG_UART_ASYNC_API=y in board.conf (see notes there).
|
||||
* Example: T1000-E (AG3335 via lc76g driver, PAIR protocol)
|
||||
*
|
||||
* WHY DEFERRED INIT: The GNSS driver's init() immediately opens the
|
||||
* modem pipe and sends commands. Without deferred-init, the driver
|
||||
* tries to talk to the GPS before it's powered → guaranteed timeout.
|
||||
* With deferred-init, ZephyrGPSManager powers up GPIO first, then
|
||||
* calls device_init() to start the driver conversation.
|
||||
*
|
||||
* &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 = "quectel,lc76g";
|
||||
* pps-mode = "GNSS_PPS_MODE_DISABLED";
|
||||
* zephyr,deferred-init;
|
||||
* };
|
||||
* };
|
||||
*
|
||||
*
|
||||
* GPS POWER GPIO ALIASES (add to aliases block above):
|
||||
* For Pattern A: only gps-enable is needed (shared with on-off-gpios)
|
||||
* For Pattern B: define all GPIOs your GPS module requires
|
||||
*
|
||||
* aliases {
|
||||
* gps-enable = &gps_en_pin; Required: GPS main power
|
||||
* gps-vrtc-enable = &gps_vrtc_pin; Optional: VRTC backup power (warm standby)
|
||||
* gps-reset = &gps_reset_pin; Optional: GPS hardware reset
|
||||
* gps-sleep-int = &gps_sleep_pin; Optional: GPS sleep/wake interrupt
|
||||
* gps-rtc-int = &gps_rtcint_pin; Optional: GPS RTC interrupt (held LOW)
|
||||
* gps-resetb = &gps_resetb_pin; Optional: Active-LOW reset (INPUT_PULLUP!)
|
||||
* };
|
||||
*
|
||||
* GPS GPIO nodes (add to root / { } block):
|
||||
*
|
||||
* gps_power: gps-power {
|
||||
* compatible = "gpio-leds";
|
||||
* gps_en_pin: gps_en {
|
||||
* gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
|
||||
* label = "GPS Enable";
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* IMPORTANT for AG3335-based boards (T1000-E, etc.):
|
||||
* gps-resetb (active-LOW reset) MUST be configured as INPUT_PULLUP.
|
||||
* Without pull-up, the pin floats LOW → AG3335 stays in permanent
|
||||
* reset → zero UART output. ZephyrGPSManager handles this for
|
||||
* power-on (INPUT_PULLUP) and power-off (OUTPUT_LOW).
|
||||
*/
|
||||
|
||||
/* --- OLED Display on I2C (any platform with I2C) ---
|
||||
|
||||
@@ -21,3 +21,11 @@ CONFIG_SPI=y
|
||||
|
||||
# PWM for buzzer
|
||||
CONFIG_PWM=y
|
||||
|
||||
# T1000-E has no display — disable to enable LED heartbeat
|
||||
# (HAS_HEARTBEAT_LED requires !CONFIG_ZEPHCORE_UI_DISPLAY)
|
||||
CONFIG_ZEPHCORE_UI_DISPLAY=n
|
||||
|
||||
# Debug logging removed — GPS and BLE confirmed working at protocol level.
|
||||
# Re-enable selectively with: -DCONFIG_BT_CONN_LOG_LEVEL_DBG=y etc.
|
||||
# Heavy debug logging contributed to BLE freeze (stack pressure + RTT blocking).
|
||||
|
||||
@@ -46,10 +46,33 @@
|
||||
};
|
||||
};
|
||||
|
||||
/* GPS RTC interrupt - P0.15 (held LOW during normal operation) */
|
||||
gps_rtcint: gps-rtc-int {
|
||||
compatible = "gpio-leds";
|
||||
gps_rtcint_pin: gps_rtc_int {
|
||||
gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS RTC Int";
|
||||
};
|
||||
};
|
||||
|
||||
/* GPS RESETB (active-LOW reset) - P1.14
|
||||
* Must be pulled HIGH (INPUT_PULLUP) for normal operation.
|
||||
* Without pull-up, this pin floats LOW and holds the AG3335
|
||||
* in permanent reset — zero UART output! */
|
||||
gps_resetb: gps-resetb {
|
||||
compatible = "gpio-leds";
|
||||
gps_resetb_pin: gps_resetb {
|
||||
gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
|
||||
label = "GPS RESETB";
|
||||
};
|
||||
};
|
||||
|
||||
aliases {
|
||||
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;
|
||||
gps-resetb = &gps_resetb_pin;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -202,9 +202,14 @@
|
||||
pinctrl-names = "default", "sleep";
|
||||
};
|
||||
|
||||
/* Airoha AG3335 GNSS on UART0 - 115200 baud (PAIR protocol)
|
||||
* Uses Zephyr quectel,lc76g driver (LC76G is built on AG3335,
|
||||
* same PAIR command set for constellation config, fix rate, etc.) */
|
||||
/* Airoha AG3335 GNSS on UART0 - 115200 baud
|
||||
*
|
||||
* Using gnss-nmea-generic (passive NMEA listener) instead of quectel,lc76g:
|
||||
* - Arduino reference code just listens for NMEA, never sends PAIR commands
|
||||
* - AG3335 outputs NMEA autonomously at factory defaults
|
||||
* - Eliminates 10s+ blocking resume_script timeout on PAIR command
|
||||
* - Trade-off: no gnss_set_enabled_systems() / gnss_set_fix_rate() API
|
||||
* (AG3335 defaults are fine: GPS+GLONASS+Galileo+BeiDou, 1Hz) */
|
||||
&uart0 {
|
||||
compatible = "nordic,nrf-uarte";
|
||||
status = "okay";
|
||||
@@ -214,8 +219,11 @@
|
||||
pinctrl-names = "default", "sleep";
|
||||
|
||||
gnss: gnss {
|
||||
compatible = "quectel,lc76g";
|
||||
pps-mode = "GNSS_PPS_MODE_DISABLED";
|
||||
compatible = "gnss-nmea-generic";
|
||||
/* Defer driver init — AG3335 needs GPIO power-up
|
||||
* (GPS_EN, GPS_VRTC_EN, GPS_RESET) before NMEA output.
|
||||
* ZephyrGPSManager handles power-up then calls device_init(). */
|
||||
zephyr,deferred-init;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -294,12 +294,15 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
|
||||
{
|
||||
void *ctx = &data->hal_ctx;
|
||||
|
||||
LOG_INF("start_rx: t=%lld duty=%d", k_uptime_get(),
|
||||
data->rx_duty_cycle_enabled);
|
||||
|
||||
/* Standby first — wake from any sleep state */
|
||||
data->hal_ctx.radio_is_sleeping = true;
|
||||
lr11xx_status_t rc = lr11xx_system_set_standby(ctx,
|
||||
LR11XX_SYSTEM_STANDBY_CFG_RC);
|
||||
if (rc != LR11XX_STATUS_OK) {
|
||||
LOG_ERR("standby failed — triggering HW reset");
|
||||
LOG_ERR("standby failed (rc=%d) — triggering HW reset", rc);
|
||||
lr11xx_hardware_reset(data, cfg);
|
||||
}
|
||||
|
||||
@@ -340,7 +343,8 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
lr11xx_system_irq_mask_t irq;
|
||||
lr11xx_system_get_and_clear_irq_status(ctx, &irq);
|
||||
|
||||
LOG_DBG("DIO1 IRQ: 0x%08x tx=%d", irq, data->tx_active);
|
||||
LOG_INF("DIO1 IRQ: 0x%08x tx=%d t=%lld", irq, data->tx_active,
|
||||
k_uptime_get());
|
||||
|
||||
/* ── RX done ── */
|
||||
if (irq & LR11XX_SYSTEM_IRQ_RX_DONE) {
|
||||
@@ -392,7 +396,8 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
|
||||
/* ── Timeout ── */
|
||||
if (irq & LR11XX_SYSTEM_IRQ_TIMEOUT) {
|
||||
LOG_DBG("Timeout IRQ");
|
||||
LOG_INF("Timeout IRQ — restarting RX (duty_cycle=%d)",
|
||||
data->rx_duty_cycle_enabled);
|
||||
if (!data->tx_active) {
|
||||
lr11xx_start_rx(data, cfg);
|
||||
}
|
||||
@@ -664,21 +669,19 @@ bool lr11xx_is_receiving(const struct device *dev)
|
||||
void lr11xx_set_rx_duty_cycle(const struct device *dev, bool enable)
|
||||
{
|
||||
struct lr11xx_data *data = dev->data;
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
|
||||
data->rx_duty_cycle_enabled = enable;
|
||||
LOG_INF("RX duty cycle %s", enable ? "enabled" : "disabled");
|
||||
|
||||
/* If currently in RX, restart with proper Standby transition.
|
||||
* LR1110 requires Standby before SetRx or SetRxDutyCycle —
|
||||
* issuing these while already in RX puts the radio in an
|
||||
* undefined state where RSSI reads work but packet detection
|
||||
* is broken (zero DIO1 IRQs). */
|
||||
if (data->in_rx_mode) {
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
if (enable) {
|
||||
lr11xx_apply_rx_duty_cycle(data);
|
||||
} else {
|
||||
lr11xx_radio_set_rx(&data->hal_ctx, 0xFFFFFF);
|
||||
if (data->rx_boost_enabled) {
|
||||
lr11xx_radio_cfg_rx_boosted(&data->hal_ctx,
|
||||
true);
|
||||
}
|
||||
}
|
||||
lr11xx_start_rx(data, cfg);
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,6 +489,7 @@ int main(void)
|
||||
companion_mesh.prefs.rx_delay_base = 0.0f; /* Disabled for companion */
|
||||
companion_mesh.prefs.airtime_factor = 10.0f; /* 10% duty cycle (EU 868 default) */
|
||||
companion_mesh.prefs.rx_duty_cycle = 1; /* Companions: duty cycle ON by default (power save) */
|
||||
companion_mesh.prefs.rx_boost = 1; /* Default: boosted RX (+3dB sensitivity, +2mA) */
|
||||
|
||||
/* Generate default node name from hardware device ID */
|
||||
uint8_t dev_id[8];
|
||||
|
||||
Reference in New Issue
Block a user