diff --git a/src/main/drivers/max7456_symbols.h b/src/main/drivers/max7456_symbols.h index a80bb6b344..88ea3a2dcd 100644 --- a/src/main/drivers/max7456_symbols.h +++ b/src/main/drivers/max7456_symbols.h @@ -29,6 +29,8 @@ // Satellite Graphics #define SYM_SAT_L 0x1E #define SYM_SAT_R 0x1F +#define SYM_HDP_L 0xBD +#define SYM_HDP_R 0xBE //#define SYM_SAT 0x0F // Not used // Degrees Icon for HEADING/DIRECTION HOME @@ -104,6 +106,7 @@ #define SYM_GMISSION1 0xB6 #define SYM_GLAND 0xB7 #define SYM_GLAND1 0xB8 +#define SYM_HOME_DIST 0xA0 // Gimbal active Mode #define SYM_GIMBAL 0x16 @@ -202,6 +205,7 @@ #define SYM_FLY_M 0x9C #define SYM_ON_H 0x70 #define SYM_FLY_H 0x71 +#define SYM_CLOCK 0xBC // Throttle Position (%) #define SYM_THR 0x04 @@ -215,6 +219,8 @@ //Misc #define SYM_COLON 0x2D +#define SYM_ZERO_HALF_TRAILING_DOT 0xC0 +#define SYM_ZERO_HALF_LEADING_DOT 0xD0 //sport #define SYM_MIN 0xB3 diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 9cc9ab6c62..1c7eba41b1 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -56,16 +56,6 @@ #include "drivers/sdcard.h" #include "drivers/time.h" -#include "io/asyncfatfs/asyncfatfs.h" -#include "io/beeper.h" -#include "io/flashfs.h" -#include "io/gps.h" -#include "io/osd.h" -#include "io/vtx_control.h" -#include "io/vtx_rtc6705.h" -#include "io/vtx_string.h" -#include "io/vtx.h" - #include "fc/config.h" #include "fc/rc_adjustments.h" #include "fc/rc_controls.h" @@ -75,6 +65,14 @@ #include "flight/imu.h" #include "flight/pid.h" +#include "io/asyncfatfs/asyncfatfs.h" +#include "io/beeper.h" +#include "io/flashfs.h" +#include "io/gps.h" +#include "io/osd.h" +#include "io/vtx_string.h" +#include "io/vtx.h" + #include "pg/pg.h" #include "pg/pg_ids.h" @@ -309,8 +307,36 @@ STATIC_UNIT_TESTED void osdFormatTimer(char *buff, bool showSymbol, int timerInd osdFormatTime(buff, OSD_TIMER_PRECISION(timer), osdGetTimerValue(src)); } +#ifdef USE_GPS +static void osdFormatCoordinate(char *buff, char sym, int32_t val) +{ + // latitude maximum integer width is 3 (-90). + // longitude maximum integer width is 4 (-180). + // We show 7 decimals, so we need to use 11 characters because + // we are embedding the decimal separator between two digits + // eg: s-1801234567z s=symbol, z=zero terminator, decimal separator embedded between 0 and 1 + + static const int decimalPlaces = 7; + static const int coordinateMaxLength = 11;//5 + decimalPlaces; // 4 for the integer part of the number + 1 for the symbol + + buff[0] = sym; + const int32_t integerPart = val / GPS_DEGREES_DIVIDER; + const int32_t decimalPart = labs(val % GPS_DEGREES_DIVIDER); + const int written = tfp_sprintf(buff + 1, "%d", integerPart); + tfp_sprintf(buff + 1 + written, "%07d", decimalPart); + // embed the decimal separator + buff[1+written-1] += SYM_ZERO_HALF_TRAILING_DOT - '0'; + buff[1+written] += SYM_ZERO_HALF_LEADING_DOT - '0'; + // pad with blanks to coordinateMaxLength + for (int pos = 1 + decimalPlaces + written; pos < coordinateMaxLength; ++pos) { + buff[pos] = SYM_BLANK; + } + buff[coordinateMaxLength] = '\0'; +} +#endif // USE_GPS + #ifdef USE_RTC_TIME -static bool printRtcDateTime(char *buffer) +static bool osdFormatRtcDateTime(char *buffer) { dateTime_t dateTime; if (!rtcGetDateTime(&dateTime)) { @@ -325,10 +351,20 @@ static bool printRtcDateTime(char *buffer) } #endif -static void osdDrawSingleElement(uint8_t item) +static void osdFormatMessage(char *buff, size_t size, const char *message) +{ + memset(buff, SYM_BLANK, size); + if (message) { + memcpy(buff, message, strlen(message)); + } + // Ensure buff is zero terminated + buff[size - 1] = '\0'; +} + +static bool osdDrawSingleElement(uint8_t item) { if (!VISIBLE(osdConfig()->item_pos[item]) || BLINK(item)) { - return; + return false; } uint8_t elemPosX = OSD_X(osdConfig()->item_pos[item]); @@ -343,13 +379,13 @@ static void osdDrawSingleElement(uint8_t item) if (osdRssi >= 100) osdRssi = 99; - tfp_sprintf(buff, "%c%d", SYM_RSSI, osdRssi); + tfp_sprintf(buff, "%c%2d", SYM_RSSI, osdRssi); break; } case OSD_MAIN_BATT_VOLTAGE: buff[0] = osdGetBatterySymbol(osdGetBatteryAverageCellVoltage()); - tfp_sprintf(buff + 1, "%d.%1d%c", getBatteryVoltage() / 10, getBatteryVoltage() % 10, SYM_VOLT); + tfp_sprintf(buff + 1, "%2d.%1d%c", getBatteryVoltage() / 10, getBatteryVoltage() % 10, SYM_VOLT); break; case OSD_CURRENT_DRAW: @@ -365,7 +401,7 @@ static void osdDrawSingleElement(uint8_t item) #ifdef USE_GPS case OSD_GPS_SATS: - tfp_sprintf(buff, "%c%c%d", SYM_SAT_L, SYM_SAT_R, gpsSol.numSat); + tfp_sprintf(buff, "%c%c%2d", SYM_SAT_L, SYM_SAT_R, gpsSol.numSat); break; case OSD_GPS_SPEED: @@ -374,26 +410,12 @@ static void osdDrawSingleElement(uint8_t item) break; case OSD_GPS_LAT: + osdFormatCoordinate(buff, SYM_LAT, gpsSol.llh.lat); + break; + case OSD_GPS_LON: - { - int32_t val; - if (item == OSD_GPS_LAT) { - buff[0] = SYM_ARROW_EAST; - val = gpsSol.llh.lat; - } else { - buff[0] = SYM_ARROW_SOUTH; - val = gpsSol.llh.lon; - } - - char wholeDegreeString[5]; - tfp_sprintf(wholeDegreeString, "%d", val / GPS_DEGREES_DIVIDER); - - char wholeUnshifted[12]; - tfp_sprintf(wholeUnshifted, "%d", val); - - tfp_sprintf(buff + 1, "%s.%s", wholeDegreeString, wholeUnshifted + strlen(wholeDegreeString)); - break; - } + osdFormatCoordinate(buff, SYM_LON, gpsSol.llh.lon); + break; case OSD_HOME_DIR: if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) { @@ -416,46 +438,37 @@ static void osdDrawSingleElement(uint8_t item) case OSD_HOME_DIST: if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) { - int32_t distance = osdGetMetersToSelectedUnit(GPS_distanceToHome); + const int32_t distance = osdGetMetersToSelectedUnit(GPS_distanceToHome); tfp_sprintf(buff, "%d%c", distance, osdGetMetersToSelectedUnitSymbol()); } else { // We use this symbol when we don't have a FIX buff[0] = SYM_COLON; - buff[1] = 0; + // overwrite any previous distance with blanks + memset(buff + 1, SYM_BLANK, 6); + buff[7] = '\0'; } break; #endif // GPS case OSD_COMPASS_BAR: - { - int16_t h = DECIDEGREES_TO_DEGREES(attitude.values.yaw); - - h = osdGetHeadingIntoDiscreteDirections(h, 16); - - memcpy(buff, compassBar + h, 9); - buff[9]=0; - break; - } + memcpy(buff, compassBar + osdGetHeadingIntoDiscreteDirections(DECIDEGREES_TO_DEGREES(attitude.values.yaw), 16), 9); + buff[9] = 0; + break; case OSD_ALTITUDE: - { - osdFormatAltitudeString(buff, getEstimatedAltitude(), true); - break; - } + osdFormatAltitudeString(buff, getEstimatedAltitude(), true); + break; case OSD_ITEM_TIMER_1: case OSD_ITEM_TIMER_2: - { - const int timer = item - OSD_ITEM_TIMER_1; - osdFormatTimer(buff, true, timer); - break; - } + osdFormatTimer(buff, true, item - OSD_ITEM_TIMER_1); + break; case OSD_REMAINING_TIME_ESTIMATE: { const int mAhDrawn = getMAhDrawn(); - int remaining_time = (int)((osdConfig()->cap_alarm - mAhDrawn) * ((float)flyTime) / mAhDrawn); + const int remaining_time = (int)((osdConfig()->cap_alarm - mAhDrawn) * ((float)flyTime) / mAhDrawn); if (mAhDrawn < 0.1 * osdConfig()->cap_alarm) { tfp_sprintf(buff, "--:--"); @@ -472,7 +485,7 @@ static void osdDrawSingleElement(uint8_t item) char *p = "ACRO"; if (isAirmodeActive()) { - p = "AIR"; + p = "AIR "; } if (FLIGHT_MODE(FAILSAFE_MODE)) { @@ -480,11 +493,11 @@ static void osdDrawSingleElement(uint8_t item) } else if (FLIGHT_MODE(ANGLE_MODE)) { p = "STAB"; } else if (FLIGHT_MODE(HORIZON_MODE)) { - p = "HOR"; + p = "HOR "; } displayWrite(osdDisplayPort, elemPosX, elemPosY, p); - return; + return true; } case OSD_CRAFT_NAME: @@ -494,16 +507,17 @@ static void osdDrawSingleElement(uint8_t item) for (unsigned int i = 0; i < MAX_NAME_LENGTH; i++) { buff[i] = toupper((unsigned char)pilotConfig()->name[i]); if (pilotConfig()->name[i] == 0) { - break; + buff[i] = SYM_BLANK; } } + buff[MAX_NAME_LENGTH] = '\0'; } break; case OSD_THROTTLE_POS: buff[0] = SYM_THR; buff[1] = SYM_THR1; - tfp_sprintf(buff + 2, "%d", (constrain(rcData[THROTTLE], PWM_RANGE_MIN, PWM_RANGE_MAX) - PWM_RANGE_MIN) * 100 / (PWM_RANGE_MAX - PWM_RANGE_MIN)); + tfp_sprintf(buff + 2, "%3d", (constrain(rcData[THROTTLE], PWM_RANGE_MIN, PWM_RANGE_MAX) - PWM_RANGE_MIN) * 100 / (PWM_RANGE_MAX - PWM_RANGE_MIN)); break; #if defined(USE_VTX_COMMON) @@ -515,7 +529,7 @@ static void osdDrawSingleElement(uint8_t item) if (vtxSettingsConfig()->lowPowerDisarm) { vtxCommonGetPowerIndex(&vtxPower); } - tfp_sprintf(buff, "%c:%s:%d", vtxBandLetter, vtxChannelName, vtxPower); + tfp_sprintf(buff, "%c:%s:%2d", vtxBandLetter, vtxChannelName, vtxPower); break; } #endif @@ -536,18 +550,15 @@ static void osdDrawSingleElement(uint8_t item) { elemPosX = 14; elemPosY = 6 - 4; // Top center of the AH area - - // Get pitch and roll limits in tenths of degrees - const int maxPitch = osdConfig()->ahMaxPitch * 10; - const int maxRoll = osdConfig()->ahMaxRoll * 10; - - const int rollAngle = constrain(attitude.values.roll, -maxRoll, maxRoll); - int pitchAngle = constrain(attitude.values.pitch, -maxPitch, maxPitch); - if (displayScreenSize(osdDisplayPort) == VIDEO_BUFFER_CHARS_PAL) { ++elemPosY; } + // Get pitch and roll limits in tenths of degrees + const int maxPitch = osdConfig()->ahMaxPitch * 10; + const int maxRoll = osdConfig()->ahMaxRoll * 10; + const int rollAngle = constrain(attitude.values.roll, -maxRoll, maxRoll); + int pitchAngle = constrain(attitude.values.pitch, -maxPitch, maxPitch); // Convert pitchAngle to y compensation value // (maxPitch / 25) divisor matches previous settings of fixed divisor of 8 and fixed max AHI pitch angle of 20.0 degrees pitchAngle = ((pitchAngle * 25) / maxPitch) - 41; // 41 = 4 * AH_SYMBOL_COUNT + 5 @@ -561,14 +572,13 @@ static void osdDrawSingleElement(uint8_t item) osdDrawSingleElement(OSD_HORIZON_SIDEBARS); - return; + return true; } case OSD_HORIZON_SIDEBARS: { elemPosX = 14; elemPosY = 6; - if (displayScreenSize(osdDisplayPort) == VIDEO_BUFFER_CHARS_PAL) { ++elemPosY; } @@ -585,7 +595,7 @@ static void osdDrawSingleElement(uint8_t item) displayWriteChar(osdDisplayPort, elemPosX - hudwidth + 1, elemPosY, SYM_AH_LEFT); displayWriteChar(osdDisplayPort, elemPosX + hudwidth - 1, elemPosY, SYM_AH_RIGHT); - return; + return true; } case OSD_ROLL_PIDS: @@ -615,7 +625,7 @@ static void osdDrawSingleElement(uint8_t item) const batteryState_e batteryState = getBatteryState(); if (enabledWarnings & OSD_WARNING_BATTERY_CRITICAL && batteryState == BATTERY_CRITICAL) { - tfp_sprintf(buff, " LAND NOW"); + osdFormatMessage(buff, sizeof(buff), " LAND NOW"); break; } @@ -623,7 +633,7 @@ static void osdDrawSingleElement(uint8_t item) if ((enabledWarnings & OSD_WARNING_CRASH_FLIP) && (isModeActivationConditionPresent(BOXFLIPOVERAFTERCRASH)) && IS_RC_MODE_ACTIVE(BOXFLIPOVERAFTERCRASH)) { - tfp_sprintf(buff, "CRASH FLIP"); + osdFormatMessage(buff, sizeof(buff), "CRASH FLIP"); break; } @@ -632,7 +642,7 @@ static void osdDrawSingleElement(uint8_t item) const armingDisableFlags_e flags = getArmingDisableFlags(); for (int i = 0; i < NUM_ARMING_DISABLE_FLAGS; i++) { if (flags & (1 << i)) { - tfp_sprintf(buff, "%s", armingDisableFlagNames[i]); + osdFormatMessage(buff, sizeof(buff), armingDisableFlagNames[i]); break; } } @@ -640,24 +650,24 @@ static void osdDrawSingleElement(uint8_t item) } if (enabledWarnings & OSD_WARNING_BATTERY_WARNING && batteryState == BATTERY_WARNING) { - tfp_sprintf(buff, "LOW BATTERY"); + osdFormatMessage(buff, sizeof(buff), "LOW BATTERY"); break; } // Show warning if battery is not fresh if (enabledWarnings & OSD_WARNING_BATTERY_NOT_FULL && !ARMING_FLAG(WAS_EVER_ARMED) && (getBatteryState() == BATTERY_OK) && getBatteryAverageCellVoltage() < batteryConfig()->vbatfullcellvoltage) { - tfp_sprintf(buff, "BATT NOT FULL"); + osdFormatMessage(buff, sizeof(buff), "BATT NOT FULL"); break; } // Visual beeper if (enabledWarnings & OSD_WARNING_VISUAL_BEEPER && showVisualBeeper) { - tfp_sprintf(buff, " * * * *"); + osdFormatMessage(buff, sizeof(buff), " * * * *"); break; } - return; + return true; } case OSD_AVG_CELL_VOLTAGE: @@ -682,42 +692,35 @@ static void osdDrawSingleElement(uint8_t item) case OSD_MAIN_BATT_USAGE: { - //Set length of indicator bar - #define MAIN_BATT_USAGE_STEPS 11 // Use an odd number so the bar can be centralised. + // Set length of indicator bar + #define MAIN_BATT_USAGE_STEPS 11 // Use an odd number so the bar can be centered. - //Calculate constrained value + // Calculate constrained value const float value = constrain(batteryConfig()->batteryCapacity - getMAhDrawn(), 0, batteryConfig()->batteryCapacity); - //Calculate mAh used progress + // Calculate mAh used progress const uint8_t mAhUsedProgress = ceil((value / (batteryConfig()->batteryCapacity / MAIN_BATT_USAGE_STEPS))); - //Create empty battery indicator bar + // Create empty battery indicator bar buff[0] = SYM_PB_START; - for (uint8_t i = 1; i <= MAIN_BATT_USAGE_STEPS; i++) { - if (i <= mAhUsedProgress) { - buff[i] = SYM_PB_FULL; - } else { - buff[i] = SYM_PB_EMPTY; - } + for (int i = 1; i <= MAIN_BATT_USAGE_STEPS; i++) { + buff[i] = i <= mAhUsedProgress ? SYM_PB_FULL : SYM_PB_EMPTY; } - buff[MAIN_BATT_USAGE_STEPS+1] = SYM_PB_CLOSE; - + buff[MAIN_BATT_USAGE_STEPS + 1] = SYM_PB_CLOSE; if (mAhUsedProgress > 0 && mAhUsedProgress < MAIN_BATT_USAGE_STEPS) { - buff[1+mAhUsedProgress] = SYM_PB_END; + buff[1 + mAhUsedProgress] = SYM_PB_END; } - - buff[MAIN_BATT_USAGE_STEPS+2] = 0; - + buff[MAIN_BATT_USAGE_STEPS+2] = '\0'; break; } case OSD_DISARMED: if (!ARMING_FLAG(ARMED)) { tfp_sprintf(buff, "DISARMED"); - break; } else { - return; + tfp_sprintf(buff, " "); } + break; case OSD_NUMERICAL_HEADING: { @@ -733,19 +736,20 @@ static void osdDrawSingleElement(uint8_t item) tfp_sprintf(buff, "%c%01d.%01d", directionSymbol, abs(verticalSpeed / 100), abs((verticalSpeed % 100) / 10)); break; } + #ifdef USE_ESC_SENSOR case OSD_ESC_TMP: - tfp_sprintf(buff, "%d%c", escData == NULL ? 0 : escData->temperature, SYM_TEMP_C); + tfp_sprintf(buff, "%3d%c", escData == NULL ? 0 : escData->temperature, SYM_TEMP_C); break; case OSD_ESC_RPM: - tfp_sprintf(buff, "%d", escData == NULL ? 0 : escData->rpm); + tfp_sprintf(buff, "%5d", escData == NULL ? 0 : escData->rpm); break; #endif #ifdef USE_RTC_TIME case OSD_RTC_DATETIME: - printRtcDateTime(&buff[0]); + osdFormatRtcDateTime(&buff[0]); break; #endif @@ -755,11 +759,13 @@ static void osdDrawSingleElement(uint8_t item) break; #endif + default: - return; + return false; } displayWrite(osdDisplayPort, elemPosX + elemOffsetX, elemPosY, buff); + return true; } static void osdDrawElements(void) @@ -915,7 +921,7 @@ void osdInit(displayPort_t *osdDisplayPortToUse) #ifdef USE_RTC_TIME char dateTimeBuffer[FORMATTED_DATE_TIME_BUFSIZE]; - if (printRtcDateTime(&dateTimeBuffer[0])) { + if (osdFormatRtcDateTime(&dateTimeBuffer[0])) { displayWrite(osdDisplayPort, 5, 12, dateTimeBuffer); } #endif @@ -1011,6 +1017,7 @@ static void osdResetStats(void) static void osdUpdateStats(void) { int16_t value = 0; + #ifdef USE_GPS value = CM_S_TO_KM_H(gpsSol.groundSpeed); #endif @@ -1027,6 +1034,7 @@ static void osdUpdateStats(void) stats.max_current = value; } + statRssi = scaleRange(getRssi(), 0, 1024, 0, 100); if (stats.min_rssi > statRssi) { stats.min_rssi = statRssi; } @@ -1097,10 +1105,10 @@ static void osdDisplayStatisticLabel(uint8_t y, const char * text, const char * static bool isSomeStatEnabled(void) { for (int i = 0; i < OSD_STAT_COUNT; i++) { - if (osdConfig()->enabled_stats[i]) { - return true; - } + if (osdConfig()->enabled_stats[i]) { + return true; } + } return false; } @@ -1115,7 +1123,7 @@ static void osdShowStats(void) if (osdConfig()->enabled_stats[OSD_STAT_RTC_DATE_TIME]) { bool success = false; #ifdef USE_RTC_TIME - success = printRtcDateTime(&buff[0]); + success = osdFormatRtcDateTime(&buff[0]); #endif if (!success) { tfp_sprintf(buff, "NO RTC"); @@ -1218,8 +1226,6 @@ STATIC_UNIT_TESTED void osdRefresh(timeUs_t currentTimeUs) armState = ARMING_FLAG(ARMED); } - statRssi = scaleRange(getRssi(), 0, 1024, 0, 100); - osdUpdateStats(); if (ARMING_FLAG(ARMED)) { @@ -1283,13 +1289,6 @@ void osdUpdate(timeUs_t currentTimeUs) } #endif // MAX7456_DMA_CHANNEL_TX - // redraw values in buffer -#ifdef USE_MAX7456 -#define DRAW_FREQ_DENOM 5 -#else -#define DRAW_FREQ_DENOM 10 // MWOSD @ 115200 baud ( -#endif - #ifdef USE_SLOW_MSP_DISPLAYPORT_RATE_WHEN_UNARMED static uint32_t idlecounter = 0; if (!ARMING_FLAG(ARMED)) { @@ -1299,13 +1298,22 @@ void osdUpdate(timeUs_t currentTimeUs) } #endif - if (counter++ % DRAW_FREQ_DENOM == 0) { - osdRefresh(currentTimeUs); + // redraw values in buffer +#ifdef USE_MAX7456 +#define DRAW_FREQ_DENOM 5 +#else +#define DRAW_FREQ_DENOM 10 // MWOSD @ 115200 baud ( +#endif +#define STATS_FREQ_DENOM 50 + if (counter % DRAW_FREQ_DENOM == 0) { + osdRefresh(currentTimeUs); showVisualBeeper = false; - } else { // rest of time redraw screen 10 chars per idle so it doesn't lock the main idle + } else { + // rest of time redraw screen 10 chars per idle so it doesn't lock the main idle displayDrawScreen(osdDisplayPort); } + ++counter; #ifdef USE_CMS // do not allow ARM if we are in menu @@ -1317,4 +1325,4 @@ void osdUpdate(timeUs_t currentTimeUs) #endif } -#endif // OSD +#endif // USE_OSD diff --git a/src/test/unit/osd_unittest.cc b/src/test/unit/osd_unittest.cc index 4121f18906..f3049187f7 100644 --- a/src/test/unit/osd_unittest.cc +++ b/src/test/unit/osd_unittest.cc @@ -139,10 +139,10 @@ void doTestArm(bool testEmpty = true) */ bool isSomeStatEnabled(void) { for (int i = 0; i < OSD_STAT_COUNT; i++) { - if (osdConfigMutable()->enabled_stats[i]) { - return true; - } + if (osdConfigMutable()->enabled_stats[i]) { + return true; } + } return false; } @@ -544,7 +544,7 @@ TEST(OsdTest, TestElementRssi) osdRefresh(simulationTime); // then - displayPortTestBufferSubstring(8, 1, "%c0", SYM_RSSI); + displayPortTestBufferSubstring(8, 1, "%c 0", SYM_RSSI); // when rssi = 512; diff --git a/src/test/unit/unittest_displayport.h b/src/test/unit/unittest_displayport.h index 4e649923dc..78be72a56d 100644 --- a/src/test/unit/unittest_displayport.h +++ b/src/test/unit/unittest_displayport.h @@ -143,6 +143,12 @@ void displayPortTestBufferIsEmpty() { for (size_t i = 0; i < UNITTEST_DISPLAYPORT_BUFFER_LEN; i++) { EXPECT_EQ(' ', testDisplayPortBuffer[i]); + if (testDisplayPortBuffer[i] != ' ') { + testDisplayPortBuffer[UNITTEST_DISPLAYPORT_BUFFER_LEN - 1] = '\0'; + printf("FIRST ERROR AT:%d\r\n", (int)i); + printf("DISPLAY:%s\r\n", testDisplayPortBuffer); + break; + } } }