mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 11:42:25 +00:00
gps fiddling
This commit is contained in:
1 parent
788e4dd48b
commit
f83ce0bed4
21 files changed
+1988
-224
No files matched your search
@@ -48,11 +48,51 @@ LOG_MODULE_REGISTER(zephcore_gps, CONFIG_ZEPHCORE_GPS_LOG_LEVEL);
|
||||
#define HAS_GNSS 0
|
||||
#endif
|
||||
|
||||
/* ========== GPS Feature Detection ==========
|
||||
* Every HAS_GPS_* predicate is defined HERE, before first use. They are pure
|
||||
* devicetree tests with no side effects, kept apart from the variables they
|
||||
* gate so that ordering can never drift again.
|
||||
*
|
||||
* Why this block exists: HAS_GPS_UART used to be defined ~500 lines below its
|
||||
* first `#if`, and an undefined identifier in `#if` is silently 0 — so the
|
||||
* entire PMTK/UBX module-configuration path compiled to nothing on every
|
||||
* board, and GPS ran at module defaults (GPS-only constellations, no AOP).
|
||||
* If you add another HAS_GPS_* macro, define it in this block.
|
||||
*/
|
||||
|
||||
/* GNSS module hangs off a UART we can write to (any compatible). */
|
||||
#if HAS_GNSS && DT_NODE_HAS_STATUS(DT_NODELABEL(gnss), okay) && \
|
||||
DT_NODE_HAS_STATUS(DT_BUS(DT_NODELABEL(gnss)), okay)
|
||||
#define HAS_GPS_UART 1
|
||||
#else
|
||||
#define HAS_GPS_UART 0
|
||||
#endif
|
||||
|
||||
/* Discrete GPS power-enable GPIO (gps-enable alias). */
|
||||
#if DT_NODE_EXISTS(DT_ALIAS(gps_enable))
|
||||
#define HAS_GPS_POWER_CONTROL 1
|
||||
#else
|
||||
#define HAS_GPS_POWER_CONTROL 0
|
||||
#endif
|
||||
|
||||
/* GPS powered from a PMU regulator rail (chosen zephcore,gps-power). */
|
||||
#if DT_NODE_EXISTS(DT_CHOSEN(zephcore_gps_power))
|
||||
#define HAS_GPS_POWER_REGULATOR 1
|
||||
#else
|
||||
#define HAS_GPS_POWER_REGULATOR 0
|
||||
#endif
|
||||
|
||||
/* ========== GPS Power Strategy ==========
|
||||
* Module power is GPIO/regulator controlled — GNSS driver PM is not used
|
||||
* for the module itself:
|
||||
* - Wio Tracker L1 (L76K): FORCE_ON pin LOW = hardware standby (~360µA,
|
||||
* Vcc stays on, ephemeris/almanac/RTC preserved, hot-start 1-2s)
|
||||
* - Wio Tracker L1 (L76K): P1.09 is the module's WAKEUP pin, not a supply
|
||||
* switch. Per the L76K hardware design: WAKEUP is a digital input, active
|
||||
* low with an internal pull-up, that "enters or exits Standby mode". In
|
||||
* Standby the RF is powered off but the internal core and I/O power domain
|
||||
* stay active, so VCC is never removed and ephemeris/almanac/RTC survive —
|
||||
* every wake is a warm start, not a cold one. (Backup mode, the deeper
|
||||
* state, requires cutting VCC while V_BCKP holds the RTC domain; this
|
||||
* board has no VCC switch, so Standby is the floor available to us.)
|
||||
* - T1000-E (AG3335): GPS_EN LOW + VRTC HIGH = warm standby (ephemeris
|
||||
* preserved via backup RAM, ~1-2µA VRTC current)
|
||||
* - All boards: gps-enable alias → GPIO power control
|
||||
@@ -342,6 +382,68 @@ static void gnss_data_cb(const struct device *dev, const struct gnss_data *data)
|
||||
/* Register GNSS callback for all GNSS devices */
|
||||
GNSS_DATA_CALLBACK_DEFINE(NULL, gnss_data_cb);
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_GPS_SAT_DIAG
|
||||
/* ========== Per-constellation satellite tally (diagnostic) ==========
|
||||
* The Zephyr GSV parser fills gnss_satellite.system from the NMEA talker ID
|
||||
* ($GPGSV/$GLGSV/$GAGSV/$GBGSV), so this is direct evidence of which
|
||||
* constellations the module is actually tracking — the only way to confirm
|
||||
* that the boot-time PMTK353 / UBX-CFG-GNSS configuration was accepted.
|
||||
* A module still in its GPS-only default yields sats_gps only.
|
||||
*
|
||||
* Counts only tracked satellites (is_tracked), not merely visible ones. */
|
||||
static uint8_t sat_count[5]; /* gps, glonass, galileo, beidou, other */
|
||||
static int64_t sat_seen_ms[5]; /* uptime when each bucket was last reported */
|
||||
|
||||
/* A constellation absent for this long is reported as zero. Long enough to
|
||||
* ride out a missed GSV cycle (they repeat at the fix rate), short enough
|
||||
* that a constellation which genuinely drops out stops being claimed. */
|
||||
#define SAT_TALLY_STALE_MS 30000
|
||||
|
||||
static void gnss_satellites_cb(const struct device *dev,
|
||||
const struct gnss_satellite *satellites,
|
||||
uint16_t size)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
uint8_t tally[5] = { 0 };
|
||||
bool seen[5] = { false };
|
||||
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
int idx;
|
||||
|
||||
switch (satellites[i].system) {
|
||||
case GNSS_SYSTEM_GPS: idx = 0; break;
|
||||
case GNSS_SYSTEM_GLONASS: idx = 1; break;
|
||||
case GNSS_SYSTEM_GALILEO: idx = 2; break;
|
||||
case GNSS_SYSTEM_BEIDOU: idx = 3; break;
|
||||
default: idx = 4; break;
|
||||
}
|
||||
|
||||
/* Mark the constellation as reported even when nothing in it is
|
||||
* tracked — that is a real "zero", distinct from "not heard". */
|
||||
seen[idx] = true;
|
||||
if (satellites[i].is_tracked) {
|
||||
tally[idx]++;
|
||||
}
|
||||
}
|
||||
|
||||
/* One GSV burst carries ONE constellation: the parser publishes each
|
||||
* talker's group separately (satellites_length == number_of_svs for
|
||||
* that group). So update only the buckets this burst reported —
|
||||
* replacing all five wholesale wipes the constellations that arrived
|
||||
* in the previous burst, which reads as G0 next to a healthy fix. */
|
||||
k_mutex_lock(&gps_mutex, K_FOREVER);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (seen[i]) {
|
||||
sat_count[i] = tally[i];
|
||||
sat_seen_ms[i] = k_uptime_get();
|
||||
}
|
||||
}
|
||||
k_mutex_unlock(&gps_mutex);
|
||||
}
|
||||
|
||||
GNSS_SATELLITES_CALLBACK_DEFINE(NULL, gnss_satellites_cb);
|
||||
#endif /* CONFIG_ZEPHCORE_GPS_SAT_DIAG */
|
||||
|
||||
/* Find and initialize GNSS device */
|
||||
static const struct device *gnss_dev = NULL;
|
||||
|
||||
@@ -358,6 +460,43 @@ static const struct device *gnss_dev = NULL;
|
||||
* persist). So we only need to configure once. */
|
||||
static bool gnss_configured = false;
|
||||
|
||||
/* ========== Configuration Diagnostics ==========
|
||||
* Module configuration is sent blind — nothing in the protocol path tells us
|
||||
* the module accepted it. This records what was attempted so the operator can
|
||||
* read it back over the CLI on a release build. `gps_set_diag(true)` also
|
||||
* clears gnss_configured, so the next GPS enable re-runs configuration and
|
||||
* refreshes the record. RAM only, never persisted. */
|
||||
enum gps_cfg_path {
|
||||
GPS_CFG_NEVER = 0, /* configuration has not run yet */
|
||||
GPS_CFG_API, /* driver implements the GNSS API (air530z, lc76g, ...) */
|
||||
GPS_CFG_UART, /* passive NMEA listener — raw PMTK + UBX sent */
|
||||
GPS_CFG_BLIND, /* no GNSS API and no writable UART — module defaults */
|
||||
};
|
||||
|
||||
static struct {
|
||||
uint8_t path; /* enum gps_cfg_path */
|
||||
int8_t api_ret; /* gnss_set_enabled_systems() result */
|
||||
int8_t rate_ret; /* gnss_set_fix_rate() result */
|
||||
uint8_t cmds; /* config commands written to the UART */
|
||||
uint16_t bytes; /* bytes written to the UART */
|
||||
int64_t at_ms; /* uptime when configuration last ran */
|
||||
} gps_cfg_diag;
|
||||
|
||||
static bool gps_diag_on = false;
|
||||
|
||||
/* Module identification string, captured by the GNSS driver from the reply to
|
||||
* its version query (CASIC parts answer in-band as a $GPTXT sentence).
|
||||
* Weak so that boards whose driver has no version query still link — an
|
||||
* absent symbol and an empty string mean the same thing to the report.
|
||||
*
|
||||
* Its real value is not the version text: on a transport where every command
|
||||
* is written blind, a captured reply is the only positive proof that the
|
||||
* MCU's TX line reaches the module at all. */
|
||||
extern "C" int zephcore_gnss_version_get(char *buf, size_t len) __attribute__((weak));
|
||||
|
||||
/* True only while gps_configure_via_uart() is running (see gps_uart_send). */
|
||||
static bool gps_cfg_counting = false;
|
||||
|
||||
/* ========== Vendor-Specific Configuration Commands ==========
|
||||
*
|
||||
* The RAK WisBlock GPS slot accepts multiple modules (L76K, ZOE-M8Q, etc.)
|
||||
@@ -372,7 +511,43 @@ static bool gnss_configured = false;
|
||||
|
||||
#if HAS_GPS_UART
|
||||
|
||||
/* --- Quectel L76K (PMTK) configuration --- */
|
||||
/* The UART the GNSS module is connected to. Works for any GNSS-on-UART node
|
||||
* regardless of compatible string. */
|
||||
static const struct device *gps_uart_dev = DEVICE_DT_GET(DT_BUS(DT_NODELABEL(gnss)));
|
||||
|
||||
/* Send raw bytes to the GPS UART using blocking poll_out.
|
||||
* Safe to call even though modem_chat/modem_ubx owns the UART pipe:
|
||||
* uart_poll_out writes one byte at a time through the TX register,
|
||||
* and GNSS modules are receive-only (no TX contention).
|
||||
*
|
||||
* Gated on HAS_GPS_UART alone — writing to the module is safe on every
|
||||
* UART-attached GNSS. The narrower power-control gate below applies to the
|
||||
* software *sleep* commands, which are only a fallback for boards that
|
||||
* cannot cut GPS power. */
|
||||
static void gps_uart_send(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!device_is_ready(gps_uart_dev)) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uart_poll_out(gps_uart_dev, data[i]);
|
||||
}
|
||||
/* Count only configuration traffic — the same helper carries the
|
||||
* sleep/wake commands, which would otherwise inflate the tally. */
|
||||
if (gps_cfg_counting) {
|
||||
gps_cfg_diag.cmds++;
|
||||
gps_cfg_diag.bytes += (uint16_t)len;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- MediaTek-family (PMTK) configuration ---
|
||||
*
|
||||
* PMTK is MediaTek's protocol. It applies to genuine MTK parts (L76B and
|
||||
* relatives). It does NOT apply to the Quectel L76K/L76KB or Air530Z, which
|
||||
* are CASIC silicon and speak PCAS — their protocol specification contains no
|
||||
* PMTK command at all, so these sentences are inert there. Those modules are
|
||||
* driven by the air530z driver via the GNSS API instead and never reach this
|
||||
* path. Kept because the RAK WisBlock GPS slot can hold an MTK part. */
|
||||
|
||||
/* PMTK353: Enable GPS + GLONASS + Galileo + BeiDou (no QZSS).
|
||||
* Default is GPS-only. Multi-constellation dramatically improves TTFF
|
||||
@@ -391,20 +566,172 @@ static const char pmtk_easy[] = "$PMTK869,1,1*35\r\n";
|
||||
* Especially useful when GPS antenna is near the SX1262 + SKY66122 PA. */
|
||||
static const char pmtk_aic[] = "$PMTK286,1*23\r\n";
|
||||
|
||||
/* --- CASIC (PCAS) configuration ---
|
||||
*
|
||||
* The Quectel L76K/L76KB (RAK12501) and Air530Z are CASIC silicon: they speak
|
||||
* neither PMTK nor UBX, so without these sentences such a module in a
|
||||
* WisBlock slot receives no configuration at all and sits on its factory
|
||||
* defaults. Boards that always carry one use the air530z driver and the GNSS
|
||||
* API instead; these are for the generic-NMEA boards whose GPS slot can hold
|
||||
* any module.
|
||||
*
|
||||
* Inert on the other families, same as PMTK and UBX are here. */
|
||||
|
||||
/* PCAS03: NMEA sentence selection. Field order is
|
||||
* GGA,GLL,GSA,GSV,RMC,VTG,ZDA,ANT,... — keep GGA + RMC (position, time) and
|
||||
* add GSV only when the satellite tally needs it, to keep the 9600-baud link
|
||||
* from spending its budget on sentences nobody parses. */
|
||||
#ifdef CONFIG_ZEPHCORE_GPS_SAT_DIAG
|
||||
static const char pcas_sentences[] = "$PCAS03,1,0,0,1,1,0,0,0,0,0,0,0,0*1F\r\n";
|
||||
#else
|
||||
static const char pcas_sentences[] = "$PCAS03,1,0,0,0,1,0,0,0,0,0,0,0,0*1E\r\n";
|
||||
#endif
|
||||
|
||||
/* PCAS04,7 = GPS + BeiDou + GLONASS, everything the part supports.
|
||||
* (No Galileo on these modules — that is silicon, not configuration.) */
|
||||
static const char pcas_constellations[] = "$PCAS04,7*1E\r\n";
|
||||
|
||||
/* PCAS11: navigation dynamic model. Stored IN THE MODULE and survives
|
||||
* reflashing the host, so a slot module that previously lived in another
|
||||
* device can arrive stuck in an automotive or airborne model that quietly
|
||||
* degrades fixes on a fixed site. See CONFIG_ZEPHCORE_GPS_NAV_MODE. */
|
||||
#if CONFIG_ZEPHCORE_GPS_NAV_MODE == 0
|
||||
static const char pcas_nav_mode[] = "$PCAS11,0*1D\r\n";
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 1
|
||||
static const char pcas_nav_mode[] = "$PCAS11,1*1C\r\n";
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 2
|
||||
static const char pcas_nav_mode[] = "$PCAS11,2*1F\r\n";
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 3
|
||||
static const char pcas_nav_mode[] = "$PCAS11,3*1E\r\n";
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 4
|
||||
static const char pcas_nav_mode[] = "$PCAS11,4*19\r\n";
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 5
|
||||
static const char pcas_nav_mode[] = "$PCAS11,5*18\r\n";
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 6
|
||||
static const char pcas_nav_mode[] = "$PCAS11,6*1B\r\n";
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 7
|
||||
static const char pcas_nav_mode[] = "$PCAS11,7*1A\r\n";
|
||||
#endif
|
||||
|
||||
/* PCAS06,0: ask the module to identify itself. A CASIC part answers in-band
|
||||
* with a $GPTXT sentence — the only readable reply available on this
|
||||
* otherwise write-only path, and therefore the only positive proof that the
|
||||
* MCU's TX line reaches the module at all. Captured by the GNSS driver and
|
||||
* surfaced as `mod=` in "get gps diag". */
|
||||
static const char pcas_version_query[] = "$PCAS06,0*1B\r\n";
|
||||
|
||||
/* u-blox equivalent of the query above. u-blox ignore $PCAS06 entirely, so
|
||||
* without this a u-blox module always reports "no reply" and the TX-path
|
||||
* proof — the whole point of asking — is unavailable on those boards. The
|
||||
* $PUBX,04 reply carries time and clock status, not a version; what matters
|
||||
* is that a reply arrives at all, which only happens if the module received
|
||||
* the request. */
|
||||
static const char pubx_version_query[] = "$PUBX,04*37\r\n";
|
||||
|
||||
/* --- u-blox NMEA output trim ($PUBX,40) ---
|
||||
*
|
||||
* THE LINK BUDGET IS THE CONSTRAINT, and it is easy to blow past it. At 9600
|
||||
* baud only ~960 bytes/s fit. With multi-GNSS enabled a u-blox emits, per
|
||||
* second: GGA + RMC + GLL + VTG, one GSA per constellation, and a GSV burst
|
||||
* that grows with satellite count — roughly 1030 bytes/s at ~26 SVs. The
|
||||
* stream then cannot fit in the second it is generated in, sentences are
|
||||
* truncated or dropped, and the symptom is not "slow GPS" but a receiver
|
||||
* that appears to have stopped: no parseable GGA, so no fix, so no position.
|
||||
*
|
||||
* Enabling constellations without trimming output is therefore actively
|
||||
* harmful on a 9600-baud link. We already do exactly this trim for CASIC
|
||||
* parts via $PCAS03; u-blox had no equivalent, which is the asymmetry this
|
||||
* fixes. GLL, GSA and VTG are dropped outright — nothing in the driver parses
|
||||
* them — and GSV is kept only when the satellite tally needs it.
|
||||
*
|
||||
* Dropping GLL/GSA/VTG takes ~1030 -> ~650 bytes/s; dropping GSV as well
|
||||
* takes it to ~160. */
|
||||
static const char pubx_off_gll[] = "$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n";
|
||||
static const char pubx_off_gsa[] = "$PUBX,40,GSA,0,0,0,0,0,0*4E\r\n";
|
||||
static const char pubx_off_vtg[] = "$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n";
|
||||
#ifndef CONFIG_ZEPHCORE_GPS_SAT_DIAG
|
||||
static const char pubx_off_gsv[] = "$PUBX,40,GSV,0,0,0,0,0,0*59\r\n";
|
||||
#endif
|
||||
|
||||
/* --- u-blox ZOE-M8Q (UBX binary) configuration --- */
|
||||
|
||||
/* UBX-CFG-GNSS: Enable GPS + Galileo + BeiDou + GLONASS.
|
||||
* ZOE-M8Q defaults to GPS-only. Multi-constellation dramatically improves
|
||||
* TTFF and fix reliability — more visible satellites in any sky condition.
|
||||
* 32 tracking channels allocated across 4 active systems.
|
||||
* SBAS disabled — needs 30-60s to download corrections, useless for our
|
||||
* quick-fix-then-sleep pattern (companions: 30s, repeaters: 5min).
|
||||
* QZSS disabled — Japan regional, wastes tracking channels elsewhere. */
|
||||
/* UBX-CFG-PRT: force UART1 to 9600 8N1 with BOTH UBX and NMEA enabled in and
|
||||
* out. Sent first, before anything that depends on the module talking to us.
|
||||
*
|
||||
* This exists because the module's port configuration is persistent and not
|
||||
* necessarily ours. RAK's own RAK12500 example — and any host using the
|
||||
* SparkFun u-blox library — calls setUART1Output(COM_TYPE_UBX) followed by
|
||||
* saveConfiguration(), which stores "UBX only, NMEA off" in the module's
|
||||
* flash. A module that has ever been driven that way stays silent on an
|
||||
* NMEA-only host forever after, through power cycles and reflashes, and
|
||||
* presents as a completely dead receiver: no GGA, no fix, no reply to any
|
||||
* query. Re-asserting the port configuration costs one frame and removes a
|
||||
* failure mode that is otherwise almost impossible to diagnose from the host.
|
||||
*
|
||||
* Limitation: if the module was also saved at a different baud rate, it will
|
||||
* not parse this frame either. Recovering from that needs a baud scan, which
|
||||
* the devicetree's fixed current-speed does not currently allow. */
|
||||
static const uint8_t ubx_cfg_prt_uart1[] = {
|
||||
0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC0, 0x08,
|
||||
0x00, 0x00, 0x80, 0x25, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x8E, 0x95
|
||||
};
|
||||
|
||||
/* UBX-CFG-GNSS: Enable GPS + Galileo + GLONASS (+ QZSS) on u-blox M8.
|
||||
*
|
||||
* Config block layout is gnssId, resTrkCh, maxTrkCh, reserved0, flags[4] —
|
||||
* EIGHT bytes, and the payload length must be exactly 4 + 8*numConfigBlocks
|
||||
* or the receiver rejects the whole message. The previous version of this
|
||||
* frame omitted reserved0, giving 7-byte blocks and a 39-byte payload where
|
||||
* numConfigBlocks=5 demanded 44, so no u-blox module ever accepted it.
|
||||
*
|
||||
* Only THREE major GNSS (GPS/Galileo/GLONASS/BeiDou) can run concurrently on
|
||||
* M8, so BeiDou is explicitly disabled rather than left alone: if the module
|
||||
* came up with BeiDou on, enabling three others would make four and the
|
||||
* message would be refused.
|
||||
*
|
||||
* QZSS is enabled even though it is Japan-regional and costs ~3 channels —
|
||||
* u-blox require GPS and QZSS to be both enabled or both disabled (they
|
||||
* share L1 C/A), and a mismatch is grounds for rejection.
|
||||
*
|
||||
* SBAS stays off: it needs 30-60 s to download corrections, useless for our
|
||||
* quick-fix-then-sleep pattern (companions 30 s, repeaters 5 min).
|
||||
*
|
||||
* numTrkChHw = 0 and numTrkChUse = 0xFF (read-only / "use max available").
|
||||
*
|
||||
* resTrkCh MUST be 0 on the disabled blocks. Reserving tracking channels for
|
||||
* a system whose enable bit is clear is self-contradictory, and the receiver
|
||||
* validates CFG-GNSS atomically — one bad block rejects all six. An earlier
|
||||
* revision left SBAS at 1 and BeiDou at 8 and the whole frame was refused
|
||||
* (observed on hardware: RAK12500/ZOE-M8Q stayed GPS-only, sys=G8/R0/E0/B0).
|
||||
*
|
||||
* NOTE — this is an M8 frame. u-blox 7 parts (MAX-7Q on the RAK1910) have no
|
||||
* Galileo or BeiDou and use a different sigCfgMask, so they will refuse it
|
||||
* and stay GPS-only. Sending an M7 frame as well is NOT safe blind: its
|
||||
* sigCfgMask of 0 would be a signal-disabling value on M8. */
|
||||
static const uint8_t ubx_cfg_gnss[] = {
|
||||
0xB5, 0x62, 0x06, 0x3E, 0x27, 0x00, 0x00, 0x20, 0x20, 0x05, 0x00, 0x08,
|
||||
0x10, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x0A, 0x01, 0x00, 0x01, 0x00,
|
||||
0x03, 0x04, 0x0A, 0x01, 0x00, 0x01, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00,
|
||||
0x01, 0x00, 0x06, 0x04, 0x0A, 0x01, 0x00, 0x01, 0x00, 0x0E, 0x13
|
||||
0xB5, 0x62, 0x06, 0x3E, 0x34, 0x00, 0x00, 0x00, 0xFF, 0x06, 0x00, 0x08,
|
||||
0x10, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x01, 0x01, 0x02, 0x04, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x03, 0x00,
|
||||
0x10, 0x00, 0x00, 0x00, 0x01, 0x01, 0x05, 0x00, 0x03, 0x00, 0x01, 0x00,
|
||||
0x01, 0x01, 0x06, 0x08, 0x0E, 0x00, 0x01, 0x00, 0x01, 0x01, 0xEE, 0x64
|
||||
};
|
||||
|
||||
/* UBX-CFG-NMEA: switch the NMEA output to version 4.10.
|
||||
*
|
||||
* Without this, Galileo and BeiDou satellites cannot be reported at all: the
|
||||
* $GAGSV and $GBGSV talker IDs only exist from NMEA 4.10, and M8 firmware
|
||||
* defaults lower. So a fully successful CFG-GNSS would still show zero
|
||||
* Galileo in "get gps diag" — a reporting limit masquerading as a config
|
||||
* failure. Mirrors Meshtastic's "enable NMEA 4.10" step (src/gps/ubx.h).
|
||||
*
|
||||
* gsvTalkerId = 0 (use the GNSS-specific talker per constellation) is what
|
||||
* makes the per-constellation tally work — do not set it to 1, which forces
|
||||
* every GSV onto the main talker and would collapse the tally into GPS. */
|
||||
static const uint8_t ubx_cfg_nmea_410[] = {
|
||||
0xB5, 0x62, 0x06, 0x17, 0x14, 0x00, 0x00, 0x41, 0x00, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x75, 0x57
|
||||
};
|
||||
|
||||
/* UBX-CFG-NAV5: Set 5° minimum satellite elevation.
|
||||
@@ -413,12 +740,15 @@ static const uint8_t ubx_cfg_gnss[] = {
|
||||
* everything including horizon-level junk.
|
||||
* Dynamic model left at factory default (Portable) — works for fixed
|
||||
* repeaters, walking companions, and vehicles alike.
|
||||
* apply mask 0x0002 = minEl(bit1) only */
|
||||
* apply mask 0x0002 = minEl(bit1) only
|
||||
*
|
||||
* The trailing checksum was CK_B=0x37 (should be 0xE7) — a one-byte typo
|
||||
* that made every receiver drop this frame silently, with no NAK. */
|
||||
static const uint8_t ubx_cfg_nav5_minelev[] = {
|
||||
0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x37
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xE7
|
||||
};
|
||||
|
||||
/* UBX-CFG-NAVX5: Enable AssistNow Autonomous (AOP).
|
||||
@@ -443,20 +773,73 @@ static const uint8_t ubx_cfg_save[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x31, 0xBF
|
||||
};
|
||||
|
||||
/* Send a PMTK command string (including \r\n). Adds small delay after. */
|
||||
static void gps_send_pmtk(const char *cmd)
|
||||
/* Settle time after a command that makes the receiver restart its navigation
|
||||
* engine — constellation changes on every family do this. A command issued
|
||||
* into a restarting engine is simply lost, and because everything here is
|
||||
* written blind the loss is silent. 20-50 ms was optimistic. */
|
||||
#define GPS_CFG_SETTLE_MS 120
|
||||
#define GPS_CFG_RESTART_MS 500
|
||||
|
||||
/* Send an NMEA command string (including \r\n). */
|
||||
static void gps_send_nmea(const char *cmd, uint32_t settle_ms = GPS_CFG_SETTLE_MS)
|
||||
{
|
||||
gps_uart_send((const uint8_t *)cmd, strlen(cmd));
|
||||
k_msleep(20); /* Let module process before next command */
|
||||
k_msleep(settle_ms);
|
||||
}
|
||||
|
||||
/* Send a UBX binary frame. Adds small delay after for processing. */
|
||||
static void gps_send_ubx(const uint8_t *frame, size_t len)
|
||||
/* Send a UBX binary frame. */
|
||||
static void gps_send_ubx(const uint8_t *frame, size_t len,
|
||||
uint32_t settle_ms = GPS_CFG_SETTLE_MS)
|
||||
{
|
||||
gps_uart_send(frame, len);
|
||||
k_msleep(50); /* UBX needs more time to ACK + apply config */
|
||||
k_msleep(settle_ms);
|
||||
}
|
||||
|
||||
/* u-blox dynamic model for CFG-NAV5, translated from the CASIC-numbered
|
||||
* CONFIG_ZEPHCORE_GPS_NAV_MODE so one setting drives both families.
|
||||
* u-blox: 0 portable, 2 stationary, 3 pedestrian, 4 automotive, 5 sea,
|
||||
* 6-8 airborne. */
|
||||
#if CONFIG_ZEPHCORE_GPS_NAV_MODE == 1
|
||||
#define UBX_DYNMODEL 2
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 2
|
||||
#define UBX_DYNMODEL 3
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 3
|
||||
#define UBX_DYNMODEL 4
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 4
|
||||
#define UBX_DYNMODEL 5
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE >= 5
|
||||
#define UBX_DYNMODEL 6
|
||||
#elif CONFIG_ZEPHCORE_GPS_NAV_MODE == 0
|
||||
#define UBX_DYNMODEL 0
|
||||
#endif
|
||||
|
||||
/* Send CFG-NAV5 with the dynamic model patched in and the checksum redone.
|
||||
* A stationary model on a fixed node suppresses position wander and lets the
|
||||
* receiver apply much tighter velocity assumptions; leaving a roof-mounted
|
||||
* repeater on the factory Portable model throws that away. Patched at runtime
|
||||
* rather than as eight hard-coded frames — one place to get the checksum
|
||||
* right instead of eight. */
|
||||
#ifdef UBX_DYNMODEL
|
||||
static void gps_send_ubx_nav5(void)
|
||||
{
|
||||
uint8_t f[sizeof(ubx_cfg_nav5_minelev)];
|
||||
|
||||
memcpy(f, ubx_cfg_nav5_minelev, sizeof(f));
|
||||
f[6] = 0x03; /* mask: dyn (bit0) + minEl (bit1) */
|
||||
f[8] = UBX_DYNMODEL; /* dynModel */
|
||||
|
||||
uint8_t ck_a = 0, ck_b = 0;
|
||||
for (size_t i = 2; i < sizeof(f) - 2; i++) {
|
||||
ck_a = (uint8_t)(ck_a + f[i]);
|
||||
ck_b = (uint8_t)(ck_b + ck_a);
|
||||
}
|
||||
f[sizeof(f) - 2] = ck_a;
|
||||
f[sizeof(f) - 1] = ck_b;
|
||||
|
||||
gps_send_ubx(f, sizeof(f));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Configure the GPS module with optimal settings for a mesh repeater.
|
||||
* Sends both PMTK (Quectel) and UBX (u-blox) commands — the module that
|
||||
* isn't present ignores bytes it doesn't understand. */
|
||||
@@ -464,21 +847,91 @@ static void gps_configure_via_uart(void)
|
||||
{
|
||||
LOG_INF("GPS: Configuring via UART (PMTK + UBX dual-protocol)");
|
||||
|
||||
/* --- Quectel L76K (PMTK) --- */
|
||||
gps_send_pmtk(pmtk_constellations);
|
||||
gps_send_pmtk(pmtk_easy);
|
||||
gps_send_pmtk(pmtk_aic);
|
||||
gps_cfg_diag.cmds = 0;
|
||||
gps_cfg_diag.bytes = 0;
|
||||
gps_cfg_counting = true;
|
||||
|
||||
/* --- MediaTek-family (PMTK) --- */
|
||||
gps_send_nmea(pmtk_constellations, GPS_CFG_RESTART_MS);
|
||||
gps_send_nmea(pmtk_easy);
|
||||
gps_send_nmea(pmtk_aic);
|
||||
LOG_INF("GPS: PMTK config sent (constellations, EASY, AIC)");
|
||||
|
||||
/* --- u-blox ZOE-M8Q (UBX) --- */
|
||||
gps_send_ubx(ubx_cfg_gnss, sizeof(ubx_cfg_gnss));
|
||||
/* --- CASIC (PCAS) ---
|
||||
* Version query last, so its $GPTXT reply is not stepped on by a
|
||||
* constellation restart. */
|
||||
gps_send_nmea(pcas_sentences);
|
||||
gps_send_nmea(pcas_constellations, GPS_CFG_RESTART_MS);
|
||||
#if CONFIG_ZEPHCORE_GPS_NAV_MODE >= 0
|
||||
gps_send_nmea(pcas_nav_mode);
|
||||
#endif
|
||||
gps_send_nmea(pcas_version_query);
|
||||
LOG_INF("GPS: PCAS config sent (sentences, constellations, nav mode, version query)");
|
||||
|
||||
/* --- u-blox ZOE-M8Q (UBX) ---
|
||||
* NMEA 4.10 goes first: it governs whether the constellations enabled
|
||||
* by the next frame can be *reported* at all. CFG-GNSS restarts the
|
||||
* navigation engine, so it gets the long settle before the frames that
|
||||
* follow it — at 50 ms they were being issued into a restarting
|
||||
* receiver. CFG-CFG stays last so it only persists whatever was
|
||||
* actually accepted; note that makes a rejected configuration sticky
|
||||
* too, which is why a frame bug here survives power cycles. */
|
||||
/* Make sure NMEA output is even switched on before anything else — a
|
||||
* module saved as UBX-only by a previous host is otherwise mute. */
|
||||
gps_send_ubx(ubx_cfg_prt_uart1, sizeof(ubx_cfg_prt_uart1), GPS_CFG_RESTART_MS);
|
||||
|
||||
/* Trim the NMEA stream BEFORE enabling more constellations — the extra
|
||||
* GSV traffic must have somewhere to fit. */
|
||||
gps_send_nmea(pubx_off_gll);
|
||||
gps_send_nmea(pubx_off_gsa);
|
||||
gps_send_nmea(pubx_off_vtg);
|
||||
#ifndef CONFIG_ZEPHCORE_GPS_SAT_DIAG
|
||||
gps_send_nmea(pubx_off_gsv);
|
||||
#endif
|
||||
gps_send_ubx(ubx_cfg_nmea_410, sizeof(ubx_cfg_nmea_410));
|
||||
gps_send_ubx(ubx_cfg_gnss, sizeof(ubx_cfg_gnss), GPS_CFG_RESTART_MS);
|
||||
#ifdef UBX_DYNMODEL
|
||||
gps_send_ubx_nav5();
|
||||
#else
|
||||
gps_send_ubx(ubx_cfg_nav5_minelev, sizeof(ubx_cfg_nav5_minelev));
|
||||
#endif
|
||||
gps_send_ubx(ubx_cfg_navx5_aop, sizeof(ubx_cfg_navx5_aop));
|
||||
gps_send_ubx(ubx_cfg_save, sizeof(ubx_cfg_save));
|
||||
LOG_INF("GPS: UBX config sent (multi-GNSS, 5° min elev, AOP, saved to flash)");
|
||||
/* Ask a u-blox to say something back — the TX-path proof for this
|
||||
* family, sent last so the reply is not stepped on by a restart. */
|
||||
gps_send_nmea(pubx_version_query);
|
||||
LOG_INF("GPS: UBX config sent (NMEA 4.10, multi-GNSS, 5° min elev, AOP, saved)");
|
||||
|
||||
gps_cfg_counting = false;
|
||||
}
|
||||
#endif /* HAS_GPS_UART */
|
||||
|
||||
/* Re-run module configuration on a GPS enable, but only when diagnostics are
|
||||
* armed — this is a deliberate, operator-triggered action, not a normal path.
|
||||
*
|
||||
* Only the raw-UART path is re-runnable. gnss_configure()'s API path goes
|
||||
* through modem_chat_run_script(), which is safe at boot only: after a GPIO
|
||||
* power restore the chip needs ~300 ms and calling it here deadlocks the main
|
||||
* thread. So on API-driver boards this records nothing new and "get gps diag"
|
||||
* keeps reporting the boot-time result, which is the honest answer. */
|
||||
static void gps_diag_maybe_reconfigure(void)
|
||||
{
|
||||
if (!gps_diag_on || gnss_configured || gnss_dev == NULL) {
|
||||
return;
|
||||
}
|
||||
#if HAS_GPS_UART
|
||||
if (gps_cfg_diag.path == GPS_CFG_UART || gps_cfg_diag.path == GPS_CFG_NEVER) {
|
||||
/* The module has just been powered; give it time to boot before
|
||||
* clocking configuration at it (same ~300 ms the modem needs). */
|
||||
k_msleep(300);
|
||||
gps_cfg_diag.path = GPS_CFG_UART;
|
||||
gps_cfg_diag.at_ms = k_uptime_get();
|
||||
gps_configure_via_uart();
|
||||
}
|
||||
#endif
|
||||
gnss_configured = true;
|
||||
}
|
||||
|
||||
static void gnss_configure(void)
|
||||
{
|
||||
if (gnss_configured || gnss_dev == NULL) {
|
||||
@@ -496,15 +949,21 @@ static void gnss_configure(void)
|
||||
systems = GNSS_SYSTEM_GPS | GNSS_SYSTEM_GLONASS | GNSS_SYSTEM_BEIDOU;
|
||||
ret = gnss_set_enabled_systems(gnss_dev, systems);
|
||||
}
|
||||
gps_cfg_diag.api_ret = (int8_t)ret;
|
||||
gps_cfg_diag.at_ms = k_uptime_get();
|
||||
|
||||
if (ret == 0) {
|
||||
LOG_INF("GPS: Multi-constellation enabled via GNSS API");
|
||||
gps_cfg_diag.path = GPS_CFG_API;
|
||||
} else if (ret == -ENOSYS || ret == -ENOTSUP) {
|
||||
#if HAS_GPS_UART
|
||||
/* gnss-nmea-generic is a passive listener — no GNSS API.
|
||||
* Configure everything via direct UART commands instead. */
|
||||
gps_cfg_diag.path = GPS_CFG_UART;
|
||||
gps_configure_via_uart();
|
||||
#else
|
||||
LOG_INF("GPS: No GNSS API and no UART access — using module defaults");
|
||||
gps_cfg_diag.path = GPS_CFG_BLIND;
|
||||
#endif
|
||||
} else {
|
||||
LOG_WRN("GPS: Failed to set constellations: %d", ret);
|
||||
@@ -514,6 +973,7 @@ static void gnss_configure(void)
|
||||
|
||||
/* Set 1Hz fix rate (explicit, don't rely on chip defaults) */
|
||||
ret = gnss_set_fix_rate(gnss_dev, 1000);
|
||||
gps_cfg_diag.rate_ret = (int8_t)ret;
|
||||
if (ret == 0) {
|
||||
LOG_INF("GPS: Fix rate set to 1Hz");
|
||||
} else if (ret != -ENOSYS && ret != -ENOTSUP) {
|
||||
@@ -538,11 +998,8 @@ static void gnss_configure(void)
|
||||
* - T1000-E: P1.11 (GPS_EN), P0.8 (GPS_VRTC_EN), P1.15 (GPS_RESET), P1.12 (GPS_SLEEP_INT)
|
||||
* - Wio Tracker L1: P1.09 (GPS power, shared with luatos,air530z on-off-gpios)
|
||||
*/
|
||||
#if DT_NODE_EXISTS(DT_ALIAS(gps_enable))
|
||||
#if HAS_GPS_POWER_CONTROL
|
||||
static const struct gpio_dt_spec gps_enable_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps_enable), gpios);
|
||||
#define HAS_GPS_POWER_CONTROL 1
|
||||
#else
|
||||
#define HAS_GPS_POWER_CONTROL 0
|
||||
#endif
|
||||
|
||||
/* GPS powered by a PMU regulator rail instead of a discrete enable GPIO (e.g.
|
||||
@@ -551,15 +1008,12 @@ static const struct gpio_dt_spec gps_enable_gpio = GPIO_DT_SPEC_GET(DT_ALIAS(gps
|
||||
* switch driven by enable/disable; the duty-cycle standby/wake uses software
|
||||
* sleep/wake (UART) and leaves the rail up, so the regulator is only toggled on
|
||||
* the (unguarded) enable/disable/boot paths — never per duty cycle. */
|
||||
#if DT_NODE_EXISTS(DT_CHOSEN(zephcore_gps_power))
|
||||
#if HAS_GPS_POWER_REGULATOR
|
||||
static const struct device *const gps_power_reg =
|
||||
DEVICE_DT_GET(DT_CHOSEN(zephcore_gps_power));
|
||||
/* Tracks our intended rail state so enable/disable stay balanced (idempotent).
|
||||
* Starts true: the rail is `regulator-boot-on`, so it is already up at boot. */
|
||||
static bool gps_reg_enabled = true;
|
||||
#define HAS_GPS_POWER_REGULATOR 1
|
||||
#else
|
||||
#define HAS_GPS_POWER_REGULATOR 0
|
||||
#endif
|
||||
|
||||
/* AXP2101 backup (button-battery) charger — feeds the GPS receiver's V_BCKP
|
||||
@@ -860,24 +1314,24 @@ void gps_power_off_for_shutdown(void)
|
||||
* 3V3_S rail is shared with the LoRa FEM), we send vendor-specific UART
|
||||
* commands to put the GPS module into low-power mode.
|
||||
*
|
||||
* Strategy: send BOTH Quectel and u-blox sleep commands — the module that
|
||||
* Strategy: send BOTH MediaTek and u-blox sleep commands — the module that
|
||||
* isn't present simply ignores the bytes it doesn't understand.
|
||||
*
|
||||
* - Quectel L76K (RAK1910): $PMTK161,0*28\r\n → standby (~1mA), wake on UART
|
||||
* - u-blox ZOE-M8Q (RAK12500): UBX-RXM-PMREQ → backup (~7µA), wake on UART
|
||||
* - MediaTek (e.g. L76B): $PMTK161,0*28\r\n → standby (~1mA), wake on UART
|
||||
* - u-blox ZOE-M8Q (RAK12500): UBX-RXM-PMREQ → backup (~7µA), wake on UART
|
||||
*
|
||||
* Neither reaches a CASIC part (L76K/L76KB/Air530Z): those ignore PMTK, UBX
|
||||
* and PCAS12 sleep commands alike — verified on hardware, which is why the
|
||||
* boards carrying them duty-cycle with a power GPIO instead. Note also that
|
||||
* the RAK1910 is a u-blox MAX-7Q, not an L76K, despite older comments here.
|
||||
*
|
||||
* Wake: any byte on UART wakes both modules from their low-power modes.
|
||||
* After wake, the module resumes outputting NMEA autonomously.
|
||||
*/
|
||||
|
||||
/* Get the UART device that the GNSS module is connected to.
|
||||
* Works for any GNSS-on-UART node regardless of compatible string. */
|
||||
#if DT_NODE_HAS_STATUS(DT_NODELABEL(gnss), okay) && \
|
||||
DT_NODE_HAS_STATUS(DT_BUS(DT_NODELABEL(gnss)), okay)
|
||||
#define HAS_GPS_UART 1
|
||||
#else
|
||||
#define HAS_GPS_UART 0
|
||||
#endif
|
||||
/* HAS_GPS_UART, gps_uart_dev and gps_uart_send are defined near the top of
|
||||
* this file (see "GPS Feature Detection") — they are needed by the boot-time
|
||||
* module configuration, which runs long before this section. */
|
||||
|
||||
/* ========== GNSS UART Suspend/Resume (device PM) ==========
|
||||
* nRF UARTE only. An armed UARTE RX holds HFCLK (~0.5-1 mA on nRF52840)
|
||||
@@ -921,11 +1375,6 @@ void gps_power_off_for_shutdown(void)
|
||||
#define HAS_GPS_UART_PM 0
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_UART && \
|
||||
(HAS_GPS_UART_PM || (!HAS_GPS_POWER_CONTROL && !HAS_GPS_POWER_REGULATOR))
|
||||
static const struct device *gps_uart_dev = DEVICE_DT_GET(DT_BUS(DT_NODELABEL(gnss)));
|
||||
#endif
|
||||
|
||||
/* Suspend/resume the GNSS UART. Main thread only (like all GPS power
|
||||
* paths — pm_device_action_run() calls the driver synchronously).
|
||||
* Ordering: resume BEFORE powering the module / sending the wake byte;
|
||||
@@ -965,22 +1414,13 @@ static inline void gps_uart_set_power(bool on) { ARG_UNUSED(on); }
|
||||
#endif
|
||||
|
||||
#if HAS_GPS_UART && !HAS_GPS_POWER_CONTROL && !HAS_GPS_POWER_REGULATOR
|
||||
/* Send raw bytes to the GPS UART using blocking poll_out.
|
||||
* Safe to call even though modem_chat/modem_ubx owns the UART pipe:
|
||||
* uart_poll_out writes one byte at a time through the TX register,
|
||||
* and GNSS modules are receive-only (no TX contention). */
|
||||
static void gps_uart_send(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!device_is_ready(gps_uart_dev)) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uart_poll_out(gps_uart_dev, data[i]);
|
||||
}
|
||||
}
|
||||
/* gps_uart_send() lives near the top of the file — see "GPS Feature
|
||||
* Detection". Only the sleep/wake commands below are gated on this board
|
||||
* having no hardware GPS power control. */
|
||||
|
||||
/* Quectel L76K: $PMTK161,0*28\r\n → enter standby mode
|
||||
* Module stops NMEA output and draws ~1mA. Wakes on any UART RX byte. */
|
||||
/* MediaTek parts: $PMTK161,0*28\r\n → enter standby mode
|
||||
* Module stops NMEA output and draws ~1mA. Wakes on any UART RX byte.
|
||||
* Inert on CASIC parts (L76K and relatives) — they have no such command. */
|
||||
static const uint8_t pmtk_standby[] = "$PMTK161,0*28\r\n";
|
||||
|
||||
/* u-blox ZOE-M8Q: UBX-RXM-PMREQ → enter backup mode
|
||||
@@ -1012,7 +1452,7 @@ static void gps_software_sleep(void)
|
||||
{
|
||||
LOG_INF("GPS: Sending software sleep (PMTK + UBX)");
|
||||
|
||||
/* Quectel L76K standby */
|
||||
/* MediaTek standby */
|
||||
gps_uart_send(pmtk_standby, sizeof(pmtk_standby) - 1); /* exclude null terminator */
|
||||
|
||||
/* Small delay between commands — let the first one drain */
|
||||
@@ -1540,6 +1980,7 @@ void gps_enable(bool enable)
|
||||
* Do NOT call modem_chat_run_script() here — the chip needs
|
||||
* ~300ms to boot after GPIO power restore and calling it
|
||||
* immediately deadlocks the main thread. */
|
||||
gps_diag_maybe_reconfigure();
|
||||
|
||||
/* Bounded first-acquisition window, then the normal duty cycle —
|
||||
* unless always-on (interval 0), where GPS never sleeps. */
|
||||
@@ -1781,6 +2222,93 @@ void gps_request_fresh_fix(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
void gps_set_diag(bool on)
|
||||
{
|
||||
gps_diag_on = on;
|
||||
#if HAS_GNSS
|
||||
if (on) {
|
||||
/* Re-arm configuration so the next GPS enable ("gps off" then
|
||||
* "gps on") actually re-runs it and refreshes the record. */
|
||||
gnss_configured = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool gps_get_diag(void)
|
||||
{
|
||||
return gps_diag_on;
|
||||
}
|
||||
|
||||
void gps_get_diag_report(char *buf, size_t len)
|
||||
{
|
||||
#if HAS_GNSS
|
||||
static const char *const path_str[] = { "never", "api", "uart", "blind" };
|
||||
uint8_t path = gps_cfg_diag.path;
|
||||
const char *pname = (path < ARRAY_SIZE(path_str)) ? path_str[path] : "?";
|
||||
|
||||
if (path == GPS_CFG_NEVER) {
|
||||
snprintf(buf, len, "diag=%s cfg=never-run (enable, then 'gps off'/'gps on')",
|
||||
gps_diag_on ? "on" : "off");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t age_s = (uint32_t)((k_uptime_get() - gps_cfg_diag.at_ms) / 1000);
|
||||
size_t n = (size_t)snprintf(buf, len, "diag=%s cfg=%s age=%us",
|
||||
gps_diag_on ? "on" : "off", pname, age_s);
|
||||
if (n >= len) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Module identity, when the driver could obtain it. Present means the
|
||||
* module answered us, i.e. the TX path is good; absent on a driver that
|
||||
* asks is a strong hint the module never hears our configuration. */
|
||||
if (zephcore_gnss_version_get != NULL) {
|
||||
char ver[40];
|
||||
if (zephcore_gnss_version_get(ver, sizeof(ver)) > 0) {
|
||||
n += (size_t)snprintf(buf + n, len - n, " mod=%s", ver);
|
||||
} else {
|
||||
n += (size_t)snprintf(buf + n, len - n, " mod=no-reply");
|
||||
}
|
||||
if (n >= len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (path == GPS_CFG_UART) {
|
||||
/* Bytes actually clocked out to the module. Note these are sent
|
||||
* blind — this proves transmission, not acceptance. Constellation
|
||||
* tallies below are the acceptance evidence. */
|
||||
n += (size_t)snprintf(buf + n, len - n, " sent=%u/%uB",
|
||||
gps_cfg_diag.cmds, gps_cfg_diag.bytes);
|
||||
} else if (path == GPS_CFG_API) {
|
||||
n += (size_t)snprintf(buf + n, len - n, " sys_ret=%d rate_ret=%d",
|
||||
gps_cfg_diag.api_ret, gps_cfg_diag.rate_ret);
|
||||
}
|
||||
if (n >= len) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_GPS_SAT_DIAG
|
||||
k_mutex_lock(&gps_mutex, K_FOREVER);
|
||||
/* Age out constellations that have stopped reporting, so a stale count
|
||||
* is never presented as current. */
|
||||
int64_t now_ms = k_uptime_get();
|
||||
uint8_t shown[5];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
shown[i] = ((now_ms - sat_seen_ms[i]) > SAT_TALLY_STALE_MS) ? 0 : sat_count[i];
|
||||
}
|
||||
k_mutex_unlock(&gps_mutex);
|
||||
snprintf(buf + n, len - n, " sys=G%u/R%u/E%u/B%u/?%u",
|
||||
shown[0], shown[1], shown[2], shown[3], shown[4]);
|
||||
#else
|
||||
snprintf(buf + n, len - n, " (build without GPS_SAT_DIAG: no per-constellation proof)");
|
||||
#endif
|
||||
#else
|
||||
ARG_UNUSED(gps_diag_on);
|
||||
snprintf(buf, len, "no GNSS on this board");
|
||||
#endif
|
||||
}
|
||||
|
||||
void gps_get_state_info(struct gps_state_info *info)
|
||||
{
|
||||
memset(info, 0, sizeof(*info));
|
||||
@@ -1803,6 +2331,19 @@ void gps_get_state_info(struct gps_state_info *info)
|
||||
} else if (gps_current_state == GPS_STATE_ACQUIRING) {
|
||||
info->next_search_s = 0; /* Searching right now */
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_GPS_SAT_DIAG
|
||||
/* Same staleness rule as the diag report — never present a count for a
|
||||
* constellation that has stopped reporting. */
|
||||
k_mutex_lock(&gps_mutex, K_FOREVER);
|
||||
int64_t sat_now = k_uptime_get();
|
||||
uint8_t *dst[5] = { &info->sats_gps, &info->sats_glonass, &info->sats_galileo,
|
||||
&info->sats_beidou, &info->sats_other };
|
||||
for (int i = 0; i < 5; i++) {
|
||||
*dst[i] = ((sat_now - sat_seen_ms[i]) > SAT_TALLY_STALE_MS) ? 0 : sat_count[i];
|
||||
}
|
||||
k_mutex_unlock(&gps_mutex);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user