From 6e27126dad2b279818ca23028bcc5a5c113c8b7f Mon Sep 17 00:00:00 2001 From: TipoMan Date: Sat, 28 Feb 2026 01:41:43 +0200 Subject: [PATCH 1/2] OSD: Add extra GPS satellite statistics widget --- src/main/fc/cli.c | 3 ++ src/main/io/gps_ublox.c | 61 ++++++++++++++++++++++++++++++++++++++ src/main/io/gps_ublox.h | 15 +++++++++- src/main/io/osd.c | 65 +++++++++++++++++++++++++++++++++++++++-- src/main/io/osd.h | 1 + 5 files changed, 142 insertions(+), 3 deletions(-) diff --git a/src/main/fc/cli.c b/src/main/fc/cli.c index 6c60f08c6ed..2f1a464e9cc 100644 --- a/src/main/fc/cli.c +++ b/src/main/fc/cli.c @@ -4800,6 +4800,9 @@ static void cliUbloxPrintSatelites(char *arg) } cliPrintLinefeed(); } + // Print MON-RF noisePerMS if available + cliPrintLinef("MON-RF noisePerMS: %u", gpsGetMonRfNoisePerMs()); + cliPrintLinef("MON-RF CW Suppression: %u", gpsGetMonRfCWSuppression()); } #endif diff --git a/src/main/io/gps_ublox.c b/src/main/io/gps_ublox.c index f0899aa69d5..536b4e56afe 100755 --- a/src/main/io/gps_ublox.c +++ b/src/main/io/gps_ublox.c @@ -87,6 +87,10 @@ static const char * baudInitDataNMEA[GPS_BAUDRATE_COUNT] = { static ubx_nav_sig_info satelites[UBLOX_MAX_SIGNALS] = {}; +// MON-RF noise value (noisePerMS) reported by UBX-MON-RF at payload offset 0x16 +static uint8_t monRfNoisePerMs = 0; +static uint8_t monCWSuppresion = 0; + // Packet checksum accumulators static uint8_t _ck_a; static uint8_t _ck_b; @@ -152,6 +156,14 @@ static union { uint8_t bytes[UBLOX_BUFFER_SIZE]; } _buffer; +// OSD controlled flag: when true, OSD requests periodic MON-RF polling +static volatile bool osdMonRfWidgetEnabled = false; + +void gpsSetOsdMonRfWidgetEnabled(bool enabled) +{ + osdMonRfWidgetEnabled = enabled; +} + bool gpsUbloxHasGalileo(void) { return (ubx_capabilities.supported & UBX_MON_GNSS_GALILEO_MASK); @@ -261,6 +273,28 @@ static void pollGnssCapabilities(void) sendConfigMessageUBLOX(); } +// Poll UBX-MON-RF (no ACK expected) - used periodically for RF status on newer modules +static void pollMonRf(void) +{ + uint8_t pkt[8]; + uint8_t ck_a = 0, ck_b = 0; + + pkt[0] = PREAMBLE1; + pkt[1] = PREAMBLE2; + pkt[2] = CLASS_MON; + pkt[3] = MSG_MON_RF; + pkt[4] = 0; // length LSB + pkt[5] = 0; // length MSB + + // compute checksum over CLASS, ID and length (4 bytes) + ublox_update_checksum(&pkt[2], 4, &ck_a, &ck_b); + pkt[6] = ck_a; + pkt[7] = ck_b; + + // send without forcing ACK state (so we don't disturb config ACK handling) + serialWriteBuf(gpsState.gpsPort, pkt, sizeof(pkt)); +} + static const uint8_t default_payload[] = { 0xFF, 0xFF, 0x03, 0x03, 0x00, // CFG-NAV5 - Set engine settings (original MWII code) @@ -732,6 +766,15 @@ static bool gpsParseFrameUBLOX(void) } } break; + case MSG_MON_RF: + if (_class == CLASS_MON) { + // MON-RF payload contains various RF stats; noisePerMS is at offset 0x16 (22) + if (_payload_length > 0x10) + monRfNoisePerMs = _buffer.bytes[0x10]; + if (_payload_length > 0x14) + monCWSuppresion = _buffer.bytes[0x14]; + } + break; case MSG_NAV_SAT: if (_class == CLASS_NAV) { static int satInfoCount = 0; @@ -1220,10 +1263,19 @@ STATIC_PROTOTHREAD(gpsProtocolStateThread) gpsSetProtocolTimeout(gpsState.baseTimeoutMs); // GPS is ready - execute the gpsProcessNewSolutionData() based on gpsProtocolReceiverThread semaphore + static uint32_t lastMonRfMs = 0; while (1) { ptSemaphoreWait(semNewDataReady); gpsProcessNewSolutionData(false); + /* Periodically poll MON-RF (~1s) for HW > UBLOX8 if OSD widget requested. Do not change ACK state. */ + if (gpsState.hwVersion > UBX_HW_VERSION_UBLOX8 && osdMonRfWidgetEnabled) { + if ((millis() - lastMonRfMs) > 1000) { + lastMonRfMs = millis(); + pollMonRf(); + } + } + if (gpsState.gpsConfig->autoConfig) { if ((millis() - gpsState.lastCapaPoolMs) > GPS_CAPA_INTERVAL) { gpsState.lastCapaPoolMs = millis(); @@ -1313,4 +1365,13 @@ bool ubloxVersionE(uint8_t mj, uint8_t mn) return gpsState.swVersionMajor == mj && gpsState.swVersionMinor == mn; } +uint8_t gpsGetMonRfNoisePerMs(void) +{ + return monRfNoisePerMs; +} +uint8_t gpsGetMonRfCWSuppression(void) +{ + return monCWSuppresion; +} + #endif diff --git a/src/main/io/gps_ublox.h b/src/main/io/gps_ublox.h index ab0ab930275..ff334c70869 100644 --- a/src/main/io/gps_ublox.h +++ b/src/main/io/gps_ublox.h @@ -506,7 +506,8 @@ typedef enum { MSG_CFG_SBAS = 0x16, MSG_CFG_GNSS = 0x3e, MSG_MON_GNSS = 0x28, - MSG_NAV_SIG = 0x43 + MSG_NAV_SIG = 0x43, + MSG_MON_RF = 0x38 } ubx_protocol_bytes_t; typedef enum { @@ -551,3 +552,15 @@ bool ubloxVersionE(uint8_t mj, uint8_t mn); #ifdef __cplusplus } #endif + +// MON-RF access Noise level +uint8_t gpsGetMonRfNoisePerMs(void); +// Called by OSD to enable/disable periodic MON-RF polling when widget is used +void gpsSetOsdMonRfWidgetEnabled(bool enabled); + +// MON-RF access CW Suppression +uint8_t gpsGetMonRfCWSuppression(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 6ad55632c17..06e3e8f8b6f 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -67,6 +67,7 @@ #include "io/adsb.h" #include "io/flashfs.h" #include "io/gps.h" +#include "io/gps_ublox.h" #include "io/osd.h" #include "io/osd_common.h" #include "io/osd_hud.h" @@ -2258,6 +2259,65 @@ static bool osdDrawSingleElement(uint8_t item) osdFormatCentiNumber(&buff[2], centiHDOP, 0, 1, 0, digits, false); break; } + + case OSD_GPS_EXTRA_STATS: + { + gpsSetOsdMonRfWidgetEnabled(true); + + // Collect satellite stats grouped by GNSS ID: 0,2,3,6 + uint8_t stats[4][2] = { {0,0}, {0,0}, {0,0}, {0,0} }; // [group][0]=total, [group][1]=good + for (int i = 0; i < UBLOX_MAX_SIGNALS; ++i) { + const ubx_nav_sig_info *sat = gpsGetUbloxSatelite(i); + if (sat == NULL) continue; + int g = -1; + switch (sat->gnssId) { + case 0: g = 0; break; // GPS + case 2: g = 1; break; // GALILEO + case 3: g = 2; break; // BEIDOU + case 6: g = 3; break; // GLONASS + default: g = -1; break; + } + if (g < 0) continue; + + if (sat->quality > UBLOX_SIG_QUALITY_SEARCHING && stats[g][0] < 255) { // Sat is visible + stats[g][0]++; + } + + if (sat->quality >= UBLOX_SIG_QUALITY_CODE_LOCK_TIME_SYNC && stats[g][1] < 255) { // Sat is good enough for navigation + stats[g][1]++; + } + } + + // Write directly to display using displayWriteChar so large symbol indices are handled correctly. + int x = elemPosX; + for (int g = 0; g < 4; ++g) { + uint8_t total = stats[g][0]; + uint8_t good = stats[g][1]; + + // Hex nibble for total (cap at F) + char hexc; + if (total > 15) hexc = 'F'; + else if (total < 10) hexc = '0' + total; + else hexc = 'A' + (total - 10); + displayWriteChar(osdDisplayPort, x++, elemPosY, (uint8_t)hexc); + + // HUD signal icon based on number of good satellites (cap at 4) + uint8_t goodClamped = good > 4 ? 4 : good; + uint16_t sym = SYM_HUD_SIGNAL_0 + goodClamped; + displayWriteChar(osdDisplayPort, x++, elemPosY, sym); + } + + // always show RF noise after the stats + { + uint8_t noise = gpsGetMonRfNoisePerMs(); + char noiseBuff[4]; + noiseBuff[0] = SYM_SNR; + tfp_sprintf(&noiseBuff[1], (noise > 99) ? "%02X" : "%02u", noise); + displayWrite(osdDisplayPort, x, elemPosY, noiseBuff); + } + + return true; // already drawn + } #ifdef USE_ADSB case OSD_ADSB_WARNING: { @@ -4195,7 +4255,7 @@ uint8_t osdIncElementIndex(uint8_t elementIndex) if (!feature(FEATURE_GPS)) { if (elementIndex == OSD_GPS_HDOP || elementIndex == OSD_TRIP_DIST || elementIndex == OSD_3D_SPEED || elementIndex == OSD_MISSION || - elementIndex == OSD_AZIMUTH || elementIndex == OSD_BATTERY_REMAINING_CAPACITY || elementIndex == OSD_EFFICIENCY_MAH_PER_KM) { + elementIndex == OSD_AZIMUTH || elementIndex == OSD_BATTERY_REMAINING_CAPACITY || elementIndex == OSD_EFFICIENCY_MAH_PER_KM || elementIndex == OSD_GPS_EXTRA_STATS ) { elementIndex++; } if (elementIndex == OSD_HEADING_GRAPH && !sensors(SENSOR_MAG)) { @@ -4454,7 +4514,8 @@ void pgResetFn_osdLayoutsConfig(osdLayoutsConfig_t *osdLayoutsConfig) osdLayoutsConfig->item_pos[0][OSD_MISSION] = OSD_POS(0, 10); osdLayoutsConfig->item_pos[0][OSD_GPS_SATS] = OSD_POS(0, 11) | OSD_VISIBLE_FLAG; - osdLayoutsConfig->item_pos[0][OSD_GPS_HDOP] = OSD_POS(0, 10); + osdLayoutsConfig->item_pos[0][OSD_GPS_HDOP] = OSD_POS(0, 10); + osdLayoutsConfig->item_pos[0][OSD_GPS_EXTRA_STATS] = OSD_POS(3, 11); osdLayoutsConfig->item_pos[0][OSD_GPS_LAT] = OSD_POS(0, 12); // Put this on top of the latitude, since it's very unlikely diff --git a/src/main/io/osd.h b/src/main/io/osd.h index bbaa68f862d..ff2fb20032f 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -340,6 +340,7 @@ typedef enum { OSD_NAV_FW_ALT_CONTROL_RESPONSE, OSD_NAV_MIN_GROUND_SPEED, OSD_THROTTLE_GAUGE, + OSD_GPS_EXTRA_STATS, OSD_ITEM_COUNT // MUST BE LAST } osd_items_e; From 8a83048341160fae3b11b6b7113101b01a4c433c Mon Sep 17 00:00:00 2001 From: TipoMan Date: Tue, 3 Mar 2026 01:19:32 +0200 Subject: [PATCH 2/2] Fix minor error in PR --- src/main/io/gps_ublox.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/io/gps_ublox.h b/src/main/io/gps_ublox.h index ff334c70869..40c3f7101b4 100644 --- a/src/main/io/gps_ublox.h +++ b/src/main/io/gps_ublox.h @@ -549,10 +549,6 @@ bool ubloxVersionGT(uint8_t mj, uint8_t mn); bool ubloxVersionGTE(uint8_t mj, uint8_t mn); bool ubloxVersionE(uint8_t mj, uint8_t mn); -#ifdef __cplusplus -} -#endif - // MON-RF access Noise level uint8_t gpsGetMonRfNoisePerMs(void); // Called by OSD to enable/disable periodic MON-RF polling when widget is used