diff --git a/src/main/interface/settings.c b/src/main/interface/settings.c index 097fb6d380..26860c1041 100644 --- a/src/main/interface/settings.c +++ b/src/main/interface/settings.c @@ -997,6 +997,9 @@ const clivalue_t valueTable[] = { { "osd_vbat_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POSCFG_MAX }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_MAIN_BATT_VOLTAGE]) }, { "osd_rssi_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POSCFG_MAX }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_RSSI_VALUE]) }, +#ifdef USE_RX_LINK_QUALITY_INFO + { "osd_link_quality_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POSCFG_MAX }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_LINK_QUALITY]) }, +#endif { "osd_tim_1_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POSCFG_MAX }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_ITEM_TIMER_1]) }, { "osd_tim_2_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POSCFG_MAX }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_ITEM_TIMER_2]) }, { "osd_remaining_time_estimate_pos", VAR_UINT16 | MASTER_VALUE, .config.minmax = { 0, OSD_POSCFG_MAX }, PG_OSD_CONFIG, offsetof(osdConfig_t, item_pos[OSD_REMAINING_TIME_ESTIMATE]) }, @@ -1066,6 +1069,7 @@ const clivalue_t valueTable[] = { { "osd_stat_max_g_force", VAR_UINT32 | MASTER_VALUE | MODE_BITSET, .config.bitpos = OSD_STAT_MAX_G_FORCE, PG_OSD_CONFIG, offsetof(osdConfig_t, enabled_stats)}, { "osd_stat_max_esc_temp", VAR_UINT32 | MASTER_VALUE | MODE_BITSET, .config.bitpos = OSD_STAT_MAX_ESC_TEMP, PG_OSD_CONFIG, offsetof(osdConfig_t, enabled_stats)}, { "osd_stat_max_esc_rpm", VAR_UINT32 | MASTER_VALUE | MODE_BITSET, .config.bitpos = OSD_STAT_MAX_ESC_RPM, PG_OSD_CONFIG, offsetof(osdConfig_t, enabled_stats)}, + { "osd_stat_min_link_quality", VAR_UINT32 | MASTER_VALUE | MODE_BITSET, .config.bitpos = OSD_STAT_MIN_LINK_QUALITY,PG_OSD_CONFIG, offsetof(osdConfig_t, enabled_stats)}, #endif diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 341f4415ff..51ae8d2f4c 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -135,12 +135,13 @@ typedef struct statistic_s { int16_t max_speed; int16_t min_voltage; // /10 int16_t max_current; // /10 - int16_t min_rssi; + uint8_t min_rssi; int32_t max_altitude; int16_t max_distance; float max_g_force; int16_t max_esc_temp; int32_t max_esc_rpm; + uint8_t min_link_quality; } statistic_t; static statistic_t stats; @@ -217,6 +218,9 @@ static const uint8_t osdElementDisplayOrder[] = { #ifdef USE_ADC_INTERNAL OSD_CORE_TEMPERATURE, #endif +#ifdef USE_RX_LINK_QUALITY_INFO + OSD_LINK_QUALITY, +#endif }; PG_REGISTER_WITH_RESET_FN(osdConfig_t, osdConfig, PG_OSD_CONFIG, 3); @@ -503,13 +507,28 @@ static bool osdDrawSingleElement(uint8_t item) case OSD_RSSI_VALUE: { uint16_t osdRssi = getRssi() * 100 / 1024; // change range - if (osdRssi >= 100) + if (osdRssi >= 100) { osdRssi = 99; + } tfp_sprintf(buff, "%c%2d", SYM_RSSI, osdRssi); break; } +#ifdef USE_RX_LINK_QUALITY_INFO + case OSD_LINK_QUALITY: + { + // change range to 0-9 (two sig. fig. adds little extra value, also reduces screen estate) + uint8_t osdLinkQuality = rxGetLinkQuality() * 10 / LINK_QUALITY_MAX_VALUE; + if (osdLinkQuality >= 10) { + osdLinkQuality = 9; + } + + tfp_sprintf(buff, "%1d", osdLinkQuality); + break; + } +#endif + case OSD_MAIN_BATT_VOLTAGE: buff[0] = osdGetBatterySymbol(osdGetBatteryAverageCellVoltage()); tfp_sprintf(buff + 1, "%2d.%1d%c", getBatteryVoltage() / 10, getBatteryVoltage() % 10, SYM_VOLT); @@ -1332,13 +1351,14 @@ static void osdResetStats(void) stats.max_current = 0; stats.max_speed = 0; stats.min_voltage = 500; - stats.min_rssi = 99; + stats.min_rssi = 99; // percent stats.max_altitude = 0; stats.max_distance = 0; stats.armed_time = 0; stats.max_g_force = 0; stats.max_esc_temp = 0; stats.max_esc_rpm = 0; + stats.min_link_quality = 99; // percent } static void osdUpdateStats(void) @@ -1382,6 +1402,13 @@ static void osdUpdateStats(void) stats.max_g_force = osdGForce; } +#ifdef USE_RX_LINK_QUALITY_INFO + value = rxGetLinkQualityPercent(); + if (stats.min_link_quality > value) { + stats.min_link_quality = value; + } +#endif + #ifdef USE_GPS if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) { value = GPS_distanceToHome; @@ -1578,6 +1605,13 @@ static void osdShowStats(uint16_t endBatteryVoltage) } #endif +#ifdef USE_RX_LINK_QUALITY_INFO + if (osdStatGetState(OSD_STAT_MIN_LINK_QUALITY)) { + itoa(stats.min_link_quality, buff, 10); + strcat(buff, "%"); + osdDisplayStatisticLabel(top++, "MIN LINK", buff); + } +#endif } static void osdShowArmed(void) diff --git a/src/main/io/osd.h b/src/main/io/osd.h index d9c226d33e..ed0c9d472e 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -98,6 +98,7 @@ typedef enum { OSD_G_FORCE, OSD_LOG_STATUS, OSD_FLIP_ARROW, + OSD_LINK_QUALITY, OSD_ITEM_COUNT // MUST BE LAST } osd_items_e; @@ -128,6 +129,7 @@ typedef enum { OSD_STAT_MAX_G_FORCE, OSD_STAT_MAX_ESC_TEMP, OSD_STAT_MAX_ESC_RPM, + OSD_STAT_MIN_LINK_QUALITY, OSD_STAT_COUNT // MUST BE LAST } osd_stats_e; diff --git a/src/main/rx/rx.c b/src/main/rx/rx.c index d4c8e29ea0..5230fb95bb 100644 --- a/src/main/rx/rx.c +++ b/src/main/rx/rx.c @@ -72,6 +72,9 @@ const char rcChannelLetters[] = "AERT12345678abcdefgh"; static uint16_t rssi = 0; // range: [0;1023] static timeUs_t lastMspRssiUpdateUs = 0; +#ifdef USE_RX_LINK_QUALITY_INFO +static uint8_t linkQuality = 0; +#endif #define MSP_RSSI_TIMEOUT_US 1500000 // 1.5 sec @@ -344,6 +347,34 @@ void resumeRxPwmPpmSignal(void) #endif } +#ifdef USE_RX_LINK_QUALITY_INFO +#define LINK_QUALITY_SAMPLE_COUNT 16 + +static uint8_t updateLinkQualitySamples(uint8_t value) +{ + static uint8_t samples[LINK_QUALITY_SAMPLE_COUNT]; + static uint8_t sampleIndex = 0; + static unsigned sum = 0; + + sum += value - samples[sampleIndex]; + samples[sampleIndex] = value; + sampleIndex = (sampleIndex + 1) % LINK_QUALITY_SAMPLE_COUNT; + return sum / LINK_QUALITY_SAMPLE_COUNT; +} +#endif + +static void setLinkQuality(bool validFrame) +{ +#ifdef USE_RX_LINK_QUALITY_INFO + // calculate new sample mean + linkQuality = updateLinkQualitySamples(validFrame ? LINK_QUALITY_MAX_VALUE : 0); +#endif + + if (rssiSource == RSSI_SOURCE_FRAME_ERRORS) { + setRssi(validFrame ? RSSI_MAX_VALUE : 0, RSSI_SOURCE_FRAME_ERRORS); + } +} + bool rxUpdateCheck(timeUs_t currentTimeUs, timeDelta_t currentDeltaTime) { UNUSED(currentDeltaTime); @@ -378,13 +409,7 @@ bool rxUpdateCheck(timeUs_t currentTimeUs, timeDelta_t currentDeltaTime) needRxSignalBefore = currentTimeUs + needRxSignalMaxDelayUs; } - if (frameStatus & (RX_FRAME_FAILSAFE | RX_FRAME_DROPPED)) { - // No (0%) signal - setRssi(0, RSSI_SOURCE_FRAME_ERRORS); - } else { - // Valid (100%) signal - setRssi(RSSI_MAX_VALUE, RSSI_SOURCE_FRAME_ERRORS); - } + setLinkQuality(signalReceived); } if (frameStatus & RX_FRAME_PROCESSING_REQUIRED) { @@ -594,24 +619,26 @@ void setRssiDirect(uint16_t newRssi, rssiSource_e source) #define RSSI_SAMPLE_COUNT 16 +static uint16_t updateRssiSamples(uint16_t value) +{ + static uint16_t samples[RSSI_SAMPLE_COUNT]; + static uint8_t sampleIndex = 0; + static unsigned sum = 0; + + sum += value - samples[sampleIndex]; + samples[sampleIndex] = value; + sampleIndex = (sampleIndex + 1) % RSSI_SAMPLE_COUNT; + return sum / RSSI_SAMPLE_COUNT; +} + void setRssi(uint16_t rssiValue, rssiSource_e source) { if (source != rssiSource) { return; } - static uint16_t rssiSamples[RSSI_SAMPLE_COUNT]; - static uint8_t rssiSampleIndex = 0; - static unsigned sum = 0; - - sum = sum + rssiValue; - sum = sum - rssiSamples[rssiSampleIndex]; - rssiSamples[rssiSampleIndex] = rssiValue; - rssiSampleIndex = (rssiSampleIndex + 1) % RSSI_SAMPLE_COUNT; - - int16_t rssiMean = sum / RSSI_SAMPLE_COUNT; - - rssi = rssiMean; + // calculate new sample mean + rssi = updateRssiSamples(rssiValue); } void setRssiMsp(uint8_t newMspRssi) @@ -693,6 +720,18 @@ uint8_t getRssiPercent(void) return scaleRange(getRssi(), 0, RSSI_MAX_VALUE, 0, 100); } +#ifdef USE_RX_LINK_QUALITY_INFO +uint8_t rxGetLinkQuality(void) +{ + return linkQuality; +} + +uint8_t rxGetLinkQualityPercent(void) +{ + return scaleRange(rxGetLinkQuality(), 0, LINK_QUALITY_MAX_VALUE, 0, 100); +} +#endif + uint16_t rxGetRefreshRate(void) { return rxRuntimeConfig.rxRefreshRate; diff --git a/src/main/rx/rx.h b/src/main/rx/rx.h index 92b4a6108b..8a2c054b4b 100644 --- a/src/main/rx/rx.h +++ b/src/main/rx/rx.h @@ -168,6 +168,11 @@ uint16_t getRssi(void); uint8_t getRssiPercent(void); bool isRssiConfigured(void); +#define LINK_QUALITY_MAX_VALUE 255 + +uint8_t rxGetLinkQuality(void); +uint8_t rxGetLinkQualityPercent(void); + void resetAllRxChannelRangeConfigurations(rxChannelRangeConfig_t *rxChannelRangeConfig); void suspendRxPwmPpmSignal(void); diff --git a/src/main/target/common_post.h b/src/main/target/common_post.h index 861f6923bc..c9a9c8cba6 100644 --- a/src/main/target/common_post.h +++ b/src/main/target/common_post.h @@ -120,6 +120,10 @@ #undef USE_MSP_OVER_TELEMETRY #endif +#if !defined(USE_OSD) +#undef USE_RX_LINK_QUALITY_INFO +#endif + /* If either VTX_CONTROL or VTX_COMMON is undefined then remove common code and device drivers */ #if !defined(USE_VTX_COMMON) || !defined(USE_VTX_CONTROL) #undef USE_VTX_COMMON diff --git a/src/main/target/common_pre.h b/src/main/target/common_pre.h index 9f599536f7..a1a85df41d 100644 --- a/src/main/target/common_pre.h +++ b/src/main/target/common_pre.h @@ -229,4 +229,5 @@ #define USE_HOTT_TEXTMODE #define USE_LED_STRIP #define USE_VARIO +#define USE_RX_LINK_QUALITY_INFO #endif diff --git a/src/test/unit/osd_unittest.cc b/src/test/unit/osd_unittest.cc index e842af53e9..9b239ee06a 100644 --- a/src/test/unit/osd_unittest.cc +++ b/src/test/unit/osd_unittest.cc @@ -1046,6 +1046,8 @@ extern "C" { uint8_t getRssiPercent(void) { return scaleRange(rssi, 0, RSSI_MAX_VALUE, 0, 100); } + uint8_t rxGetLinkQuality(void) { return LINK_QUALITY_MAX_VALUE; } + uint16_t getCoreTemperatureCelsius(void) { return simulationCoreTemperature; } bool isFlipOverAfterCrashMode(void) {