Skip to content
2 changes: 1 addition & 1 deletion docs/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -1975,7 +1975,7 @@ Which SBAS mode to be used

### gps_ublox_nav_hz

Navigation update rate for UBLOX receivers. M9 modules limit satellite tracking to 16 satellites at 10Hz or higher, but use 32 satellites below 10Hz for better accuracy. M10 modules work well at 8Hz with 3 constellations. Some M10 devices with high-performance clock can do up to 25Hz with 4 constellations. 8Hz is a safe, accurate default for M8/M9/M10.
Navigation update rate for UBLOX receivers. M9 modules limit satellite tracking to 16 satellites at 10Hz or higher, but use 32 satellites below 10Hz for better accuracy. M10 modules work well at 8Hz with 3 constellations. Some M10 devices with high-performance clock can do up to 25Hz with 4 constellations. ZED-F9P modules reach 7Hz with 4 constellations on firmware HPG 1.51 (9Hz on HPG 1.32), ZED-X20P modules up to 25Hz. 8Hz is a safe, accurate default for M8/M9/M10.

| Default | Min | Max |
| --- | --- | --- |
Expand Down
2 changes: 1 addition & 1 deletion src/main/fc/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1847,7 +1847,7 @@ groups:
min: 5
max: 10
- name: gps_ublox_nav_hz
description: "Navigation update rate for UBLOX receivers. M9 modules limit satellite tracking to 16 satellites at 10Hz or higher, but use 32 satellites below 10Hz for better accuracy. M10 modules work well at 8Hz with 3 constellations. Some M10 devices with high-performance clock can do up to 25Hz with 4 constellations. 8Hz is a safe, accurate default for M8/M9/M10."
description: "Navigation update rate for UBLOX receivers. M9 modules limit satellite tracking to 16 satellites at 10Hz or higher, but use 32 satellites below 10Hz for better accuracy. M10 modules work well at 8Hz with 3 constellations. Some M10 devices with high-performance clock can do up to 25Hz with 4 constellations. ZED-F9P modules reach 7Hz with 4 constellations on firmware HPG 1.51 (9Hz on HPG 1.32), ZED-X20P modules up to 25Hz. 8Hz is a safe, accurate default for M8/M9/M10."
default_value: 8
field: ubloxNavHz
type: uint8_t
Expand Down
4 changes: 4 additions & 0 deletions src/main/io/gps.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,12 @@ const char *getGpsHwVersion(void)
return "UBLOX8";
case UBX_HW_VERSION_UBLOX9:
return "UBLOX9";
case UBX_HW_VERSION_UBLOX_F9:
return "UBLOXF9";
case UBX_HW_VERSION_UBLOX10:
return "UBLOX10";
case UBX_HW_VERSION_UBLOX20:
return "UBLOX20";
default:
return "Unknown";
}
Expand Down
149 changes: 67 additions & 82 deletions src/main/io/gps_ublox.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@


#include "common/axis.h"
#include "common/typeconversion.h"
#include "common/gps_conversion.h"
#include "common/maths.h"
#include "common/utils.h"
Expand Down Expand Up @@ -120,6 +119,7 @@ static struct {
int capMaxGnss;
uint8_t defaultGnss;
uint8_t enabledGnss;
bool monGnssUnsupported; // MON-GNSS came back in a version the parser does not read
} ubx_capabilities = { };

// Example packet sizes from UBlox u-center from a Glonass capable GPS receiver.
Expand Down Expand Up @@ -475,6 +475,16 @@ static void configureGNSS10(void)
ubloxSendSetCfgBytes(gnssConfigValues, 12);
}

static void configureGNSSEnables(void)
{
ubx_config_data8_payload_t gnssConfigValues[UBLOX_GNSS_ENABLE_KEYS_MAX];
const uint8_t count = ubloxGnssEnableKeys(gnssConfigValues, gpsState.gpsConfig->sbasMode != SBAS_NONE,
gpsState.gpsConfig->ubloxUseGalileo, gpsState.gpsConfig->ubloxUseBeidou, gpsState.gpsConfig->ubloxUseGlonass,
ubx_capabilities.supported);

ubloxSendSetCfgBytes(gnssConfigValues, count);
}

static void configureGNSS(void)
{
int blocksUsed = 0;
Expand Down Expand Up @@ -530,12 +540,6 @@ static void configureMSG(uint8_t msg_class, uint8_t id, uint8_t rate)
*/
static void configureRATE(uint16_t measRate)
{
if(ubloxVersionLT(24, 0)) {
measRate = MAX(50, measRate);
} else {
measRate = MAX(25, measRate);
}

if (ubloxVersionLTE(23, 1)) {
send_buffer.message.header.msg_class = CLASS_CFG;
send_buffer.message.header.msg_id = MSG_CFG_RATE;
Expand Down Expand Up @@ -564,6 +568,11 @@ static void configureRATE(uint16_t measRate)
}
}

static uint8_t ubloxEffectiveNavHz(void)
{
return ubloxNavHzFor(gpsState.hwVersion, gpsState.swVersionMajor, gpsState.swVersionMinor, gpsState.gpsConfig->ubloxNavHz);
}

/*
*/
static void configureSBAS(void)
Expand All @@ -579,53 +588,6 @@ static void configureSBAS(void)
sendConfigMessageUBLOX();
}

static void gpsDecodeProtocolVersion(const char *proto, size_t bufferLength)
{
if (bufferLength > 13 && (!strncmp(proto, "PROTVER=", 8) || !strncmp(proto, "PROTVER ", 8))) {
proto+=8;

float ver = fastA2F(proto);

gpsState.swVersionMajor = (uint8_t)ver;
gpsState.swVersionMinor = (uint8_t)((ver - gpsState.swVersionMajor) * 100.0f);
}
}

static uint8_t gpsDecodeHardwareVersion(const char * szBuf, unsigned nBufSize)
{
// ublox_5 hwVersion 00040005
if (strncmp(szBuf, "00040005", nBufSize) == 0) {
return UBX_HW_VERSION_UBLOX5;
}

// ublox_6 hwVersion 00040007
if (strncmp(szBuf, "00040007", nBufSize) == 0) {
return UBX_HW_VERSION_UBLOX6;
}

// ublox_7 hwVersion 00070000
if (strncmp(szBuf, "00070000", nBufSize) == 0) {
return UBX_HW_VERSION_UBLOX7;
}

// ublox_M8 hwVersion 00080000
if (strncmp(szBuf, "00080000", nBufSize) == 0) {
return UBX_HW_VERSION_UBLOX8;
}

// ublox_M9 hwVersion 00190000
if (strncmp(szBuf, "00190000", nBufSize) == 0) {
return UBX_HW_VERSION_UBLOX9;
}

// ublox_M10 hwVersion 000A0000
if (strncmp(szBuf, "000A0000", nBufSize) == 0) {
return UBX_HW_VERSION_UBLOX10;
}

return UBX_HW_VERSION_UNKNOWN;
}

static bool gpsParseFrameUBLOX(void)
{
switch (_msg_id) {
Expand Down Expand Up @@ -732,9 +694,23 @@ static bool gpsParseFrameUBLOX(void)
_new_speed = true;
break;
case MSG_VER:
if (_class == CLASS_MON) {
gpsState.hwVersion = gpsDecodeHardwareVersion(_buffer.ver.hwVersion, sizeof(_buffer.ver.hwVersion));
if (gpsState.hwVersion >= UBX_HW_VERSION_UBLOX8) {
if (_class == CLASS_MON && _payload_length >= sizeof(ubx_mon_ver)) {
gpsState.hwVersion = ubloxDecodeHardwareVersion(_buffer.ver.hwVersion, sizeof(_buffer.ver.hwVersion));
// Parsed before the gates below so receivers missing from the hardware ID table are not treated as legacy
const char *module = NULL;
for (unsigned j = sizeof(ubx_mon_ver); j + 30 <= _payload_length; j += 30) {
const char *extension = (const char *)(_buffer.bytes + j);
uint8_t major, minor;
if (ubloxParseProtocolVersion(extension, 30, &major, &minor)) {
gpsState.swVersionMajor = major;
gpsState.swVersionMinor = minor;
}
if (strncmp(extension, "MOD=", 4) == 0) {
module = extension;
}
}
gpsState.hwVersion = ubloxRefineHardwareVersion(gpsState.hwVersion, _buffer.ver.swVersion, sizeof(_buffer.ver.swVersion), module, 30);
if (ubloxCanConfigureGnss(gpsState.hwVersion, gpsState.swVersionMajor, gpsState.swVersionMinor)) {
if (_buffer.ver.swVersion[9] > '2' || true) {
// check extensions;
// after hw + sw vers; each is 30 bytes
Expand All @@ -759,12 +735,6 @@ static bool gpsParseFrameUBLOX(void)
}
}
}
for(int j = 40; j < _payload_length; j += 30) {
if (strnstr((const char *)(_buffer.bytes + j), "PROTVER", 30)) {
gpsDecodeProtocolVersion((const char *)(_buffer.bytes + j), 30);
break;
}
}
}
}
break;
Expand All @@ -776,6 +746,8 @@ static bool gpsParseFrameUBLOX(void)
ubx_capabilities.enabledGnss = _buffer.gnss.enabled;
ubx_capabilities.capMaxGnss = _buffer.gnss.maxConcurrent;
gpsState.lastCapaUpdMs = millis();
} else {
ubx_capabilities.monGnssUnsupported = true;
}
}
break;
Expand Down Expand Up @@ -1125,10 +1097,8 @@ STATIC_PROTOTHREAD(gpsConfigure)
}// end message config

ptWaitTimeout((_ack_state == UBX_ACK_GOT_ACK || _ack_state == UBX_ACK_GOT_NAK), GPS_SHORT_TIMEOUT);
if ((gpsState.hwVersion >= UBX_HW_VERSION_UBLOX7)) {
configureRATE(hz2rate(gpsState.gpsConfig->ubloxNavHz)); // default 10Hz
} else {
configureRATE(hz2rate(5)); // 5Hz
configureRATE(hz2rate(ubloxEffectiveNavHz()));
if (!ubloxCanConfigureNavRate(gpsState.hwVersion, gpsState.swVersionMajor, gpsState.swVersionMinor)) {
gpsConfigMutable()->ubloxNavHz = SETTING_GPS_UBLOX_NAV_HZ_DEFAULT;
}
ptWait(_ack_state == UBX_ACK_GOT_ACK || _ack_state == UBX_ACK_GOT_NAK);
Expand All @@ -1151,14 +1121,12 @@ STATIC_PROTOTHREAD(gpsConfigure)
ptWaitTimeout((_ack_state == UBX_ACK_GOT_ACK || _ack_state == UBX_ACK_GOT_NAK), GPS_CFG_CMD_TIMEOUT_MS);

// Configure GNSS for M8N and later
if (gpsState.hwVersion >= UBX_HW_VERSION_UBLOX8) { // TODO: This check can be remove in INAV 9.0.0
if (ubloxCanConfigureGnss(gpsState.hwVersion, gpsState.swVersionMajor, gpsState.swVersionMinor)) {
gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT);
bool use_VALSET = 0;
if (ubloxVersionGT(23,1)) {
use_VALSET = 1;
}

if ( use_VALSET && (gpsState.hwVersion >= UBX_HW_VERSION_UBLOX10) ) {
if (ubloxUseGnssEnableKeys(gpsState.hwVersion, gpsState.swVersionMajor, gpsState.swVersionMinor)) {
configureGNSSEnables();
} else if (ubloxUseM10GnssKeys(gpsState.hwVersion, gpsState.swVersionMajor, gpsState.swVersionMinor)) {
configureGNSS10();
} else {
configureGNSS();
Expand Down Expand Up @@ -1250,26 +1218,41 @@ STATIC_PROTOTHREAD(gpsProtocolStateThread)

// Attempt to detect GPS hw version
gpsState.hwVersion = UBX_HW_VERSION_UNKNOWN;
gpsState.swVersionMajor = 0;
gpsState.swVersionMinor = 0;
gpsState.autoConfigStep = 0;

// Configure GPS module if enabled
if (gpsState.gpsConfig->autoConfig) {
// Before MON-VER, whose extensions set the constellation bits
ubx_capabilities.supported = ubx_capabilities.enabledGnss = ubx_capabilities.defaultGnss = 0;
ubx_capabilities.monGnssUnsupported = false;
do {
// gps.c counts its timeout from this call, not from received frames, so the retries would be cut short
gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT);
pollVersion();
gpsState.autoConfigStep++;
ptWaitTimeout((gpsState.hwVersion != UBX_HW_VERSION_UNKNOWN), GPS_CFG_CMD_TIMEOUT_MS);
} while(gpsState.autoConfigStep < GPS_VERSION_RETRY_TIMES && gpsState.hwVersion == UBX_HW_VERSION_UNKNOWN);
ptWaitTimeout((gpsState.hwVersion != UBX_HW_VERSION_UNKNOWN || gpsState.swVersionMajor != 0), GPS_CFG_CMD_TIMEOUT_MS);
} while(gpsState.autoConfigStep < GPS_VERSION_RETRY_TIMES &&
gpsState.hwVersion == UBX_HW_VERSION_UNKNOWN && gpsState.swVersionMajor == 0);

// Without a version the receiver would be set up on the legacy path, so restart like a communication loss
if (gpsState.hwVersion == UBX_HW_VERSION_UNKNOWN && gpsState.swVersionMajor == 0) {
ptStop(0);
}
Comment on lines +1239 to +1242

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Gps never works if version detection fails 🐞 Bug ☼ Reliability

After three MON-VER polls with no recognised hardware ID and no PROTVER, gpsProtocolStateThread
now calls ptStop(0). Before, it went on to gpsConfigure on the legacy path. Once stopped, the
thread no longer runs gpsProcessNewSolutionData (the only caller that refreshes lastMessageMs),
so gpsThread times out and restarts the protocol. This repeats every cycle: a receiver that sends
navigation data but doesn't answer MON-VER, or answers with an unknown ID and no PROTVER extension,
never gets a fix. Before this change, such a receiver was configured with the legacy messages and
worked.
Agent Prompt
## Issue description
When no hardware or protocol version is detected after the MON-VER retries, `gpsProtocolStateThread` calls `ptStop(0)`. GPS then restarts over and over, and a receiver that never answers MON-VER (or has an unknown ID and no PROTVER) is never usable. The previous behaviour configured it on the legacy path.

## Fix Focus Areas
- src/main/io/gps_ublox.c[1239-1242]

## Recommended Fix
Don't stop on the first failed detection. Either keep the old fallback (continue to `ptSpawn(gpsConfigure)`; the periodic MON-VER poll in the main loop already retries detection), or restart only a limited number of times (e.g. track a counter across restarts) and then fall back to the legacy configuration instead of stopping forever.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


gpsState.autoConfigStep = 0;
ubx_capabilities.supported = ubx_capabilities.enabledGnss = ubx_capabilities.defaultGnss = 0;
// M7 and earlier will never get pass this step, so skip it (#9440).
// UBLOX documents that this is M8N and later
if (gpsState.hwVersion > UBX_HW_VERSION_UBLOX7) {
if (ubloxCanConfigureGnss(gpsState.hwVersion, gpsState.swVersionMajor, gpsState.swVersionMinor)) {
do {
// MON-VER was answered, so stay alive even when MON-GNSS (version 1 on the X20) is not parsed
gpsSetProtocolTimeout(GPS_SHORT_TIMEOUT);
pollGnssCapabilities();
gpsState.autoConfigStep++;
ptWaitTimeout((ubx_capabilities.capMaxGnss != 0), GPS_CFG_CMD_TIMEOUT_MS);
} while (gpsState.autoConfigStep < GPS_VERSION_RETRY_TIMES && ubx_capabilities.capMaxGnss == 0);
ptWaitTimeout((ubx_capabilities.capMaxGnss != 0 || ubx_capabilities.monGnssUnsupported), GPS_CFG_CMD_TIMEOUT_MS);
} while (gpsState.autoConfigStep < GPS_VERSION_RETRY_TIMES && ubx_capabilities.capMaxGnss == 0 &&
!ubx_capabilities.monGnssUnsupported);
}

// Configure GPS
Expand All @@ -1290,17 +1273,19 @@ STATIC_PROTOTHREAD(gpsProtocolStateThread)
if ((millis() - gpsState.lastCapaPoolMs) > GPS_CAPA_INTERVAL) {
gpsState.lastCapaPoolMs = millis();
gnssPolled=true;
if (gpsState.hwVersion == UBX_HW_VERSION_UNKNOWN)
if (gpsState.hwVersion == UBX_HW_VERSION_UNKNOWN && gpsState.swVersionMajor == 0)
{
pollVersion();
}

pollGnssCapabilities();
if (!ubx_capabilities.monGnssUnsupported) {
pollGnssCapabilities();
}
}
}

/* Periodically poll MON-RF (~1s) for HW > UBLOX8 if OSD widget requested. Do not change ACK state. */
if ((!gnssPolled) && gpsState.hwVersion > UBX_HW_VERSION_UBLOX8 && osdMonRfWidgetEnabled) {
if ((!gnssPolled) && UBX_HW_GENERATION(gpsState.hwVersion) > UBX_HW_GENERATION(UBX_HW_VERSION_UBLOX8) && osdMonRfWidgetEnabled) {
if ((millis() - lastMonRfMs) > 1000) {
lastMonRfMs = millis();
pollMonRf();
Expand Down
14 changes: 10 additions & 4 deletions src/main/io/gps_ublox.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,19 @@ STATIC_ASSERT(MAX_UBLOX_PAYLOAD_SIZE >= 256, ubx_size_too_small);

/*
* hwVersion encoding (fits in uint8_t):
* bits [7:6] series: 0b00=unknown, 0b01=u-blox Neo/M series
* bits [5:0] generation within series (e.g. 8=M8, 9=M9, 10=M10)
* bits [7:6] series: 0b00=unknown, 0b01=u-blox Neo/M series, 0b10=u-blox F9
* bits [5:0] generation within series (e.g. 8=M8, 9=M9/F9, 10=M10, 20=X20)
*
* This leaves 0b10 and 0b11 available for future series (e.g. u-blox F9,
* other manufacturers).
* The ZED-X20P reports 000B0000 (after the M10's 000A0000); the ZED-F9P reports the M9's 00190000,
* so it is told apart by the MON-VER module name.
*
* This leaves 0b11 available for future series (e.g. other manufacturers).
*/
#define UBX_HW_SERIES_MASK 0xC0
#define UBX_HW_GEN_MASK 0x3F
#define UBX_HW_SERIES_UBLOX_NM 0x40 // 0b01 << 6: u-blox Neo/M series
#define UBX_HW_SERIES_UBLOX_F9 0x80 // 0b10 << 6: u-blox F9, which reports the M9's hardware ID
#define UBX_HW_GENERATION(hw) ((hw) & UBX_HW_GEN_MASK)

#define UBX_HW_VERSION_UNKNOWN 0
#define UBX_HW_VERSION_UBLOX5 (UBX_HW_SERIES_UBLOX_NM | 5) // 0x45
Expand All @@ -81,6 +85,8 @@ STATIC_ASSERT(MAX_UBLOX_PAYLOAD_SIZE >= 256, ubx_size_too_small);
#define UBX_HW_VERSION_UBLOX8 (UBX_HW_SERIES_UBLOX_NM | 8) // 0x48
#define UBX_HW_VERSION_UBLOX9 (UBX_HW_SERIES_UBLOX_NM | 9) // 0x49
#define UBX_HW_VERSION_UBLOX10 (UBX_HW_SERIES_UBLOX_NM | 10) // 0x4A
#define UBX_HW_VERSION_UBLOX20 (UBX_HW_SERIES_UBLOX_NM | 20) // 0x54, X20
#define UBX_HW_VERSION_UBLOX_F9 (UBX_HW_SERIES_UBLOX_F9 | 9) // 0x89

#define UBLOX_CFG_MSGOUT_NAV_POSLLH_UART1 0x2091002a // U1
#define UBLOX_CFG_MSGOUT_NAV_SAT_UART1 0x20910016 // U1
Expand Down
Loading
Loading