t1000 gps fix

This commit is contained in:
liquidraver
2026-02-24 15:50:11 +01:00
parent 89bee0959f
commit 6c7d52e725
12 changed files with 498 additions and 67 deletions
+50 -36
View File
@@ -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) {
+232 -7
View File
@@ -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;
+11 -5
View File
@@ -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
+30
View File
@@ -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);
}
}
+1
View File
@@ -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];