2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
19 Created by Marcin Baliniak
20 some functions based on MinimOSD
22 OSD-CMS separation by jflyper
34 FILE_COMPILE_FOR_SPEED
38 #include "build/debug.h"
39 #include "build/version.h"
42 #include "cms/cms_types.h"
43 #include "cms/cms_menu_osd.h"
45 #include "common/axis.h"
46 #include "common/constants.h"
47 #include "common/filter.h"
48 #include "common/log.h"
49 #include "common/olc.h"
50 #include "common/printf.h"
51 #include "common/string_light.h"
52 #include "common/time.h"
53 #include "common/typeconversion.h"
54 #include "common/utils.h"
56 #include "config/feature.h"
57 #include "config/parameter_group.h"
58 #include "config/parameter_group_ids.h"
60 #include "drivers/display.h"
61 #include "drivers/display_canvas.h"
62 #include "drivers/display_font_metadata.h"
63 #include "drivers/osd_symbols.h"
64 #include "drivers/time.h"
65 #include "drivers/vtx_common.h"
67 #include "io/flashfs.h"
70 #include "io/osd_common.h"
71 #include "io/osd_hud.h"
72 #include "io/displayport_msp_bf_compat.h"
74 #include "io/vtx_string.h"
76 #include "fc/config.h"
77 #include "fc/controlrate_profile.h"
78 #include "fc/fc_core.h"
79 #include "fc/fc_tasks.h"
80 #include "fc/rc_adjustments.h"
81 #include "fc/rc_controls.h"
82 #include "fc/rc_modes.h"
83 #include "fc/runtime_config.h"
84 #include "fc/settings.h"
86 #include "flight/imu.h"
87 #include "flight/mixer.h"
88 #include "flight/pid.h"
89 #include "flight/power_limits.h"
90 #include "flight/rth_estimator.h"
91 #include "flight/servos.h"
92 #include "flight/wind_estimator.h"
94 #include "navigation/navigation.h"
95 #include "navigation/navigation_private.h"
98 #include "rx/msp_override.h"
100 #include "sensors/acceleration.h"
101 #include "sensors/battery.h"
102 #include "sensors/boardalignment.h"
103 #include "sensors/diagnostics.h"
104 #include "sensors/sensors.h"
105 #include "sensors/pitotmeter.h"
106 #include "sensors/temperature.h"
107 #include "sensors/esc_sensor.h"
108 #include "sensors/rangefinder.h"
110 #include "programming/logic_condition.h"
111 #include "programming/global_variables.h"
113 #ifdef USE_HARDWARE_REVISION_DETECTION
114 #include "hardware_revision.h"
117 #define VIDEO_BUFFER_CHARS_PAL 480
118 #define VIDEO_BUFFER_CHARS_HDZERO 900
119 #define VIDEO_BUFFER_CHARS_DJIWTF 1320
121 #define GFORCE_FILTER_TC 0.2
123 #define DELAYED_REFRESH_RESUME_COMMAND (checkStickPosition(THR_HI) || checkStickPosition(PIT_HI))
124 #define STATS_PAGE2 (checkStickPosition(ROL_HI))
125 #define STATS_PAGE1 (checkStickPosition(ROL_LO))
127 #define SPLASH_SCREEN_DISPLAY_TIME 4000 // ms
128 #define ARMED_SCREEN_DISPLAY_TIME 1500 // ms
129 #define STATS_SCREEN_DISPLAY_TIME 60000 // ms
131 #define EFFICIENCY_UPDATE_INTERVAL (5 * 1000)
133 // Adjust OSD_MESSAGE's default position when
134 // changing OSD_MESSAGE_LENGTH
135 #define OSD_MESSAGE_LENGTH 28
136 #define OSD_ALTERNATING_CHOICES(ms, num_choices) ((millis() / ms) % num_choices)
137 #define _CONST_STR_SIZE(s) ((sizeof(s)/sizeof(s[0]))-1) // -1 to avoid counting final '\0'
138 // Wrap all string constants intenteded for display as messages with
139 // this macro to ensure compile time length validation.
140 #define OSD_MESSAGE_STR(x) ({ \
141 STATIC_ASSERT(_CONST_STR_SIZE(x) <= OSD_MESSAGE_LENGTH, message_string_ ## __COUNTER__ ## _too_long); \
145 #define OSD_CHR_IS_NUM(c) (c >= '0' && c <= '9')
147 #define OSD_CENTER_LEN(x) ((osdDisplayPort->cols - x) / 2)
148 #define OSD_CENTER_S(s) OSD_CENTER_LEN(strlen(s))
150 #define OSD_MIN_FONT_VERSION 3
152 static timeMs_t notify_settings_saved
= 0;
153 static bool savingSettings
= false;
155 static unsigned currentLayout
= 0;
156 static int layoutOverride
= -1;
157 static bool hasExtendedFont
= false; // Wether the font supports characters > 256
158 static timeMs_t layoutOverrideUntil
= 0;
159 static pt1Filter_t GForceFilter
, GForceFilterAxis
[XYZ_AXIS_COUNT
];
160 static float GForce
, GForceAxis
[XYZ_AXIS_COUNT
];
162 typedef struct statistic_s
{
164 uint16_t max_3D_speed
;
165 uint16_t max_air_speed
;
166 uint16_t min_voltage
; // /100
170 int16_t min_lq
; // for CRSF
171 int16_t min_rssi_dbm
; // for CRSF
172 int32_t max_altitude
;
173 uint32_t max_distance
;
176 static statistic_t stats
;
178 static timeUs_t resumeRefreshAt
= 0;
179 static bool refreshWaitForResumeCmdRelease
;
181 static bool fullRedraw
= false;
183 static uint8_t armState
;
184 static uint8_t statsPagesCheck
= 0;
186 typedef struct osdMapData_s
{
188 char referenceSymbol
;
191 static osdMapData_t osdMapData
;
193 static displayPort_t
*osdDisplayPort
;
194 static bool osdDisplayIsReady
= false;
195 #if defined(USE_CANVAS)
196 static displayCanvas_t osdCanvas
;
197 static bool osdDisplayHasCanvas
;
199 #define osdDisplayHasCanvas false
202 #define AH_MAX_PITCH_DEFAULT 20 // Specify default maximum AHI pitch value displayed (degrees)
204 PG_REGISTER_WITH_RESET_TEMPLATE(osdConfig_t
, osdConfig
, PG_OSD_CONFIG
, 7);
205 PG_REGISTER_WITH_RESET_FN(osdLayoutsConfig_t
, osdLayoutsConfig
, PG_OSD_LAYOUTS_CONFIG
, 1);
207 void osdStartedSaveProcess() {
208 savingSettings
= true;
211 void osdShowEEPROMSavedNotification() {
212 savingSettings
= false;
213 notify_settings_saved
= millis() + 5000;
216 static int digitCount(int32_t value
)
229 bool osdDisplayIsPAL(void)
231 return displayScreenSize(osdDisplayPort
) == VIDEO_BUFFER_CHARS_PAL
;
234 bool osdDisplayIsHD(void)
236 if (displayScreenSize(osdDisplayPort
) >= VIDEO_BUFFER_CHARS_HDZERO
)
244 * Formats a number given in cents, to support non integer values
245 * without using floating point math. Value is always right aligned
246 * and spaces are inserted before the number to always yield a string
247 * of the same length. If the value doesn't fit into the provided length
248 * it will be divided by scale and true will be returned.
250 bool osdFormatCentiNumber(char *buff
, int32_t centivalue
, uint32_t scale
, int maxDecimals
, int maxScaledDecimals
, int length
)
254 int decimals
= maxDecimals
;
255 bool negative
= false;
257 bool explicitDecimal
= isBfCompatibleVideoSystem(osdConfig());
261 if (centivalue
< 0) {
263 centivalue
= -centivalue
;
267 int32_t integerPart
= centivalue
/ 100;
269 int32_t millis
= (centivalue
% 100) * 10;
271 int digits
= digitCount(integerPart
);
272 int remaining
= length
- digits
;
273 if (explicitDecimal
) {
277 if (remaining
< 0 && scale
> 0) {
280 decimals
= maxScaledDecimals
;
281 integerPart
= integerPart
/ scale
;
282 // Multiply by 10 to get 3 decimal digits
283 millis
= ((centivalue
% (100 * scale
)) * 10) / scale
;
284 digits
= digitCount(integerPart
);
285 remaining
= length
- digits
;
286 if (explicitDecimal
) {
291 // 3 decimals at most
292 decimals
= MIN(remaining
, MIN(decimals
, 3));
293 remaining
-= decimals
;
295 // Done counting. Time to write the characters.
297 // Write spaces at the start
298 while (remaining
> 0) {
304 // Write the minus sign if required
309 // Now write the digits.
310 ui2a(integerPart
, 10, 0, ptr
);
314 if (explicitDecimal
) {
318 *(ptr
- 1) += SYM_ZERO_HALF_TRAILING_DOT
- '0';
321 int factor
= 3; // we're getting the decimal part in millis first
322 while (decimals
< factor
) {
326 int decimalDigits
= digitCount(millis
);
327 while (decimalDigits
< decimals
) {
332 ui2a(millis
, 10, 0, ptr
);
333 if (!explicitDecimal
) {
334 *dec
+= SYM_ZERO_HALF_LEADING_DOT
- '0';
341 * Aligns text to the left side. Adds spaces at the end to keep string length unchanged.
343 static void osdLeftAlignString(char *buff
)
345 uint8_t sp
= 0, ch
= 0;
346 uint8_t len
= strlen(buff
);
347 while (buff
[sp
] == ' ') sp
++;
348 for (ch
= 0; ch
< (len
- sp
); ch
++) buff
[ch
] = buff
[ch
+ sp
];
349 for (sp
= ch
; sp
< len
; sp
++) buff
[sp
] = ' ';
353 * Converts distance into a string based on the current unit system
354 * prefixed by a a symbol to indicate the unit used.
355 * @param dist Distance in centimeters
357 static void osdFormatDistanceSymbol(char *buff
, int32_t dist
, uint8_t decimals
)
359 switch ((osd_unit_e
)osdConfig()->units
) {
362 case OSD_UNIT_IMPERIAL
:
363 if (osdFormatCentiNumber(buff
, CENTIMETERS_TO_CENTIFEET(dist
), FEET_PER_MILE
, decimals
, 3, 3)) {
364 buff
[3] = SYM_DIST_MI
;
366 buff
[3] = SYM_DIST_FT
;
370 case OSD_UNIT_METRIC_MPH
:
372 case OSD_UNIT_METRIC
:
373 if (osdFormatCentiNumber(buff
, dist
, METERS_PER_KILOMETER
, decimals
, 3, 3)) {
374 buff
[3] = SYM_DIST_KM
;
376 buff
[3] = SYM_DIST_M
;
381 if (osdFormatCentiNumber(buff
, CENTIMETERS_TO_CENTIFEET(dist
), FEET_PER_NAUTICALMILE
, decimals
, 3, 3)) {
382 buff
[3] = SYM_DIST_NM
;
384 buff
[3] = SYM_DIST_FT
;
392 * Converts distance into a string based on the current unit system.
393 * @param dist Distance in centimeters
395 static void osdFormatDistanceStr(char *buff
, int32_t dist
)
398 switch ((osd_unit_e
)osdConfig()->units
) {
401 case OSD_UNIT_IMPERIAL
:
402 centifeet
= CENTIMETERS_TO_CENTIFEET(dist
);
403 if (abs(centifeet
) < FEET_PER_MILE
* 100 / 2) {
404 // Show feet when dist < 0.5mi
405 tfp_sprintf(buff
, "%d%c", (int)(centifeet
/ 100), SYM_FT
);
407 // Show miles when dist >= 0.5mi
408 tfp_sprintf(buff
, "%d.%02d%c", (int)(centifeet
/ (100*FEET_PER_MILE
)),
409 (abs(centifeet
) % (100 * FEET_PER_MILE
)) / FEET_PER_MILE
, SYM_MI
);
412 case OSD_UNIT_METRIC_MPH
:
414 case OSD_UNIT_METRIC
:
415 if (abs(dist
) < METERS_PER_KILOMETER
* 100) {
416 // Show meters when dist < 1km
417 tfp_sprintf(buff
, "%d%c", (int)(dist
/ 100), SYM_M
);
419 // Show kilometers when dist >= 1km
420 tfp_sprintf(buff
, "%d.%02d%c", (int)(dist
/ (100*METERS_PER_KILOMETER
)),
421 (abs(dist
) % (100 * METERS_PER_KILOMETER
)) / METERS_PER_KILOMETER
, SYM_KM
);
425 centifeet
= CENTIMETERS_TO_CENTIFEET(dist
);
426 if (abs(centifeet
) < 100000) {
427 // Show feet when dist < 1000ft
428 tfp_sprintf(buff
, "%d%c", (int)(centifeet
/ 100), SYM_FT
);
430 // Show nautical miles when dist >= 1000ft
431 tfp_sprintf(buff
, "%d.%02d%c", (int)(centifeet
/ (100 * FEET_PER_NAUTICALMILE
)),
432 (int)((abs(centifeet
) % (int)(100 * FEET_PER_NAUTICALMILE
)) / FEET_PER_NAUTICALMILE
), SYM_NM
);
439 * Converts velocity based on the current unit system (kmh or mph).
440 * @param alt Raw velocity (i.e. as taken from gpsSol.groundSpeed in centimeters/second)
442 static int32_t osdConvertVelocityToUnit(int32_t vel
)
444 switch ((osd_unit_e
)osdConfig()->units
) {
447 case OSD_UNIT_METRIC_MPH
:
449 case OSD_UNIT_IMPERIAL
:
450 return CMSEC_TO_CENTIMPH(vel
) / 100; // Convert to mph
451 case OSD_UNIT_METRIC
:
452 return CMSEC_TO_CENTIKPH(vel
) / 100; // Convert to kmh
454 return CMSEC_TO_CENTIKNOTS(vel
) / 100; // Convert to Knots
461 * Converts velocity into a string based on the current unit system.
462 * @param vel Raw velocity (i.e. as taken from gpsSol.groundSpeed in centimeters/seconds)
463 * @param _3D is a 3D velocity
464 * @param _max is a maximum velocity
466 void osdFormatVelocityStr(char* buff
, int32_t vel
, bool _3D
, bool _max
)
468 switch ((osd_unit_e
)osdConfig()->units
) {
471 case OSD_UNIT_METRIC_MPH
:
473 case OSD_UNIT_IMPERIAL
:
475 tfp_sprintf(buff
, "%c%3d%c", SYM_MAX
, (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_MPH
: SYM_MPH
));
477 tfp_sprintf(buff
, "%3d%c", (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_MPH
: SYM_MPH
));
480 case OSD_UNIT_METRIC
:
482 tfp_sprintf(buff
, "%c%3d%c", SYM_MAX
, (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KMH
: SYM_KMH
));
484 tfp_sprintf(buff
, "%3d%c", (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KMH
: SYM_KMH
));
489 tfp_sprintf(buff
, "%c%3d%c", SYM_MAX
, (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KT
: SYM_KT
));
491 tfp_sprintf(buff
, "%3d%c", (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KT
: SYM_KT
));
498 * Returns the average velocity. This always uses stats, so can be called as an OSD element later if wanted, to show a real time average
500 static void osdGenerateAverageVelocityStr(char* buff
) {
501 uint32_t cmPerSec
= getTotalTravelDistance() / getFlightTime();
502 osdFormatVelocityStr(buff
, cmPerSec
, false, false);
506 * Converts wind speed into a string based on the current unit system, using
507 * always 3 digits and an additional character for the unit at the right. buff
508 * is null terminated.
509 * @param ws Raw wind speed in cm/s
511 #ifdef USE_WIND_ESTIMATOR
512 static void osdFormatWindSpeedStr(char *buff
, int32_t ws
, bool isValid
)
516 switch (osdConfig()->units
) {
519 case OSD_UNIT_METRIC_MPH
:
521 case OSD_UNIT_IMPERIAL
:
522 centivalue
= CMSEC_TO_CENTIMPH(ws
);
526 centivalue
= CMSEC_TO_CENTIKNOTS(ws
);
530 case OSD_UNIT_METRIC
:
531 centivalue
= CMSEC_TO_CENTIKPH(ws
);
535 osdFormatCentiNumber(buff
, centivalue
, 0, 2, 0, 3);
546 * Converts altitude into a string based on the current unit system
547 * prefixed by a a symbol to indicate the unit used.
548 * @param alt Raw altitude/distance (i.e. as taken from baro.BaroAlt in centimeters)
550 void osdFormatAltitudeSymbol(char *buff
, int32_t alt
)
559 switch ((osd_unit_e
)osdConfig()->units
) {
564 case OSD_UNIT_IMPERIAL
:
565 if (osdFormatCentiNumber(buff
+ 4 - digits
, CENTIMETERS_TO_CENTIFEET(alt
), 1000, 0, 2, digits
)) {
567 buff
[4] = SYM_ALT_KFT
;
570 buff
[4] = SYM_ALT_FT
;
574 case OSD_UNIT_METRIC_MPH
:
576 case OSD_UNIT_METRIC
:
577 // alt is alredy in cm
578 if (osdFormatCentiNumber(buff
+ 4 - digits
, alt
, 1000, 0, 2, digits
)) {
580 buff
[4] = SYM_ALT_KM
;
591 * Converts altitude into a string based on the current unit system.
592 * @param alt Raw altitude/distance (i.e. as taken from baro.BaroAlt in centimeters)
594 static void osdFormatAltitudeStr(char *buff
, int32_t alt
)
597 switch ((osd_unit_e
)osdConfig()->units
) {
602 case OSD_UNIT_IMPERIAL
:
603 value
= CENTIMETERS_TO_FEET(alt
);
604 tfp_sprintf(buff
, "%d%c", (int)value
, SYM_FT
);
606 case OSD_UNIT_METRIC_MPH
:
608 case OSD_UNIT_METRIC
:
609 value
= CENTIMETERS_TO_METERS(alt
);
610 tfp_sprintf(buff
, "%d%c", (int)value
, SYM_M
);
615 static void osdFormatTime(char *buff
, uint32_t seconds
, char sym_m
, char sym_h
)
617 uint32_t value
= seconds
;
619 // Maximum value we can show in minutes is 99 minutes and 59 seconds
620 if (seconds
> (99 * 60) + 59) {
622 value
= seconds
/ 60;
625 tfp_sprintf(buff
+ 1, "%02d:%02d", (int)(value
/ 60), (int)(value
% 60));
628 static inline void osdFormatOnTime(char *buff
)
630 osdFormatTime(buff
, micros() / 1000000, SYM_ON_M
, SYM_ON_H
);
633 static inline void osdFormatFlyTime(char *buff
, textAttributes_t
*attr
)
635 uint32_t seconds
= getFlightTime();
636 osdFormatTime(buff
, seconds
, SYM_FLY_M
, SYM_FLY_H
);
637 if (attr
&& osdConfig()->time_alarm
> 0) {
638 if (seconds
/ 60 >= osdConfig()->time_alarm
&& ARMING_FLAG(ARMED
)) {
639 TEXT_ATTRIBUTES_ADD_BLINK(*attr
);
645 * Converts RSSI into a % value used by the OSD.
647 static uint16_t osdConvertRSSI(void)
649 // change range to [0, 99]
650 return constrain(getRSSI() * 100 / RSSI_MAX_VALUE
, 0, 99);
653 static uint16_t osdGetCrsfLQ(void)
655 int16_t statsLQ
= rxLinkStatistics
.uplinkLQ
;
656 int16_t scaledLQ
= scaleRange(constrain(statsLQ
, 0, 100), 0, 100, 170, 300);
658 switch (osdConfig()->crsf_lq_format
) {
659 case OSD_CRSF_LQ_TYPE1
:
660 displayedLQ
= statsLQ
;
662 case OSD_CRSF_LQ_TYPE2
:
663 displayedLQ
= statsLQ
;
665 case OSD_CRSF_LQ_TYPE3
:
666 displayedLQ
= rxLinkStatistics
.rfMode
>= 2 ? scaledLQ
: statsLQ
;
672 static int16_t osdGetCrsfdBm(void)
674 return rxLinkStatistics
.uplinkRSSI
;
677 * Displays a temperature postfixed with a symbol depending on the current unit system
678 * @param label to display
679 * @param valid true if measurement is valid
680 * @param temperature in deciDegrees Celcius
682 static void osdDisplayTemperature(uint8_t elemPosX
, uint8_t elemPosY
, uint16_t symbol
, const char *label
, bool valid
, int16_t temperature
, int16_t alarm_min
, int16_t alarm_max
)
684 char buff
[TEMPERATURE_LABEL_LEN
+ 2 < 6 ? 6 : TEMPERATURE_LABEL_LEN
+ 2];
685 textAttributes_t elemAttr
= valid
? TEXT_ATTRIBUTES_NONE
: _TEXT_ATTRIBUTES_BLINK_BIT
;
686 uint8_t valueXOffset
= 0;
691 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
694 #ifdef USE_TEMPERATURE_SENSOR
695 else if (label
[0] != '\0') {
696 uint8_t label_len
= strnlen(label
, TEMPERATURE_LABEL_LEN
);
697 memcpy(buff
, label
, label_len
);
698 memset(buff
+ label_len
, ' ', TEMPERATURE_LABEL_LEN
+ 1 - label_len
);
700 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
701 valueXOffset
= osdConfig()->temp_label_align
== OSD_ALIGN_LEFT
? 5 : label_len
+ 1;
709 if ((temperature
<= alarm_min
) || (temperature
>= alarm_max
)) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
710 if (osdConfig()->units
== OSD_UNIT_IMPERIAL
) temperature
= temperature
* 9 / 5.0f
+ 320;
711 tfp_sprintf(buff
, "%3d", temperature
/ 10);
716 buff
[3] = osdConfig()->units
== OSD_UNIT_IMPERIAL
? SYM_TEMP_F
: SYM_TEMP_C
;
719 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ valueXOffset
, elemPosY
, buff
, elemAttr
);
722 #ifdef USE_TEMPERATURE_SENSOR
723 static void osdDisplayTemperatureSensor(uint8_t elemPosX
, uint8_t elemPosY
, uint8_t sensorIndex
)
726 const bool valid
= getSensorTemperature(sensorIndex
, &temperature
);
727 const tempSensorConfig_t
*sensorConfig
= tempSensorConfig(sensorIndex
);
728 uint16_t symbol
= sensorConfig
->osdSymbol
? SYM_TEMP_SENSOR_FIRST
+ sensorConfig
->osdSymbol
- 1 : 0;
729 osdDisplayTemperature(elemPosX
, elemPosY
, symbol
, sensorConfig
->label
, valid
, temperature
, sensorConfig
->alarm_min
, sensorConfig
->alarm_max
);
733 static void osdFormatCoordinate(char *buff
, char sym
, int32_t val
)
735 // up to 4 for number + 1 for the symbol + null terminator + fill the rest with decimals
736 const int coordinateLength
= osdConfig()->coordinate_digits
+ 1;
739 int32_t integerPart
= val
/ GPS_DEGREES_DIVIDER
;
740 // Latitude maximum integer width is 3 (-90) while
741 // longitude maximum integer width is 4 (-180).
742 int integerDigits
= tfp_sprintf(buff
+ 1, (integerPart
== 0 && val
< 0) ? "-%d" : "%d", (int)integerPart
);
743 // We can show up to 7 digits in decimalPart.
744 int32_t decimalPart
= abs(val
% GPS_DEGREES_DIVIDER
);
745 STATIC_ASSERT(GPS_DEGREES_DIVIDER
== 1e7
, adjust_max_decimal_digits
);
747 if (!isBfCompatibleVideoSystem(osdConfig())) {
748 decimalDigits
= tfp_sprintf(buff
+ 1 + integerDigits
, "%07d", (int)decimalPart
);
749 // Embbed the decimal separator
750 buff
[1 + integerDigits
- 1] += SYM_ZERO_HALF_TRAILING_DOT
- '0';
751 buff
[1 + integerDigits
] += SYM_ZERO_HALF_LEADING_DOT
- '0';
753 decimalDigits
= tfp_sprintf(buff
+ 1 + integerDigits
, ".%06d", (int)decimalPart
);
755 // Fill up to coordinateLength with zeros
756 int total
= 1 + integerDigits
+ decimalDigits
;
757 while(total
< coordinateLength
) {
761 buff
[coordinateLength
] = '\0';
764 static void osdFormatCraftName(char *buff
)
766 if (strlen(systemConfig()->craftName
) == 0)
767 strcpy(buff
, "CRAFT_NAME");
769 for (int i
= 0; i
< MAX_NAME_LENGTH
; i
++) {
770 buff
[i
] = sl_toupper((unsigned char)systemConfig()->craftName
[i
]);
771 if (systemConfig()->craftName
[i
] == 0)
777 void osdFormatPilotName(char *buff
)
779 if (strlen(systemConfig()->pilotName
) == 0)
780 strcpy(buff
, "PILOT_NAME");
782 for (int i
= 0; i
< MAX_NAME_LENGTH
; i
++) {
783 buff
[i
] = sl_toupper((unsigned char)systemConfig()->pilotName
[i
]);
784 if (systemConfig()->pilotName
[i
] == 0)
790 static const char * osdArmingDisabledReasonMessage(void)
792 const char *message
= NULL
;
793 char messageBuf
[MAX(SETTING_MAX_NAME_LENGTH
, OSD_MESSAGE_LENGTH
+1)];
795 switch (isArmingDisabledReason()) {
796 case ARMING_DISABLED_FAILSAFE_SYSTEM
:
797 // See handling of FAILSAFE_RX_LOSS_MONITORING in failsafe.c
798 if (failsafePhase() == FAILSAFE_RX_LOSS_MONITORING
) {
799 if (failsafeIsReceivingRxData()) {
800 // If we're not using sticks, it means the ARM switch
801 // hasn't been off since entering FAILSAFE_RX_LOSS_MONITORING
803 return OSD_MESSAGE_STR(OSD_MSG_TURN_ARM_SW_OFF
);
805 // Not receiving RX data
806 return OSD_MESSAGE_STR(OSD_MSG_RC_RX_LINK_LOST
);
808 return OSD_MESSAGE_STR(OSD_MSG_DISABLED_BY_FS
);
809 case ARMING_DISABLED_NOT_LEVEL
:
810 return OSD_MESSAGE_STR(OSD_MSG_AIRCRAFT_UNLEVEL
);
811 case ARMING_DISABLED_SENSORS_CALIBRATING
:
812 return OSD_MESSAGE_STR(OSD_MSG_SENSORS_CAL
);
813 case ARMING_DISABLED_SYSTEM_OVERLOADED
:
814 return OSD_MESSAGE_STR(OSD_MSG_SYS_OVERLOADED
);
815 case ARMING_DISABLED_NAVIGATION_UNSAFE
:
816 // Check the exact reason
817 switch (navigationIsBlockingArming(NULL
)) {
819 case NAV_ARMING_BLOCKER_NONE
:
821 case NAV_ARMING_BLOCKER_MISSING_GPS_FIX
:
822 return OSD_MESSAGE_STR(OSD_MSG_WAITING_GPS_FIX
);
823 case NAV_ARMING_BLOCKER_NAV_IS_ALREADY_ACTIVE
:
824 return OSD_MESSAGE_STR(OSD_MSG_DISABLE_NAV_FIRST
);
825 case NAV_ARMING_BLOCKER_FIRST_WAYPOINT_TOO_FAR
:
826 osdFormatDistanceSymbol(buf
, distanceToFirstWP(), 0);
827 tfp_sprintf(messageBuf
, "FIRST WP TOO FAR (%s)", buf
);
828 return message
= messageBuf
;
829 case NAV_ARMING_BLOCKER_JUMP_WAYPOINT_ERROR
:
830 return OSD_MESSAGE_STR(OSD_MSG_JUMP_WP_MISCONFIG
);
833 case ARMING_DISABLED_COMPASS_NOT_CALIBRATED
:
834 return OSD_MESSAGE_STR(OSD_MSG_MAG_NOT_CAL
);
835 case ARMING_DISABLED_ACCELEROMETER_NOT_CALIBRATED
:
836 return OSD_MESSAGE_STR(OSD_MSG_ACC_NOT_CAL
);
837 case ARMING_DISABLED_ARM_SWITCH
:
838 return OSD_MESSAGE_STR(OSD_MSG_DISARM_1ST
);
839 case ARMING_DISABLED_HARDWARE_FAILURE
:
841 if (!HW_SENSOR_IS_HEALTHY(getHwGyroStatus())) {
842 return OSD_MESSAGE_STR(OSD_MSG_GYRO_FAILURE
);
844 if (!HW_SENSOR_IS_HEALTHY(getHwAccelerometerStatus())) {
845 return OSD_MESSAGE_STR(OSD_MSG_ACC_FAIL
);
847 if (!HW_SENSOR_IS_HEALTHY(getHwCompassStatus())) {
848 return OSD_MESSAGE_STR(OSD_MSG_MAG_FAIL
);
850 if (!HW_SENSOR_IS_HEALTHY(getHwBarometerStatus())) {
851 return OSD_MESSAGE_STR(OSD_MSG_BARO_FAIL
);
853 if (!HW_SENSOR_IS_HEALTHY(getHwGPSStatus())) {
854 return OSD_MESSAGE_STR(OSD_MSG_GPS_FAIL
);
856 if (!HW_SENSOR_IS_HEALTHY(getHwRangefinderStatus())) {
857 return OSD_MESSAGE_STR(OSD_MSG_RANGEFINDER_FAIL
);
859 if (!HW_SENSOR_IS_HEALTHY(getHwPitotmeterStatus())) {
860 return OSD_MESSAGE_STR(OSD_MSG_PITOT_FAIL
);
863 return OSD_MESSAGE_STR(OSD_MSG_HW_FAIL
);
864 case ARMING_DISABLED_BOXFAILSAFE
:
865 return OSD_MESSAGE_STR(OSD_MSG_FS_EN
);
866 case ARMING_DISABLED_BOXKILLSWITCH
:
867 return OSD_MESSAGE_STR(OSD_MSG_KILL_SW_EN
);
868 case ARMING_DISABLED_RC_LINK
:
869 return OSD_MESSAGE_STR(OSD_MSG_NO_RC_LINK
);
870 case ARMING_DISABLED_THROTTLE
:
871 return OSD_MESSAGE_STR(OSD_MSG_THROTTLE_NOT_LOW
);
872 case ARMING_DISABLED_ROLLPITCH_NOT_CENTERED
:
873 return OSD_MESSAGE_STR(OSD_MSG_ROLLPITCH_OFFCENTER
);
874 case ARMING_DISABLED_SERVO_AUTOTRIM
:
875 return OSD_MESSAGE_STR(OSD_MSG_AUTOTRIM_ACTIVE
);
876 case ARMING_DISABLED_OOM
:
877 return OSD_MESSAGE_STR(OSD_MSG_NOT_ENOUGH_MEMORY
);
878 case ARMING_DISABLED_INVALID_SETTING
:
879 return OSD_MESSAGE_STR(OSD_MSG_INVALID_SETTING
);
880 case ARMING_DISABLED_CLI
:
881 return OSD_MESSAGE_STR(OSD_MSG_CLI_ACTIVE
);
882 case ARMING_DISABLED_PWM_OUTPUT_ERROR
:
883 return OSD_MESSAGE_STR(OSD_MSG_PWM_INIT_ERROR
);
884 case ARMING_DISABLED_NO_PREARM
:
885 return OSD_MESSAGE_STR(OSD_MSG_NO_PREARM
);
886 case ARMING_DISABLED_DSHOT_BEEPER
:
887 return OSD_MESSAGE_STR(OSD_MSG_DSHOT_BEEPER
);
888 // Cases without message
889 case ARMING_DISABLED_LANDING_DETECTED
:
891 case ARMING_DISABLED_CMS_MENU
:
893 case ARMING_DISABLED_OSD_MENU
:
895 case ARMING_DISABLED_ALL_FLAGS
:
907 static const char * osdFailsafePhaseMessage(void)
909 // See failsafe.h for each phase explanation
910 switch (failsafePhase()) {
911 case FAILSAFE_RETURN_TO_HOME
:
912 // XXX: Keep this in sync with OSD_FLYMODE.
913 return OSD_MESSAGE_STR(OSD_MSG_RTH_FS
);
914 case FAILSAFE_LANDING
:
915 // This should be considered an emergengy landing
916 return OSD_MESSAGE_STR(OSD_MSG_EMERG_LANDING_FS
);
917 case FAILSAFE_RX_LOSS_MONITORING
:
918 // Only reachable from FAILSAFE_LANDED, which performs
919 // a disarm. Since aircraft has been disarmed, we no
920 // longer show failsafe details.
922 case FAILSAFE_LANDED
:
923 // Very brief, disarms and transitions into
924 // FAILSAFE_RX_LOSS_MONITORING. Note that it prevents
925 // further rearming via ARMING_DISABLED_FAILSAFE_SYSTEM,
926 // so we'll show the user how to re-arm in when
927 // that flag is the reason to prevent arming.
929 case FAILSAFE_RX_LOSS_IDLE
:
930 // This only happens when user has chosen NONE as FS
931 // procedure. The recovery messages should be enough.
934 // Failsafe not active
936 case FAILSAFE_RX_LOSS_DETECTED
:
937 // Very brief, changes to FAILSAFE_RX_LOSS_RECOVERED
938 // or the FS procedure immediately.
940 case FAILSAFE_RX_LOSS_RECOVERED
:
947 static const char * osdFailsafeInfoMessage(void)
949 if (failsafeIsReceivingRxData()) {
950 // User must move sticks to exit FS mode
951 return OSD_MESSAGE_STR(OSD_MSG_MOVE_EXIT_FS
);
953 return OSD_MESSAGE_STR(OSD_MSG_RC_RX_LINK_LOST
);
955 #if defined(USE_SAFE_HOME)
956 static const char * divertingToSafehomeMessage(void)
958 if (NAV_Status
.state
!= MW_NAV_STATE_HOVER_ABOVE_HOME
&& safehome_applied
) {
959 return OSD_MESSAGE_STR(OSD_MSG_DIVERT_SAFEHOME
);
965 static const char * navigationStateMessage(void)
967 switch (NAV_Status
.state
) {
968 case MW_NAV_STATE_NONE
:
970 case MW_NAV_STATE_RTH_START
:
971 return OSD_MESSAGE_STR(OSD_MSG_STARTING_RTH
);
972 case MW_NAV_STATE_RTH_CLIMB
:
973 return OSD_MESSAGE_STR(OSD_MSG_RTH_CLIMB
);
974 case MW_NAV_STATE_RTH_ENROUTE
:
975 if (posControl
.flags
.rthTrackbackActive
) {
976 return OSD_MESSAGE_STR(OSD_MSG_RTH_TRACKBACK
);
978 return OSD_MESSAGE_STR(OSD_MSG_HEADING_HOME
);
980 case MW_NAV_STATE_HOLD_INFINIT
:
981 // Used by HOLD flight modes. No information to add.
983 case MW_NAV_STATE_HOLD_TIMED
:
984 // "HOLDING WP FOR xx S" Countdown added in osdGetSystemMessage
986 case MW_NAV_STATE_WP_ENROUTE
:
987 // "TO WP" + WP countdown added in osdGetSystemMessage
989 case MW_NAV_STATE_PROCESS_NEXT
:
990 return OSD_MESSAGE_STR(OSD_MSG_PREPARE_NEXT_WP
);
991 case MW_NAV_STATE_DO_JUMP
:
994 case MW_NAV_STATE_LAND_START
:
997 case MW_NAV_STATE_EMERGENCY_LANDING
:
998 return OSD_MESSAGE_STR(OSD_MSG_EMERG_LANDING
);
999 case MW_NAV_STATE_LAND_IN_PROGRESS
:
1000 return OSD_MESSAGE_STR(OSD_MSG_LANDING
);
1001 case MW_NAV_STATE_HOVER_ABOVE_HOME
:
1002 if (STATE(FIXED_WING_LEGACY
)) {
1003 #if defined(USE_SAFE_HOME)
1004 if (safehome_applied
) {
1005 return OSD_MESSAGE_STR(OSD_MSG_LOITERING_SAFEHOME
);
1008 return OSD_MESSAGE_STR(OSD_MSG_LOITERING_HOME
);
1010 return OSD_MESSAGE_STR(OSD_MSG_HOVERING
);
1011 case MW_NAV_STATE_LANDED
:
1012 return OSD_MESSAGE_STR(OSD_MSG_LANDED
);
1013 case MW_NAV_STATE_LAND_SETTLE
:
1014 return OSD_MESSAGE_STR(OSD_MSG_PREPARING_LAND
);
1015 case MW_NAV_STATE_LAND_START_DESCENT
:
1022 static void osdFormatMessage(char *buff
, size_t size
, const char *message
, bool isCenteredText
)
1024 // String is always filled with Blanks
1025 memset(buff
, SYM_BLANK
, size
);
1027 size_t messageLength
= strlen(message
);
1028 int rem
= isCenteredText
? MAX(0, (int)size
- (int)messageLength
) : 0;
1029 strncpy(buff
+ rem
/ 2, message
, MIN((int)size
- rem
/ 2, (int)messageLength
));
1031 // Ensure buff is zero terminated
1036 * Draws the battery symbol filled in accordingly to the
1037 * battery voltage to buff[0].
1039 static void osdFormatBatteryChargeSymbol(char *buff
)
1041 uint8_t p
= calculateBatteryPercentage();
1042 p
= (100 - p
) / 16.6;
1043 buff
[0] = SYM_BATT_FULL
+ p
;
1046 static void osdUpdateBatteryCapacityOrVoltageTextAttributes(textAttributes_t
*attr
)
1048 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && ((batteryUsesCapacityThresholds() && (getBatteryRemainingCapacity() <= currentBatteryProfile
->capacity
.warning
- currentBatteryProfile
->capacity
.critical
)) || ((!batteryUsesCapacityThresholds()) && (getBatteryVoltage() <= getBatteryWarningVoltage()))))
1049 TEXT_ATTRIBUTES_ADD_BLINK(*attr
);
1052 void osdCrosshairPosition(uint8_t *x
, uint8_t *y
)
1054 *x
= osdDisplayPort
->cols
/ 2;
1055 *y
= osdDisplayPort
->rows
/ 2;
1056 *y
-= osdConfig()->horizon_offset
; // positive horizon_offset moves the HUD up, negative moves down
1060 * Formats throttle position prefixed by its symbol.
1061 * Shows output to motor, not stick position
1063 static void osdFormatThrottlePosition(char *buff
, bool autoThr
, textAttributes_t
*elemAttr
)
1065 buff
[0] = SYM_BLANK
;
1067 if (autoThr
&& navigationIsControllingThrottle()) {
1068 buff
[0] = SYM_AUTO_THR0
;
1069 buff
[1] = SYM_AUTO_THR1
;
1070 if (isFixedWingAutoThrottleManuallyIncreased()) {
1071 TEXT_ATTRIBUTES_ADD_BLINK(*elemAttr
);
1074 #ifdef USE_POWER_LIMITS
1075 if (powerLimiterIsLimiting()) {
1076 TEXT_ATTRIBUTES_ADD_BLINK(*elemAttr
);
1079 tfp_sprintf(buff
+ 2, "%3d", getThrottlePercent());
1083 * Formats gvars prefixed by its number (0-indexed). If autoThr
1085 static void osdFormatGVar(char *buff
, uint8_t index
)
1088 buff
[1] = '0'+index
;
1090 #ifdef USE_PROGRAMMING_FRAMEWORK
1091 osdFormatCentiNumber(buff
+ 3, (int32_t)gvGet(index
)*(int32_t)100, 1, 0, 0, 5);
1095 #if defined(USE_ESC_SENSOR)
1096 static void osdFormatRpm(char *buff
, uint32_t rpm
)
1100 if ( digitCount(rpm
) > osdConfig()->esc_rpm_precision
) {
1101 uint8_t rpmMaxDecimals
= (osdConfig()->esc_rpm_precision
- 3);
1102 osdFormatCentiNumber(buff
+ 1, rpm
/ 10, 0, rpmMaxDecimals
, rpmMaxDecimals
, osdConfig()->esc_rpm_precision
-1);
1103 buff
[osdConfig()->esc_rpm_precision
] = 'K';
1104 buff
[osdConfig()->esc_rpm_precision
+1] = '\0';
1107 switch(osdConfig()->esc_rpm_precision
) {
1109 tfp_sprintf(buff
+ 1, "%6lu", rpm
);
1112 tfp_sprintf(buff
+ 1, "%5lu", rpm
);
1115 tfp_sprintf(buff
+ 1, "%4lu", rpm
);
1119 tfp_sprintf(buff
+ 1, "%3lu", rpm
);
1127 uint8_t buffPos
= 1;
1128 while (buffPos
<=( osdConfig()->esc_rpm_precision
)) {
1129 strcpy(buff
+ buffPos
++, "-");
1135 int32_t osdGetAltitude(void)
1137 return getEstimatedActualPosition(Z
);
1140 static inline int32_t osdGetAltitudeMsl(void)
1142 return getEstimatedActualPosition(Z
)+GPS_home
.alt
;
1145 uint16_t osdGetRemainingGlideTime(void) {
1146 float value
= getEstimatedActualVelocity(Z
);
1147 static pt1Filter_t glideTimeFilterState
;
1148 const timeMs_t curTimeMs
= millis();
1149 static timeMs_t glideTimeUpdatedMs
;
1151 value
= pt1FilterApply4(&glideTimeFilterState
, isnormal(value
) ? value
: 0, 0.5, MS2S(curTimeMs
- glideTimeUpdatedMs
));
1152 glideTimeUpdatedMs
= curTimeMs
;
1155 value
= osdGetAltitude() / abs((int)value
);
1160 return (uint16_t)round(value
);
1163 static bool osdIsHeadingValid(void)
1165 return isImuHeadingValid();
1168 int16_t osdGetHeading(void)
1170 return attitude
.values
.yaw
;
1173 int16_t osdPanServoHomeDirectionOffset(void)
1175 int8_t servoIndex
= osdConfig()->pan_servo_index
;
1176 int16_t servoPosition
= servo
[servoIndex
];
1177 int16_t servoMiddle
= servoParams(servoIndex
)->middle
;
1178 return (int16_t)CENTIDEGREES_TO_DEGREES((servoPosition
- servoMiddle
) * osdConfig()->pan_servo_pwm2centideg
);
1181 // Returns a heading angle in degrees normalized to [0, 360).
1182 int osdGetHeadingAngle(int angle
)
1187 while (angle
>= 360) {
1193 #if defined(USE_GPS)
1195 /* Draws a map with the given symbol in the center and given point of interest
1196 * defined by its distance in meters and direction in degrees.
1197 * referenceHeading indicates the up direction in the map, in degrees, while
1198 * referenceSym (if non-zero) is drawn at the upper right corner below a small
1199 * arrow to indicate the map reference to the user. The drawn argument is an
1200 * in-out used to store the last position where the craft was drawn to avoid
1201 * erasing all screen on each redraw.
1203 static void osdDrawMap(int referenceHeading
, uint16_t referenceSym
, uint16_t centerSym
,
1204 uint32_t poiDistance
, int16_t poiDirection
, uint16_t poiSymbol
,
1205 uint16_t *drawn
, uint32_t *usedScale
)
1207 // TODO: These need to be tested with several setups. We might
1208 // need to make them configurable.
1209 const int hMargin
= 5;
1210 const int vMargin
= 3;
1212 // TODO: Get this from the display driver?
1213 const int charWidth
= 12;
1214 const int charHeight
= 18;
1216 uint8_t minX
= hMargin
;
1217 uint8_t maxX
= osdDisplayPort
->cols
- 1 - hMargin
;
1218 uint8_t minY
= vMargin
;
1219 uint8_t maxY
= osdDisplayPort
->rows
- 1 - vMargin
;
1220 uint8_t midX
= osdDisplayPort
->cols
/ 2;
1221 uint8_t midY
= osdDisplayPort
->rows
/ 2;
1224 displayWriteChar(osdDisplayPort
, midX
, midY
, centerSym
);
1226 // First, erase the previous drawing.
1227 if (OSD_VISIBLE(*drawn
)) {
1228 displayWriteChar(osdDisplayPort
, OSD_X(*drawn
), OSD_Y(*drawn
), SYM_BLANK
);
1232 uint32_t initialScale
;
1233 const unsigned scaleMultiplier
= 2;
1234 // We try to reduce the scale when the POI will be around half the distance
1235 // between the center and the closers map edge, to avoid too much jumping
1236 const int scaleReductionMultiplier
= MIN(midX
- hMargin
, midY
- vMargin
) / 2;
1238 switch (osdConfig()->units
) {
1241 case OSD_UNIT_IMPERIAL
:
1242 initialScale
= 16; // 16m ~= 0.01miles
1245 initialScale
= 18; // 18m ~= 0.01 nautical miles
1248 case OSD_UNIT_METRIC_MPH
:
1250 case OSD_UNIT_METRIC
:
1251 initialScale
= 10; // 10m as initial scale
1255 // Try to keep the same scale when getting closer until we draw over the center point
1256 uint32_t scale
= initialScale
;
1259 if (scale
> initialScale
&& poiDistance
< *usedScale
* scaleReductionMultiplier
) {
1260 scale
/= scaleMultiplier
;
1264 if (STATE(GPS_FIX
)) {
1266 int directionToPoi
= osdGetHeadingAngle(poiDirection
- referenceHeading
);
1267 float poiAngle
= DEGREES_TO_RADIANS(directionToPoi
);
1268 float poiSin
= sin_approx(poiAngle
);
1269 float poiCos
= cos_approx(poiAngle
);
1271 // Now start looking for a valid scale that lets us draw everything
1273 for (ii
= 0; ii
< 50; ii
++) {
1274 // Calculate location of the aircraft in map
1275 int points
= poiDistance
/ ((float)scale
/ charHeight
);
1277 float pointsX
= points
* poiSin
;
1278 int poiX
= midX
- roundf(pointsX
/ charWidth
);
1279 if (poiX
< minX
|| poiX
> maxX
) {
1280 scale
*= scaleMultiplier
;
1284 float pointsY
= points
* poiCos
;
1285 int poiY
= midY
+ roundf(pointsY
/ charHeight
);
1286 if (poiY
< minY
|| poiY
> maxY
) {
1287 scale
*= scaleMultiplier
;
1291 if (poiX
== midX
&& poiY
== midY
) {
1292 // We're over the map center symbol, so we would be drawing
1293 // over it even if we increased the scale. Alternate between
1294 // drawing the center symbol or drawing the POI.
1295 if (centerSym
!= SYM_BLANK
&& OSD_ALTERNATING_CHOICES(1000, 2) == 0) {
1301 if (displayReadCharWithAttr(osdDisplayPort
, poiX
, poiY
, &c
, NULL
) && c
!= SYM_BLANK
) {
1302 // Something else written here, increase scale. If the display doesn't support reading
1303 // back characters, we assume there's nothing.
1305 // If we're close to the center, decrease scale. Otherwise increase it.
1306 uint8_t centerDeltaX
= (maxX
- minX
) / (scaleMultiplier
* 2);
1307 uint8_t centerDeltaY
= (maxY
- minY
) / (scaleMultiplier
* 2);
1308 if (poiX
>= midX
- centerDeltaX
&& poiX
<= midX
+ centerDeltaX
&&
1309 poiY
>= midY
- centerDeltaY
&& poiY
<= midY
+ centerDeltaY
&&
1310 scale
> scaleMultiplier
) {
1312 scale
/= scaleMultiplier
;
1314 scale
*= scaleMultiplier
;
1320 // Draw the point on the map
1321 if (poiSymbol
== SYM_ARROW_UP
) {
1322 // Drawing aircraft, rotate
1323 int mapHeading
= osdGetHeadingAngle(DECIDEGREES_TO_DEGREES(osdGetHeading()) - referenceHeading
);
1324 poiSymbol
+= mapHeading
* 2 / 45;
1326 displayWriteChar(osdDisplayPort
, poiX
, poiY
, poiSymbol
);
1328 // Update saved location
1329 *drawn
= OSD_POS(poiX
, poiY
) | OSD_VISIBLE_FLAG
;
1336 // Update global map data for scale and reference
1337 osdMapData
.scale
= scale
;
1338 osdMapData
.referenceSymbol
= referenceSym
;
1341 /* Draws a map with the home in the center and the craft moving around.
1342 * See osdDrawMap() for reference.
1344 static void osdDrawHomeMap(int referenceHeading
, uint8_t referenceSym
, uint16_t *drawn
, uint32_t *usedScale
)
1346 osdDrawMap(referenceHeading
, referenceSym
, SYM_HOME
, GPS_distanceToHome
, GPS_directionToHome
, SYM_ARROW_UP
, drawn
, usedScale
);
1349 /* Draws a map with the aircraft in the center and the home moving around.
1350 * See osdDrawMap() for reference.
1352 static void osdDrawRadar(uint16_t *drawn
, uint32_t *usedScale
)
1354 int16_t reference
= DECIDEGREES_TO_DEGREES(osdGetHeading());
1355 int16_t poiDirection
= osdGetHeadingAngle(GPS_directionToHome
+ 180);
1356 osdDrawMap(reference
, 0, SYM_ARROW_UP
, GPS_distanceToHome
, poiDirection
, SYM_HOME
, drawn
, usedScale
);
1359 static uint16_t crc_accumulate(uint8_t data
, uint16_t crcAccum
)
1362 tmp
= data
^ (uint8_t)(crcAccum
& 0xff);
1364 crcAccum
= (crcAccum
>> 8) ^ (tmp
<< 8) ^ (tmp
<< 3) ^ (tmp
>> 4);
1369 static void osdDisplayTelemetry(void)
1372 uint16_t trk_crc
= 0;
1373 char trk_buffer
[31];
1374 static int16_t trk_elevation
= 127;
1375 static uint16_t trk_bearing
= 0;
1377 if (ARMING_FLAG(ARMED
)) {
1378 if (STATE(GPS_FIX
)){
1379 if (GPS_distanceToHome
> 5) {
1380 trk_bearing
= GPS_directionToHome
;
1381 trk_bearing
+= 360 + 180;
1383 int32_t alt
= CENTIMETERS_TO_METERS(osdGetAltitude());
1384 float at
= atan2(alt
, GPS_distanceToHome
);
1385 trk_elevation
= (float)at
* 57.2957795; // 57.2957795 = 1 rad
1386 trk_elevation
+= 37; // because elevation in telemetry should be from -37 to 90
1387 if (trk_elevation
< 0) {
1394 trk_elevation
= 127;
1398 trk_data
= 0; // bit 0 - packet type 0 = bearing/elevation, 1 = 2 byte data packet
1399 trk_data
= trk_data
| (uint32_t)(0x7F & trk_elevation
) << 1; // bits 1-7 - elevation angle to target. NOTE number is abused. constrained value of -37 to 90 sent as 0 to 127.
1400 trk_data
= trk_data
| (uint32_t)trk_bearing
<< 8; // bits 8-17 - bearing angle to target. 0 = true north. 0 to 360
1401 trk_crc
= crc_accumulate(0xFF & trk_data
, trk_crc
); // CRC First Byte bits 0-7
1402 trk_crc
= crc_accumulate(0xFF & trk_bearing
, trk_crc
); // CRC Second Byte bits 8-15
1403 trk_crc
= crc_accumulate(trk_bearing
>> 8, trk_crc
); // CRC Third Byte bits 16-17
1404 trk_data
= trk_data
| (uint32_t)trk_crc
<< 17; // bits 18-29 CRC & 0x3FFFF
1406 for (uint8_t t_ctr
= 0; t_ctr
< 30; t_ctr
++) { // Prepare screen buffer and write data line.
1407 if (trk_data
& (uint32_t)1 << t_ctr
){
1408 trk_buffer
[29 - t_ctr
] = SYM_TELEMETRY_0
;
1411 trk_buffer
[29 - t_ctr
] = SYM_TELEMETRY_1
;
1415 displayWrite(osdDisplayPort
, 0, 0, trk_buffer
);
1416 if (osdConfig()->telemetry
>1){
1417 displayWrite(osdDisplayPort
, 0, 3, trk_buffer
); // Test display because normal telemetry line is not visible
1422 static void osdFormatPidControllerOutput(char *buff
, const char *label
, const pidController_t
*pidController
, uint8_t scale
, bool showDecimal
) {
1423 strcpy(buff
, label
);
1424 for (uint8_t i
= strlen(label
); i
< 5; ++i
) buff
[i
] = ' ';
1425 uint8_t decimals
= showDecimal
? 1 : 0;
1426 osdFormatCentiNumber(buff
+ 5, pidController
->proportional
* scale
, 0, decimals
, 0, 4);
1428 osdFormatCentiNumber(buff
+ 10, pidController
->integrator
* scale
, 0, decimals
, 0, 4);
1430 osdFormatCentiNumber(buff
+ 15, pidController
->derivative
* scale
, 0, decimals
, 0, 4);
1432 osdFormatCentiNumber(buff
+ 20, pidController
->output_constrained
* scale
, 0, decimals
, 0, 4);
1436 static void osdDisplayBatteryVoltage(uint8_t elemPosX
, uint8_t elemPosY
, uint16_t voltage
, uint8_t digits
, uint8_t decimals
)
1439 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
1441 osdFormatBatteryChargeSymbol(buff
);
1443 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1444 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
1446 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1447 digits
= MIN(digits
, 5);
1448 osdFormatCentiNumber(buff
, voltage
, 0, decimals
, 0, digits
);
1449 buff
[digits
] = SYM_VOLT
;
1450 buff
[digits
+1] = '\0';
1451 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && (getBatteryVoltage() <= getBatteryWarningVoltage()))
1452 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1453 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 1, elemPosY
, buff
, elemAttr
);
1456 static void osdDisplayFlightPIDValues(uint8_t elemPosX
, uint8_t elemPosY
, const char *str
, pidIndex_e pidIndex
, adjustmentFunction_e adjFuncP
, adjustmentFunction_e adjFuncI
, adjustmentFunction_e adjFuncD
, adjustmentFunction_e adjFuncFF
)
1458 textAttributes_t elemAttr
;
1461 const pid8_t
*pid
= &pidBank()->pid
[pidIndex
];
1462 pidType_e pidType
= pidIndexGetType(pidIndex
);
1464 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1466 if (pidType
== PID_TYPE_NONE
) {
1467 // PID is not used in this configuration. Draw dashes.
1468 // XXX: Keep this in sync with the %3d format and spacing used below
1469 displayWrite(osdDisplayPort
, elemPosX
+ 6, elemPosY
, "- - - -");
1473 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1474 tfp_sprintf(buff
, "%3d", pid
->P
);
1475 if ((isAdjustmentFunctionSelected(adjFuncP
)) || (((adjFuncP
== ADJUSTMENT_ROLL_P
) || (adjFuncP
== ADJUSTMENT_PITCH_P
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_P
))))
1476 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1477 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1479 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1480 tfp_sprintf(buff
, "%3d", pid
->I
);
1481 if ((isAdjustmentFunctionSelected(adjFuncI
)) || (((adjFuncI
== ADJUSTMENT_ROLL_I
) || (adjFuncI
== ADJUSTMENT_PITCH_I
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_I
))))
1482 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1483 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 8, elemPosY
, buff
, elemAttr
);
1485 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1486 tfp_sprintf(buff
, "%3d", pid
->D
);
1487 if ((isAdjustmentFunctionSelected(adjFuncD
)) || (((adjFuncD
== ADJUSTMENT_ROLL_D
) || (adjFuncD
== ADJUSTMENT_PITCH_D
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_D
))))
1488 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1489 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 12, elemPosY
, buff
, elemAttr
);
1491 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1492 tfp_sprintf(buff
, "%3d", pid
->FF
);
1493 if ((isAdjustmentFunctionSelected(adjFuncFF
)) || (((adjFuncFF
== ADJUSTMENT_ROLL_FF
) || (adjFuncFF
== ADJUSTMENT_PITCH_FF
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_FF
))))
1494 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1495 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 16, elemPosY
, buff
, elemAttr
);
1498 static void osdDisplayNavPIDValues(uint8_t elemPosX
, uint8_t elemPosY
, const char *str
, pidIndex_e pidIndex
, adjustmentFunction_e adjFuncP
, adjustmentFunction_e adjFuncI
, adjustmentFunction_e adjFuncD
)
1500 textAttributes_t elemAttr
;
1503 const pid8_t
*pid
= &pidBank()->pid
[pidIndex
];
1504 pidType_e pidType
= pidIndexGetType(pidIndex
);
1506 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1508 if (pidType
== PID_TYPE_NONE
) {
1509 // PID is not used in this configuration. Draw dashes.
1510 // XXX: Keep this in sync with the %3d format and spacing used below
1511 displayWrite(osdDisplayPort
, elemPosX
+ 6, elemPosY
, "- - -");
1515 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1516 tfp_sprintf(buff
, "%3d", pid
->P
);
1517 if ((isAdjustmentFunctionSelected(adjFuncP
)) || (((adjFuncP
== ADJUSTMENT_ROLL_P
) || (adjFuncP
== ADJUSTMENT_PITCH_P
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_P
))))
1518 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1519 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1521 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1522 tfp_sprintf(buff
, "%3d", pid
->I
);
1523 if ((isAdjustmentFunctionSelected(adjFuncI
)) || (((adjFuncI
== ADJUSTMENT_ROLL_I
) || (adjFuncI
== ADJUSTMENT_PITCH_I
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_I
))))
1524 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1525 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 8, elemPosY
, buff
, elemAttr
);
1527 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1528 tfp_sprintf(buff
, "%3d", pidType
== PID_TYPE_PIFF
? pid
->FF
: pid
->D
);
1529 if ((isAdjustmentFunctionSelected(adjFuncD
)) || (((adjFuncD
== ADJUSTMENT_ROLL_D
) || (adjFuncD
== ADJUSTMENT_PITCH_D
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_D
))))
1530 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1531 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 12, elemPosY
, buff
, elemAttr
);
1534 static void osdDisplayAdjustableDecimalValue(uint8_t elemPosX
, uint8_t elemPosY
, const char *str
, const uint8_t valueOffset
, const float value
, const uint8_t valueLength
, const uint8_t maxDecimals
, adjustmentFunction_e adjFunc
) {
1536 textAttributes_t elemAttr
;
1537 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1539 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1540 osdFormatCentiNumber(buff
, value
* 100, 0, maxDecimals
, 0, MIN(valueLength
, 8));
1541 if (isAdjustmentFunctionSelected(adjFunc
))
1542 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1543 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ strlen(str
) + 1 + valueOffset
, elemPosY
, buff
, elemAttr
);
1546 int8_t getGeoWaypointNumber(int8_t waypointIndex
)
1548 static int8_t lastWaypointIndex
= 1;
1549 static int8_t geoWaypointIndex
;
1551 if (waypointIndex
!= lastWaypointIndex
) {
1552 lastWaypointIndex
= geoWaypointIndex
= waypointIndex
;
1553 for (uint8_t i
= posControl
.startWpIndex
; i
<= waypointIndex
; i
++) {
1554 if (posControl
.waypointList
[i
].action
== NAV_WP_ACTION_SET_POI
||
1555 posControl
.waypointList
[i
].action
== NAV_WP_ACTION_SET_HEAD
||
1556 posControl
.waypointList
[i
].action
== NAV_WP_ACTION_JUMP
) {
1557 geoWaypointIndex
-= 1;
1562 return geoWaypointIndex
- posControl
.startWpIndex
+ 1;
1565 void osdDisplaySwitchIndicator(const char *swName
, int rcValue
, char *buff
) {
1568 if (osdConfig()->osd_switch_indicators_align_left
) {
1569 for (ptr
= 0; ptr
< constrain(strlen(swName
), 0, OSD_SWITCH_INDICATOR_NAME_LENGTH
); ptr
++) {
1570 buff
[ptr
] = swName
[ptr
];
1573 if ( rcValue
< 1333) {
1574 buff
[ptr
++] = SYM_SWITCH_INDICATOR_LOW
;
1575 } else if ( rcValue
> 1666) {
1576 buff
[ptr
++] = SYM_SWITCH_INDICATOR_HIGH
;
1578 buff
[ptr
++] = SYM_SWITCH_INDICATOR_MID
;
1581 if ( rcValue
< 1333) {
1582 buff
[ptr
++] = SYM_SWITCH_INDICATOR_LOW
;
1583 } else if ( rcValue
> 1666) {
1584 buff
[ptr
++] = SYM_SWITCH_INDICATOR_HIGH
;
1586 buff
[ptr
++] = SYM_SWITCH_INDICATOR_MID
;
1589 for (ptr
= 1; ptr
< constrain(strlen(swName
), 0, OSD_SWITCH_INDICATOR_NAME_LENGTH
) + 1; ptr
++) {
1590 buff
[ptr
] = swName
[ptr
-1];
1599 static bool osdDrawSingleElement(uint8_t item
)
1601 uint16_t pos
= osdLayoutsConfig()->item_pos
[currentLayout
][item
];
1602 if (!OSD_VISIBLE(pos
)) {
1605 uint8_t elemPosX
= OSD_X(pos
);
1606 uint8_t elemPosY
= OSD_Y(pos
);
1607 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
1608 char buff
[32] = {0};
1611 case OSD_RSSI_VALUE
:
1613 uint16_t osdRssi
= osdConvertRSSI();
1615 tfp_sprintf(buff
+ 1, "%2d", osdRssi
);
1616 if (osdRssi
< osdConfig()->rssi_alarm
) {
1617 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1622 case OSD_MAIN_BATT_VOLTAGE
:
1623 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatteryRawVoltage(), (isBfCompatibleVideoSystem(osdConfig()) ? 3 : 2) + osdConfig()->main_voltage_decimals
, osdConfig()->main_voltage_decimals
);
1626 case OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
:
1627 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatterySagCompensatedVoltage(), (isBfCompatibleVideoSystem(osdConfig()) ? 3 : 2) + osdConfig()->main_voltage_decimals
, osdConfig()->main_voltage_decimals
);
1630 case OSD_CURRENT_DRAW
:
1631 osdFormatCentiNumber(buff
, getAmperage(), 0, 2, 0, 3);
1635 uint8_t current_alarm
= osdConfig()->current_alarm
;
1636 if ((current_alarm
> 0) && ((getAmperage() / 100.0f
) > current_alarm
)) {
1637 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1641 case OSD_MAH_DRAWN
: {
1643 if (isBfCompatibleVideoSystem(osdConfig())) {
1644 //BFcompat is unable to work with scaled values and it only has mAh symbol to work with
1645 tfp_sprintf(buff
, "%4d", (int)getMAhDrawn());
1649 if (osdFormatCentiNumber(buff
, getMAhDrawn() * 100, 1000, 0, (osdConfig()->mAh_used_precision
- 2), osdConfig()->mAh_used_precision
)) {
1651 buff
[osdConfig()->mAh_used_precision
] = SYM_AH
;
1654 buff
[osdConfig()->mAh_used_precision
] = SYM_MAH
;
1656 buff
[(osdConfig()->mAh_used_precision
+ 1)] = '\0';
1658 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1662 osdFormatCentiNumber(buff
, getMWhDrawn() / 10, 0, 2, 0, 3);
1663 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1668 case OSD_BATTERY_REMAINING_CAPACITY
:
1670 if (currentBatteryProfile
->capacity
.value
== 0)
1671 tfp_sprintf(buff
, " NA");
1672 else if (!batteryWasFullWhenPluggedIn())
1673 tfp_sprintf(buff
, " NF");
1674 else if (currentBatteryProfile
->capacity
.unit
== BAT_CAPACITY_UNIT_MAH
)
1675 tfp_sprintf(buff
, "%4lu", getBatteryRemainingCapacity());
1676 else // currentBatteryProfile->capacity.unit == BAT_CAPACITY_UNIT_MWH
1677 osdFormatCentiNumber(buff
+ 1, getBatteryRemainingCapacity() / 10, 0, 2, 0, 3);
1679 buff
[4] = currentBatteryProfile
->capacity
.unit
== BAT_CAPACITY_UNIT_MAH
? SYM_MAH
: SYM_WH
;
1682 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && batteryUsesCapacityThresholds() && (getBatteryRemainingCapacity() <= currentBatteryProfile
->capacity
.warning
- currentBatteryProfile
->capacity
.critical
))
1683 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1687 case OSD_BATTERY_REMAINING_PERCENT
:
1688 osdFormatBatteryChargeSymbol(buff
);
1689 tfp_sprintf(buff
+ 1, "%3d%%", calculateBatteryPercentage());
1690 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1693 case OSD_POWER_SUPPLY_IMPEDANCE
:
1694 if (isPowerSupplyImpedanceValid())
1695 tfp_sprintf(buff
, "%3d", getPowerSupplyImpedance());
1697 strcpy(buff
, "---");
1698 buff
[3] = SYM_MILLIOHM
;
1704 buff
[0] = SYM_SAT_L
;
1705 buff
[1] = SYM_SAT_R
;
1706 tfp_sprintf(buff
+ 2, "%2d", gpsSol
.numSat
);
1707 if (!STATE(GPS_FIX
)) {
1708 if (getHwGPSStatus() == HW_SENSOR_UNAVAILABLE
|| getHwGPSStatus() == HW_SENSOR_UNHEALTHY
) {
1709 strcpy(buff
+ 2, "X!");
1711 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1716 osdFormatVelocityStr(buff
, gpsSol
.groundSpeed
, false, false);
1719 case OSD_GPS_MAX_SPEED
:
1720 osdFormatVelocityStr(buff
, stats
.max_speed
, false, true);
1724 osdFormatVelocityStr(buff
, osdGet3DSpeed(), true, false);
1727 case OSD_3D_MAX_SPEED
:
1728 osdFormatVelocityStr(buff
, stats
.max_3D_speed
, true, true);
1731 case OSD_GLIDESLOPE
:
1733 float horizontalSpeed
= gpsSol
.groundSpeed
;
1734 float sinkRate
= -getEstimatedActualVelocity(Z
);
1735 static pt1Filter_t gsFilterState
;
1736 const timeMs_t currentTimeMs
= millis();
1737 static timeMs_t gsUpdatedTimeMs
;
1738 float glideSlope
= horizontalSpeed
/ sinkRate
;
1739 glideSlope
= pt1FilterApply4(&gsFilterState
, isnormal(glideSlope
) ? glideSlope
: 200, 0.5, MS2S(currentTimeMs
- gsUpdatedTimeMs
));
1740 gsUpdatedTimeMs
= currentTimeMs
;
1742 buff
[0] = SYM_GLIDESLOPE
;
1743 if (glideSlope
> 0.0f
&& glideSlope
< 100.0f
) {
1744 osdFormatCentiNumber(buff
+ 1, glideSlope
* 100.0f
, 0, 2, 0, 3);
1746 buff
[1] = buff
[2] = buff
[3] = '-';
1753 osdFormatCoordinate(buff
, SYM_LAT
, gpsSol
.llh
.lat
);
1757 osdFormatCoordinate(buff
, SYM_LON
, gpsSol
.llh
.lon
);
1762 if (STATE(GPS_FIX
) && STATE(GPS_FIX_HOME
) && isImuHeadingValid()) {
1763 if (GPS_distanceToHome
< (navConfig()->general
.min_rth_distance
/ 100) ) {
1764 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
, SYM_HOME_NEAR
);
1768 int16_t panHomeDirOffset
= 0;
1769 if (!(osdConfig()->pan_servo_pwm2centideg
== 0)){
1770 panHomeDirOffset
= osdPanServoHomeDirectionOffset();
1772 int16_t flightDirection
= STATE(AIRPLANE
) ? CENTIDEGREES_TO_DEGREES(posControl
.actualState
.cog
) : DECIDEGREES_TO_DEGREES(osdGetHeading());
1773 int homeDirection
= GPS_directionToHome
- flightDirection
+ panHomeDirOffset
;
1774 osdDrawDirArrow(osdDisplayPort
, osdGetDisplayPortCanvas(), OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), homeDirection
);
1777 // No home or no fix or unknown heading, blink.
1778 // If we're unarmed, show the arrow pointing up so users can see the arrow
1779 // while configuring the OSD. If we're armed, show a '-' indicating that
1780 // we don't know the direction to home.
1781 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1782 displayWriteCharWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, ARMING_FLAG(ARMED
) ? '-' : SYM_ARROW_UP
, elemAttr
);
1787 case OSD_HOME_HEADING_ERROR
:
1790 buff
[1] = SYM_HEADING
;
1792 if (isImuHeadingValid() && navigationPositionEstimateIsHealthy()) {
1793 int16_t h
= lrintf(CENTIDEGREES_TO_DEGREES((float)wrap_18000(DEGREES_TO_CENTIDEGREES((int32_t)GPS_directionToHome
) - (STATE(AIRPLANE
) ? posControl
.actualState
.cog
: DECIDEGREES_TO_CENTIDEGREES((int32_t)osdGetHeading())))));
1794 tfp_sprintf(buff
+ 2, "%4d", h
);
1796 strcpy(buff
+ 2, "----");
1799 buff
[6] = SYM_DEGREES
;
1807 osdFormatDistanceSymbol(&buff
[1], GPS_distanceToHome
* 100, 0);
1808 uint16_t dist_alarm
= osdConfig()->dist_alarm
;
1809 if (dist_alarm
> 0 && GPS_distanceToHome
> dist_alarm
) {
1810 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1816 buff
[0] = SYM_TOTAL
;
1817 osdFormatDistanceSymbol(buff
+ 1, getTotalTravelDistance(), 0);
1820 case OSD_GROUND_COURSE
:
1822 buff
[0] = SYM_GROUND_COURSE
;
1823 if (osdIsHeadingValid()) {
1824 tfp_sprintf(&buff
[1], "%3d", (int16_t)CENTIDEGREES_TO_DEGREES(posControl
.actualState
.cog
));
1826 buff
[1] = buff
[2] = buff
[3] = '-';
1828 buff
[4] = SYM_DEGREES
;
1833 case OSD_COURSE_HOLD_ERROR
:
1835 if (ARMING_FLAG(ARMED
) && !FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) {
1836 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, " ");
1840 buff
[0] = SYM_HEADING
;
1842 if ((!ARMING_FLAG(ARMED
)) || (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
) && isAdjustingPosition())) {
1843 buff
[1] = buff
[2] = buff
[3] = '-';
1844 } else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) {
1845 int16_t herr
= lrintf(CENTIDEGREES_TO_DEGREES((float)navigationGetHeadingError()));
1847 strcpy(buff
+ 1, ">99");
1849 tfp_sprintf(buff
+ 1, "%3d", herr
);
1852 buff
[4] = SYM_DEGREES
;
1857 case OSD_COURSE_HOLD_ADJUSTMENT
:
1859 int16_t heading_adjust
= lrintf(CENTIDEGREES_TO_DEGREES((float)getCruiseHeadingAdjustment()));
1861 if (ARMING_FLAG(ARMED
) && ((!FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) || !(isAdjustingPosition() || isAdjustingHeading() || (heading_adjust
!= 0)))) {
1862 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, " ");
1866 buff
[0] = SYM_HEADING
;
1868 if (!ARMING_FLAG(ARMED
)) {
1869 buff
[1] = buff
[2] = buff
[3] = buff
[4] = '-';
1870 } else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) {
1871 tfp_sprintf(buff
+ 1, "%4d", heading_adjust
);
1874 buff
[5] = SYM_DEGREES
;
1879 case OSD_CROSS_TRACK_ERROR
:
1881 if (isWaypointNavTrackingActive()) {
1882 buff
[0] = SYM_CROSS_TRACK_ERROR
;
1883 osdFormatDistanceSymbol(buff
+ 1, navigationGetCrossTrackError(), 0);
1885 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, " ");
1893 buff
[0] = SYM_HDP_L
;
1894 buff
[1] = SYM_HDP_R
;
1895 int32_t centiHDOP
= 100 * gpsSol
.hdop
/ HDOP_SCALE
;
1896 osdFormatCentiNumber(&buff
[2], centiHDOP
, 0, 1, 0, 2);
1902 static uint16_t drawn
= 0;
1903 static uint32_t scale
= 0;
1904 osdDrawHomeMap(0, 'N', &drawn
, &scale
);
1907 case OSD_MAP_TAKEOFF
:
1909 static uint16_t drawn
= 0;
1910 static uint32_t scale
= 0;
1911 osdDrawHomeMap(CENTIDEGREES_TO_DEGREES(navigationGetHomeHeading()), 'T', &drawn
, &scale
);
1916 static uint16_t drawn
= 0;
1917 static uint32_t scale
= 0;
1918 osdDrawRadar(&drawn
, &scale
);
1925 int32_t alt
= osdGetAltitude();
1926 osdFormatAltitudeSymbol(buff
, alt
);
1927 uint16_t alt_alarm
= osdConfig()->alt_alarm
;
1928 uint16_t neg_alt_alarm
= osdConfig()->neg_alt_alarm
;
1929 if ((alt_alarm
> 0 && CENTIMETERS_TO_METERS(alt
) > alt_alarm
) ||
1930 (neg_alt_alarm
> 0 && alt
< 0 && -CENTIMETERS_TO_METERS(alt
) > neg_alt_alarm
)) {
1932 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1937 case OSD_ALTITUDE_MSL
:
1939 int32_t alt
= osdGetAltitudeMsl();
1940 osdFormatAltitudeSymbol(buff
, alt
);
1944 #ifdef USE_RANGEFINDER
1945 case OSD_RANGEFINDER
:
1947 int32_t range
= rangefinderGetLatestRawAltitude();
1951 osdFormatDistanceSymbol(buff
, range
, 1);
1959 osdFormatOnTime(buff
);
1965 osdFormatFlyTime(buff
, &elemAttr
);
1969 case OSD_ONTIME_FLYTIME
:
1971 if (ARMING_FLAG(ARMED
)) {
1972 osdFormatFlyTime(buff
, &elemAttr
);
1974 osdFormatOnTime(buff
);
1979 case OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
:
1981 /*static int32_t updatedTimeSeconds = 0;*/
1982 static int32_t timeSeconds
= -1;
1983 #if defined(USE_ADC) && defined(USE_GPS)
1984 static timeUs_t updatedTimestamp
= 0;
1985 timeUs_t currentTimeUs
= micros();
1986 if (cmpTimeUs(currentTimeUs
, updatedTimestamp
) >= MS2US(1000)) {
1987 #ifdef USE_WIND_ESTIMATOR
1988 timeSeconds
= calculateRemainingFlightTimeBeforeRTH(osdConfig()->estimations_wind_compensation
);
1990 timeSeconds
= calculateRemainingFlightTimeBeforeRTH(false);
1992 updatedTimestamp
= currentTimeUs
;
1995 if ((!ARMING_FLAG(ARMED
)) || (timeSeconds
== -1)) {
1996 buff
[0] = SYM_FLIGHT_MINS_REMAINING
;
1997 strcpy(buff
+ 1, "--:--");
1998 #if defined(USE_ADC) && defined(USE_GPS)
1999 updatedTimestamp
= 0;
2001 } else if (timeSeconds
== -2) {
2002 // Wind is too strong to come back with cruise throttle
2003 buff
[0] = SYM_FLIGHT_MINS_REMAINING
;
2004 buff
[1] = buff
[2] = buff
[4] = buff
[5] = SYM_WIND_HORIZONTAL
;
2007 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2009 osdFormatTime(buff
, timeSeconds
, SYM_FLIGHT_MINS_REMAINING
, SYM_FLIGHT_HOURS_REMAINING
);
2010 if (timeSeconds
== 0)
2011 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2016 case OSD_REMAINING_DISTANCE_BEFORE_RTH
:;
2017 static int32_t distanceMeters
= -1;
2018 #if defined(USE_ADC) && defined(USE_GPS)
2019 static timeUs_t updatedTimestamp
= 0;
2020 timeUs_t currentTimeUs
= micros();
2021 if (cmpTimeUs(currentTimeUs
, updatedTimestamp
) >= MS2US(1000)) {
2022 #ifdef USE_WIND_ESTIMATOR
2023 distanceMeters
= calculateRemainingDistanceBeforeRTH(osdConfig()->estimations_wind_compensation
);
2025 distanceMeters
= calculateRemainingDistanceBeforeRTH(false);
2027 updatedTimestamp
= currentTimeUs
;
2030 //buff[0] = SYM_TRIP_DIST;
2031 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
, SYM_FLIGHT_DIST_REMAINING
);
2032 if ((!ARMING_FLAG(ARMED
)) || (distanceMeters
== -1)) {
2033 buff
[4] = SYM_BLANK
;
2035 strcpy(buff
+ 1, "---");
2036 } else if (distanceMeters
== -2) {
2037 // Wind is too strong to come back with cruise throttle
2038 buff
[1] = buff
[2] = buff
[3] = SYM_WIND_HORIZONTAL
;
2039 switch ((osd_unit_e
)osdConfig()->units
){
2042 case OSD_UNIT_IMPERIAL
:
2043 buff
[4] = SYM_DIST_MI
;
2045 case OSD_UNIT_METRIC_MPH
:
2047 case OSD_UNIT_METRIC
:
2048 buff
[4] = SYM_DIST_KM
;
2051 buff
[4] = SYM_DIST_NM
;
2055 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2057 osdFormatDistanceSymbol(buff
+ 1, distanceMeters
* 100, 0);
2058 if (distanceMeters
== 0)
2059 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2067 if (FLIGHT_MODE(FAILSAFE_MODE
))
2069 else if (FLIGHT_MODE(MANUAL_MODE
))
2071 else if (FLIGHT_MODE(TURTLE_MODE
))
2073 else if (FLIGHT_MODE(NAV_RTH_MODE
))
2074 p
= isWaypointMissionRTHActive() ? "WRTH" : "RTH ";
2075 else if (FLIGHT_MODE(NAV_POSHOLD_MODE
))
2077 else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
) && FLIGHT_MODE(NAV_ALTHOLD_MODE
))
2079 else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
))
2081 else if (FLIGHT_MODE(NAV_WP_MODE
))
2083 else if (FLIGHT_MODE(NAV_ALTHOLD_MODE
) && navigationRequiresAngleMode()) {
2084 // If navigationRequiresAngleMode() returns false when ALTHOLD is active,
2085 // it means it can be combined with ANGLE, HORIZON, ACRO, etc...
2086 // and its display is handled by OSD_MESSAGES rather than OSD_FLYMODE.
2089 else if (FLIGHT_MODE(ANGLE_MODE
))
2091 else if (FLIGHT_MODE(HORIZON_MODE
))
2094 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, p
);
2098 case OSD_CRAFT_NAME
:
2099 osdFormatCraftName(buff
);
2102 case OSD_PILOT_NAME
:
2103 osdFormatPilotName(buff
);
2106 case OSD_THROTTLE_POS
:
2108 osdFormatThrottlePosition(buff
, false, &elemAttr
);
2112 case OSD_VTX_CHANNEL
:
2114 vtxDeviceOsdInfo_t osdInfo
;
2115 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
2117 tfp_sprintf(buff
, "CH:%c%s:", osdInfo
.bandLetter
, osdInfo
.channelName
);
2118 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2120 tfp_sprintf(buff
, "%c", osdInfo
.powerIndexLetter
);
2121 if (isAdjustmentFunctionSelected(ADJUSTMENT_VTX_POWER_LEVEL
)) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2122 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 6, elemPosY
, buff
, elemAttr
);
2129 vtxDeviceOsdInfo_t osdInfo
;
2130 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
2132 tfp_sprintf(buff
, "%c", SYM_VTX_POWER
);
2133 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2135 tfp_sprintf(buff
, "%c", osdInfo
.powerIndexLetter
);
2136 if (isAdjustmentFunctionSelected(ADJUSTMENT_VTX_POWER_LEVEL
)) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2137 displayWriteWithAttr(osdDisplayPort
, elemPosX
+1, elemPosY
, buff
, elemAttr
);
2141 #if defined(USE_SERIALRX_CRSF)
2142 case OSD_CRSF_RSSI_DBM
:
2144 int16_t rssi
= rxLinkStatistics
.uplinkRSSI
;
2145 buff
[0] = (rxLinkStatistics
.activeAntenna
== 0) ? SYM_RSSI
: SYM_2RSS
; // Separate symbols for each antenna
2147 tfp_sprintf(buff
+ 1, "%4d%c", rssi
, SYM_DBM
);
2149 tfp_sprintf(buff
+ 1, "%3d%c%c", rssi
, SYM_DBM
, ' ');
2151 if (!failsafeIsReceivingRxData()){
2152 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2153 } else if (osdConfig()->rssi_dbm_alarm
&& rssi
< osdConfig()->rssi_dbm_alarm
) {
2154 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2161 int16_t statsLQ
= rxLinkStatistics
.uplinkLQ
;
2162 int16_t scaledLQ
= scaleRange(constrain(statsLQ
, 0, 100), 0, 100, 170, 300);
2163 switch (osdConfig()->crsf_lq_format
) {
2164 case OSD_CRSF_LQ_TYPE1
:
2165 if (!failsafeIsReceivingRxData()) {
2166 tfp_sprintf(buff
+1, "%3d", 0);
2168 tfp_sprintf(buff
+1, "%3d", rxLinkStatistics
.uplinkLQ
);
2171 case OSD_CRSF_LQ_TYPE2
:
2172 if (!failsafeIsReceivingRxData()) {
2173 tfp_sprintf(buff
+1, "%s:%3d", " ", 0);
2175 tfp_sprintf(buff
+1, "%d:%3d", rxLinkStatistics
.rfMode
, rxLinkStatistics
.uplinkLQ
);
2178 case OSD_CRSF_LQ_TYPE3
:
2179 if (!failsafeIsReceivingRxData()) {
2180 tfp_sprintf(buff
+1, "%3d", 0);
2182 tfp_sprintf(buff
+1, "%3d", rxLinkStatistics
.rfMode
>= 2 ? scaledLQ
: rxLinkStatistics
.uplinkLQ
);
2186 if (!failsafeIsReceivingRxData()) {
2187 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2188 } else if (rxLinkStatistics
.uplinkLQ
< osdConfig()->link_quality_alarm
) {
2189 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2194 case OSD_CRSF_SNR_DB
:
2196 static pt1Filter_t snrFilterState
;
2197 static timeMs_t snrUpdated
= 0;
2198 int8_t snrFiltered
= pt1FilterApply4(&snrFilterState
, rxLinkStatistics
.uplinkSNR
, 0.5f
, MS2S(millis() - snrUpdated
));
2199 snrUpdated
= millis();
2201 const char* showsnr
= "-20";
2202 const char* hidesnr
= " ";
2203 if (snrFiltered
> osdConfig()->snr_alarm
) {
2206 tfp_sprintf(buff
+ 1, "%s%c", showsnr
, SYM_DB
);
2208 buff
[0] = SYM_BLANK
;
2209 tfp_sprintf(buff
+ 1, "%s%c", hidesnr
, SYM_BLANK
);
2211 } else if (snrFiltered
<= osdConfig()->snr_alarm
) {
2213 if (snrFiltered
<= -10) {
2214 tfp_sprintf(buff
+ 1, "%3d%c", snrFiltered
, SYM_DB
);
2216 tfp_sprintf(buff
+ 1, "%2d%c%c", snrFiltered
, SYM_DB
, ' ');
2222 case OSD_CRSF_TX_POWER
:
2224 if (!failsafeIsReceivingRxData())
2225 tfp_sprintf(buff
, "%s%c", " ", SYM_BLANK
);
2227 tfp_sprintf(buff
, "%4d%c", rxLinkStatistics
.uplinkTXPower
, SYM_MW
);
2232 case OSD_CROSSHAIRS
: // Hud is a sub-element of the crosshair
2234 osdCrosshairPosition(&elemPosX
, &elemPosY
);
2235 osdHudDrawCrosshair(osdGetDisplayPortCanvas(), elemPosX
, elemPosY
);
2237 if (osdConfig()->hud_homing
&& STATE(GPS_FIX
) && STATE(GPS_FIX_HOME
) && isImuHeadingValid()) {
2238 osdHudDrawHoming(elemPosX
, elemPosY
);
2241 if (STATE(GPS_FIX
) && isImuHeadingValid()) {
2243 if (osdConfig()->hud_homepoint
|| osdConfig()->hud_radar_disp
> 0 || osdConfig()->hud_wp_disp
> 0) {
2247 // -------- POI : Home point
2249 if (osdConfig()->hud_homepoint
) { // Display the home point (H)
2250 osdHudDrawPoi(GPS_distanceToHome
, GPS_directionToHome
, -osdGetAltitude() / 100, 0, SYM_HOME
, 0 , 0);
2253 // -------- POI : Nearby aircrafts from ESP32 radar
2255 if (osdConfig()->hud_radar_disp
> 0) { // Display the POI from the radar
2256 for (uint8_t i
= 0; i
< osdConfig()->hud_radar_disp
; i
++) {
2257 if (radar_pois
[i
].gps
.lat
!= 0 && radar_pois
[i
].gps
.lon
!= 0 && radar_pois
[i
].state
< 2) { // state 2 means POI has been lost and must be skipped
2259 geoConvertGeodeticToLocal(&poi
, &posControl
.gpsOrigin
, &radar_pois
[i
].gps
, GEO_ALT_RELATIVE
);
2260 radar_pois
[i
].distance
= calculateDistanceToDestination(&poi
) / 100; // In meters
2262 if (radar_pois
[i
].distance
>= osdConfig()->hud_radar_range_min
&& radar_pois
[i
].distance
<= osdConfig()->hud_radar_range_max
) {
2263 radar_pois
[i
].direction
= calculateBearingToDestination(&poi
) / 100; // In °
2264 radar_pois
[i
].altitude
= (radar_pois
[i
].gps
.alt
- osdGetAltitudeMsl()) / 100;
2265 osdHudDrawPoi(radar_pois
[i
].distance
, osdGetHeadingAngle(radar_pois
[i
].direction
), radar_pois
[i
].altitude
, 1, 65 + i
, radar_pois
[i
].heading
, radar_pois
[i
].lq
);
2271 // -------- POI : Next waypoints from navigation
2273 if (osdConfig()->hud_wp_disp
> 0 && posControl
.waypointListValid
&& posControl
.waypointCount
> 0) { // Display the next waypoints
2277 for (int i
= osdConfig()->hud_wp_disp
- 1; i
>= 0 ; i
--) { // Display in reverse order so the next WP is always written on top
2278 j
= posControl
.activeWaypointIndex
+ i
;
2279 if (j
> posControl
.startWpIndex
+ posControl
.waypointCount
- 1) { // limit to max WP index for mission
2282 if (posControl
.waypointList
[j
].lat
!= 0 && posControl
.waypointList
[j
].lon
!= 0) {
2283 wp2
.lat
= posControl
.waypointList
[j
].lat
;
2284 wp2
.lon
= posControl
.waypointList
[j
].lon
;
2285 wp2
.alt
= posControl
.waypointList
[j
].alt
;
2287 geoConvertGeodeticToLocal(&poi
, &posControl
.gpsOrigin
, &wp2
, waypointMissionAltConvMode(posControl
.waypointList
[j
].p3
));
2288 int32_t altConvModeAltitude
= waypointMissionAltConvMode(posControl
.waypointList
[j
].p3
) == GEO_ALT_ABSOLUTE
? osdGetAltitudeMsl() : osdGetAltitude();
2289 j
= getGeoWaypointNumber(j
);
2290 while (j
> 9) j
-= 10; // Only the last digit displayed if WP>=10, no room for more (48 = ascii 0)
2291 osdHudDrawPoi(calculateDistanceToDestination(&poi
) / 100, osdGetHeadingAngle(calculateBearingToDestination(&poi
) / 100), (posControl
.waypointList
[j
].alt
- altConvModeAltitude
)/ 100, 2, SYM_WAYPOINT
, 48 + j
, i
);
2300 case OSD_ATTITUDE_ROLL
:
2301 buff
[0] = SYM_ROLL_LEVEL
;
2302 if (ABS(attitude
.values
.roll
) >= 1)
2303 buff
[0] += (attitude
.values
.roll
< 0 ? -1 : 1);
2304 osdFormatCentiNumber(buff
+ 1, DECIDEGREES_TO_CENTIDEGREES(ABS(attitude
.values
.roll
)), 0, 1, 0, 3);
2307 case OSD_ATTITUDE_PITCH
:
2308 if (ABS(attitude
.values
.pitch
) < 1)
2310 else if (attitude
.values
.pitch
> 0)
2311 buff
[0] = SYM_PITCH_DOWN
;
2312 else if (attitude
.values
.pitch
< 0)
2313 buff
[0] = SYM_PITCH_UP
;
2314 osdFormatCentiNumber(buff
+ 1, DECIDEGREES_TO_CENTIDEGREES(ABS(attitude
.values
.pitch
)), 0, 1, 0, 3);
2317 case OSD_ARTIFICIAL_HORIZON
:
2319 float rollAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.roll
);
2320 float pitchAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.pitch
);
2322 pitchAngle
-= osdConfig()->ahi_camera_uptilt_comp
? DEGREES_TO_RADIANS(osdConfig()->camera_uptilt
) : 0;
2323 pitchAngle
+= DEGREES_TO_RADIANS(getFixedWingLevelTrim());
2324 if (osdConfig()->ahi_reverse_roll
) {
2325 rollAngle
= -rollAngle
;
2327 osdDrawArtificialHorizon(osdDisplayPort
, osdGetDisplayPortCanvas(),
2328 OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), rollAngle
, pitchAngle
);
2329 osdDrawSingleElement(OSD_HORIZON_SIDEBARS
);
2330 osdDrawSingleElement(OSD_CROSSHAIRS
);
2335 case OSD_HORIZON_SIDEBARS
:
2337 osdDrawSidebars(osdDisplayPort
, osdGetDisplayPortCanvas());
2341 #if defined(USE_BARO) || defined(USE_GPS)
2344 float zvel
= getEstimatedActualVelocity(Z
);
2345 osdDrawVario(osdDisplayPort
, osdGetDisplayPortCanvas(), OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), zvel
);
2351 int16_t value
= getEstimatedActualVelocity(Z
);
2353 switch ((osd_unit_e
)osdConfig()->units
) {
2356 case OSD_UNIT_IMPERIAL
:
2357 // Convert to centifeet/s
2358 value
= CENTIMETERS_TO_CENTIFEET(value
);
2362 // Convert to centi-100feet/min
2363 value
= CENTIMETERS_TO_FEET(value
* 60);
2367 case OSD_UNIT_METRIC_MPH
:
2369 case OSD_UNIT_METRIC
:
2375 osdFormatCentiNumber(buff
, value
, 0, 1, 0, 3);
2380 case OSD_CLIMB_EFFICIENCY
:
2382 // amperage is in centi amps (10mA), vertical speed is in cms/s. We want
2383 // Ah/dist only to show when vertical speed > 1m/s.
2384 static pt1Filter_t veFilterState
;
2385 static timeUs_t vEfficiencyUpdated
= 0;
2387 timeUs_t currentTimeUs
= micros();
2388 timeDelta_t vEfficiencyTimeDelta
= cmpTimeUs(currentTimeUs
, vEfficiencyUpdated
);
2389 if (getEstimatedActualVelocity(Z
) > 0) {
2390 if (vEfficiencyTimeDelta
>= EFFICIENCY_UPDATE_INTERVAL
) {
2391 // Centiamps (kept for osdFormatCentiNumber) / m/s - Will appear as A / m/s in OSD
2392 value
= pt1FilterApply4(&veFilterState
, (float)getAmperage() / (getEstimatedActualVelocity(Z
) / 100.0f
), 1, US2S(vEfficiencyTimeDelta
));
2394 vEfficiencyUpdated
= currentTimeUs
;
2396 value
= veFilterState
.state
;
2399 bool efficiencyValid
= (value
> 0) && (getEstimatedActualVelocity(Z
) > 100);
2400 switch (osdConfig()->units
) {
2405 case OSD_UNIT_IMPERIAL
:
2407 if (efficiencyValid
) {
2408 osdFormatCentiNumber(buff
, (value
* METERS_PER_FOOT
), 1, 2, 2, 3);
2409 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_AH_V_FT_0
, SYM_AH_V_FT_1
);
2411 buff
[0] = buff
[1] = buff
[2] = '-';
2412 buff
[3] = SYM_AH_V_FT_0
;
2413 buff
[4] = SYM_AH_V_FT_1
;
2417 case OSD_UNIT_METRIC_MPH
:
2419 case OSD_UNIT_METRIC
:
2421 if (efficiencyValid
) {
2422 osdFormatCentiNumber(buff
, value
, 1, 2, 2, 3);
2423 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_AH_V_M_0
, SYM_AH_V_M_1
);
2425 buff
[0] = buff
[1] = buff
[2] = '-';
2426 buff
[3] = SYM_AH_V_M_0
;
2427 buff
[4] = SYM_AH_V_M_1
;
2434 case OSD_GLIDE_TIME_REMAINING
:
2436 uint16_t glideTime
= osdGetRemainingGlideTime();
2437 buff
[0] = SYM_GLIDE_MINS
;
2438 if (glideTime
> 0) {
2439 // Maximum value we can show in minutes is 99 minutes and 59 seconds. It is extremely unlikely that glide
2440 // time will be longer than 99 minutes. If it is, it will show 99:^^
2441 if (glideTime
> (99 * 60) + 59) {
2442 tfp_sprintf(buff
+ 1, "%02d:", (int)(glideTime
/ 60));
2443 buff
[4] = SYM_DIRECTION
;
2444 buff
[5] = SYM_DIRECTION
;
2446 tfp_sprintf(buff
+ 1, "%02d:%02d", (int)(glideTime
/ 60), (int)(glideTime
% 60));
2449 tfp_sprintf(buff
+ 1, "%s", "--:--");
2454 case OSD_GLIDE_RANGE
:
2456 uint16_t glideSeconds
= osdGetRemainingGlideTime();
2457 buff
[0] = SYM_GLIDE_DIST
;
2458 if (glideSeconds
> 0) {
2459 uint32_t glideRangeCM
= glideSeconds
* gpsSol
.groundSpeed
;
2460 osdFormatDistanceSymbol(buff
+ 1, glideRangeCM
, 0);
2462 tfp_sprintf(buff
+ 1, "%s%c", "---", SYM_BLANK
);
2469 case OSD_SWITCH_INDICATOR_0
:
2470 osdDisplaySwitchIndicator(osdConfig()->osd_switch_indicator0_name
, rxGetChannelValue(osdConfig()->osd_switch_indicator0_channel
- 1), buff
);
2473 case OSD_SWITCH_INDICATOR_1
:
2474 osdDisplaySwitchIndicator(osdConfig()->osd_switch_indicator1_name
, rxGetChannelValue(osdConfig()->osd_switch_indicator1_channel
- 1), buff
);
2477 case OSD_SWITCH_INDICATOR_2
:
2478 osdDisplaySwitchIndicator(osdConfig()->osd_switch_indicator2_name
, rxGetChannelValue(osdConfig()->osd_switch_indicator2_channel
- 1), buff
);
2481 case OSD_SWITCH_INDICATOR_3
:
2482 osdDisplaySwitchIndicator(osdConfig()->osd_switch_indicator3_name
, rxGetChannelValue(osdConfig()->osd_switch_indicator3_channel
- 1), buff
);
2485 case OSD_ACTIVE_PROFILE
:
2486 tfp_sprintf(buff
, "%c%u", SYM_PROFILE
, (getConfigProfile() + 1));
2487 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2491 osdDisplayFlightPIDValues(elemPosX
, elemPosY
, "ROL", PID_ROLL
, ADJUSTMENT_ROLL_P
, ADJUSTMENT_ROLL_I
, ADJUSTMENT_ROLL_D
, ADJUSTMENT_ROLL_FF
);
2494 case OSD_PITCH_PIDS
:
2495 osdDisplayFlightPIDValues(elemPosX
, elemPosY
, "PIT", PID_PITCH
, ADJUSTMENT_PITCH_P
, ADJUSTMENT_PITCH_I
, ADJUSTMENT_PITCH_D
, ADJUSTMENT_PITCH_FF
);
2499 osdDisplayFlightPIDValues(elemPosX
, elemPosY
, "YAW", PID_YAW
, ADJUSTMENT_YAW_P
, ADJUSTMENT_YAW_I
, ADJUSTMENT_YAW_D
, ADJUSTMENT_YAW_FF
);
2502 case OSD_LEVEL_PIDS
:
2503 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "LEV", PID_LEVEL
, ADJUSTMENT_LEVEL_P
, ADJUSTMENT_LEVEL_I
, ADJUSTMENT_LEVEL_D
);
2506 case OSD_POS_XY_PIDS
:
2507 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "PXY", PID_POS_XY
, ADJUSTMENT_POS_XY_P
, ADJUSTMENT_POS_XY_I
, ADJUSTMENT_POS_XY_D
);
2510 case OSD_POS_Z_PIDS
:
2511 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "PZ", PID_POS_Z
, ADJUSTMENT_POS_Z_P
, ADJUSTMENT_POS_Z_I
, ADJUSTMENT_POS_Z_D
);
2514 case OSD_VEL_XY_PIDS
:
2515 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "VXY", PID_VEL_XY
, ADJUSTMENT_VEL_XY_P
, ADJUSTMENT_VEL_XY_I
, ADJUSTMENT_VEL_XY_D
);
2518 case OSD_VEL_Z_PIDS
:
2519 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "VZ", PID_VEL_Z
, ADJUSTMENT_VEL_Z_P
, ADJUSTMENT_VEL_Z_I
, ADJUSTMENT_VEL_Z_D
);
2523 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "HP", 0, pidBank()->pid
[PID_HEADING
].P
, 3, 0, ADJUSTMENT_HEADING_P
);
2526 case OSD_BOARD_ALIGN_ROLL
:
2527 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "AR", 0, DECIDEGREES_TO_DEGREES((float)boardAlignment()->rollDeciDegrees
), 4, 1, ADJUSTMENT_ROLL_BOARD_ALIGNMENT
);
2530 case OSD_BOARD_ALIGN_PITCH
:
2531 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "AP", 0, DECIDEGREES_TO_DEGREES((float)boardAlignment()->pitchDeciDegrees
), 4, 1, ADJUSTMENT_PITCH_BOARD_ALIGNMENT
);
2535 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "EXP", 0, currentControlRateProfile
->stabilized
.rcExpo8
, 3, 0, ADJUSTMENT_RC_EXPO
);
2538 case OSD_RC_YAW_EXPO
:
2539 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "YEX", 0, currentControlRateProfile
->stabilized
.rcYawExpo8
, 3, 0, ADJUSTMENT_RC_YAW_EXPO
);
2542 case OSD_THROTTLE_EXPO
:
2543 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "TEX", 0, currentControlRateProfile
->throttle
.rcExpo8
, 3, 0, ADJUSTMENT_THROTTLE_EXPO
);
2546 case OSD_PITCH_RATE
:
2547 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "SPR");
2549 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2550 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->stabilized
.rates
[FD_PITCH
]);
2551 if (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_RATE
))
2552 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2553 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2557 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "SRR");
2559 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2560 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->stabilized
.rates
[FD_ROLL
]);
2561 if (isAdjustmentFunctionSelected(ADJUSTMENT_ROLL_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_RATE
))
2562 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2563 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2567 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "SYR", 0, currentControlRateProfile
->stabilized
.rates
[FD_YAW
], 3, 0, ADJUSTMENT_YAW_RATE
);
2570 case OSD_MANUAL_RC_EXPO
:
2571 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MEX", 0, currentControlRateProfile
->manual
.rcExpo8
, 3, 0, ADJUSTMENT_MANUAL_RC_EXPO
);
2574 case OSD_MANUAL_RC_YAW_EXPO
:
2575 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MYX", 0, currentControlRateProfile
->manual
.rcYawExpo8
, 3, 0, ADJUSTMENT_MANUAL_RC_YAW_EXPO
);
2578 case OSD_MANUAL_PITCH_RATE
:
2579 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "MPR");
2581 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2582 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->manual
.rates
[FD_PITCH
]);
2583 if (isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_ROLL_RATE
))
2584 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2585 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2588 case OSD_MANUAL_ROLL_RATE
:
2589 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "MRR");
2591 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2592 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->manual
.rates
[FD_ROLL
]);
2593 if (isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_ROLL_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_ROLL_RATE
))
2594 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2595 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2598 case OSD_MANUAL_YAW_RATE
:
2599 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MYR", 0, currentControlRateProfile
->stabilized
.rates
[FD_YAW
], 3, 0, ADJUSTMENT_YAW_RATE
);
2602 case OSD_NAV_FW_CRUISE_THR
:
2603 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "CRZ", 0, currentBatteryProfile
->nav
.fw
.cruise_throttle
, 4, 0, ADJUSTMENT_NAV_FW_CRUISE_THR
);
2606 case OSD_NAV_FW_PITCH2THR
:
2607 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "P2T", 0, currentBatteryProfile
->nav
.fw
.pitch_to_throttle
, 3, 0, ADJUSTMENT_NAV_FW_PITCH2THR
);
2610 case OSD_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
:
2611 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "0TP", 0, (float)navConfig()->fw
.minThrottleDownPitchAngle
/ 10, 3, 1, ADJUSTMENT_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
);
2614 case OSD_FW_ALT_PID_OUTPUTS
:
2616 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2617 osdFormatPidControllerOutput(buff
, "PZO", &nav_pids
->fw_alt
, 10, true); // display requested pitch degrees
2621 case OSD_FW_POS_PID_OUTPUTS
:
2623 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers(); // display requested roll degrees
2624 osdFormatPidControllerOutput(buff
, "PXYO", &nav_pids
->fw_nav
, 1, true);
2628 case OSD_MC_VEL_Z_PID_OUTPUTS
:
2630 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2631 osdFormatPidControllerOutput(buff
, "VZO", &nav_pids
->vel
[Z
], 100, false); // display throttle adjustment µs
2635 case OSD_MC_VEL_X_PID_OUTPUTS
:
2637 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2638 osdFormatPidControllerOutput(buff
, "VXO", &nav_pids
->vel
[X
], 100, false); // display requested acceleration cm/s^2
2642 case OSD_MC_VEL_Y_PID_OUTPUTS
:
2644 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2645 osdFormatPidControllerOutput(buff
, "VYO", &nav_pids
->vel
[Y
], 100, false); // display requested acceleration cm/s^2
2649 case OSD_MC_POS_XYZ_P_OUTPUTS
:
2651 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2652 strcpy(buff
, "POSO ");
2653 // display requested velocity cm/s
2654 tfp_sprintf(buff
+ 5, "%4d", (int)lrintf(nav_pids
->pos
[X
].output_constrained
* 100));
2656 tfp_sprintf(buff
+ 10, "%4d", (int)lrintf(nav_pids
->pos
[Y
].output_constrained
* 100));
2658 tfp_sprintf(buff
+ 15, "%4d", (int)lrintf(nav_pids
->pos
[Z
].output_constrained
* 100));
2665 bool kiloWatt
= osdFormatCentiNumber(buff
, getPower(), 1000, 2, 2, 3);
2666 buff
[3] = kiloWatt
? SYM_KILOWATT
: SYM_WATT
;
2669 uint8_t current_alarm
= osdConfig()->current_alarm
;
2670 if ((current_alarm
> 0) && ((getAmperage() / 100.0f
) > current_alarm
)) {
2671 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2679 const float airspeed_estimate
= getAirspeedEstimate();
2681 osdFormatVelocityStr(buff
+ 1, airspeed_estimate
, false, false);
2683 if ((osdConfig()->airspeed_alarm_min
!= 0 && airspeed_estimate
< osdConfig()->airspeed_alarm_min
) ||
2684 (osdConfig()->airspeed_alarm_max
!= 0 && airspeed_estimate
> osdConfig()->airspeed_alarm_max
)) {
2685 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2693 case OSD_AIR_MAX_SPEED
:
2698 osdFormatVelocityStr(buff
+ 2, stats
.max_air_speed
, false, false);
2707 // RTC not configured will show 00:00
2708 dateTime_t dateTime
;
2709 rtcGetDateTimeLocal(&dateTime
);
2710 buff
[0] = SYM_CLOCK
;
2711 tfp_sprintf(buff
+ 1, "%02u:%02u:%02u", dateTime
.hours
, dateTime
.minutes
, dateTime
.seconds
);
2717 elemAttr
= osdGetSystemMessage(buff
, OSD_MESSAGE_LENGTH
, true);
2723 tfp_sprintf(buff
, "INAV %s", FC_VERSION_STRING
);
2724 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2728 case OSD_MAIN_BATT_CELL_VOLTAGE
:
2730 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatteryRawAverageCellVoltage(), (isBfCompatibleVideoSystem(osdConfig()) ? 4 : 3), 2);
2734 case OSD_MAIN_BATT_SAG_COMPENSATED_CELL_VOLTAGE
:
2736 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatterySagCompensatedAverageCellVoltage(), (isBfCompatibleVideoSystem(osdConfig()) ? 4 : 3), 2);
2740 case OSD_THROTTLE_POS_AUTO_THR
:
2742 osdFormatThrottlePosition(buff
, true, &elemAttr
);
2748 buff
[0] = SYM_HEADING
;
2749 if (osdIsHeadingValid()) {
2750 int16_t h
= DECIDEGREES_TO_DEGREES(osdGetHeading());
2754 tfp_sprintf(&buff
[1], "%3d", h
);
2756 buff
[1] = buff
[2] = buff
[3] = '-';
2758 buff
[4] = SYM_DEGREES
;
2763 case OSD_HEADING_GRAPH
:
2765 if (osdIsHeadingValid()) {
2766 osdDrawHeadingGraph(osdDisplayPort
, osdGetDisplayPortCanvas(), OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), osdGetHeading());
2769 buff
[0] = buff
[2] = buff
[4] = buff
[6] = buff
[8] = SYM_HEADING_LINE
;
2770 buff
[1] = buff
[3] = buff
[5] = buff
[7] = SYM_HEADING_DIVIDED_LINE
;
2771 buff
[OSD_HEADING_GRAPH_WIDTH
] = '\0';
2776 case OSD_EFFICIENCY_MAH_PER_KM
:
2778 // amperage is in centi amps, speed is in cms/s. We want
2779 // mah/km. Only show when ground speed > 1m/s.
2780 static pt1Filter_t eFilterState
;
2781 static timeUs_t efficiencyUpdated
= 0;
2783 bool moreThanAh
= false;
2784 timeUs_t currentTimeUs
= micros();
2785 timeDelta_t efficiencyTimeDelta
= cmpTimeUs(currentTimeUs
, efficiencyUpdated
);
2786 if (STATE(GPS_FIX
) && gpsSol
.groundSpeed
> 0) {
2787 if (efficiencyTimeDelta
>= EFFICIENCY_UPDATE_INTERVAL
) {
2788 value
= pt1FilterApply4(&eFilterState
, ((float)getAmperage() / gpsSol
.groundSpeed
) / 0.0036f
,
2789 1, US2S(efficiencyTimeDelta
));
2791 efficiencyUpdated
= currentTimeUs
;
2793 value
= eFilterState
.state
;
2796 bool efficiencyValid
= (value
> 0) && (gpsSol
.groundSpeed
> 100);
2797 switch (osdConfig()->units
) {
2800 case OSD_UNIT_IMPERIAL
:
2801 moreThanAh
= osdFormatCentiNumber(buff
, value
* METERS_PER_MILE
/ 10, 1000, 0, 2, 3);
2803 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_MI_0
, SYM_MAH_MI_1
);
2805 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_MI
);
2807 if (!efficiencyValid
) {
2808 buff
[0] = buff
[1] = buff
[2] = '-';
2809 buff
[3] = SYM_MAH_MI_0
;
2810 buff
[4] = SYM_MAH_MI_1
;
2815 moreThanAh
= osdFormatCentiNumber(buff
, value
* METERS_PER_NAUTICALMILE
/ 10, 1000, 0, 2, 3);
2817 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_NM_0
, SYM_MAH_NM_1
);
2819 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_NM
);
2821 if (!efficiencyValid
) {
2822 buff
[0] = buff
[1] = buff
[2] = '-';
2823 buff
[3] = SYM_MAH_NM_0
;
2824 buff
[4] = SYM_MAH_NM_1
;
2828 case OSD_UNIT_METRIC_MPH
:
2830 case OSD_UNIT_METRIC
:
2831 moreThanAh
= osdFormatCentiNumber(buff
, value
* 100, 1000, 0, 2, 3);
2833 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_KM_0
, SYM_MAH_KM_1
);
2835 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_KM
);
2837 if (!efficiencyValid
) {
2838 buff
[0] = buff
[1] = buff
[2] = '-';
2839 buff
[3] = SYM_MAH_KM_0
;
2840 buff
[4] = SYM_MAH_KM_1
;
2848 case OSD_EFFICIENCY_WH_PER_KM
:
2850 // amperage is in centi amps, speed is in cms/s. We want
2851 // mWh/km. Only show when ground speed > 1m/s.
2852 static pt1Filter_t eFilterState
;
2853 static timeUs_t efficiencyUpdated
= 0;
2855 timeUs_t currentTimeUs
= micros();
2856 timeDelta_t efficiencyTimeDelta
= cmpTimeUs(currentTimeUs
, efficiencyUpdated
);
2857 if (STATE(GPS_FIX
) && gpsSol
.groundSpeed
> 0) {
2858 if (efficiencyTimeDelta
>= EFFICIENCY_UPDATE_INTERVAL
) {
2859 value
= pt1FilterApply4(&eFilterState
, ((float)getPower() / gpsSol
.groundSpeed
) / 0.0036f
,
2860 1, US2S(efficiencyTimeDelta
));
2862 efficiencyUpdated
= currentTimeUs
;
2864 value
= eFilterState
.state
;
2867 bool efficiencyValid
= (value
> 0) && (gpsSol
.groundSpeed
> 100);
2868 switch (osdConfig()->units
) {
2871 case OSD_UNIT_IMPERIAL
:
2872 osdFormatCentiNumber(buff
, value
* METERS_PER_MILE
/ 10000, 0, 2, 0, 3);
2873 buff
[3] = SYM_WH_MI
;
2876 osdFormatCentiNumber(buff
, value
* METERS_PER_NAUTICALMILE
/ 10000, 0, 2, 0, 3);
2877 buff
[3] = SYM_WH_NM
;
2879 case OSD_UNIT_METRIC_MPH
:
2881 case OSD_UNIT_METRIC
:
2882 osdFormatCentiNumber(buff
, value
/ 10, 0, 2, 0, 3);
2883 buff
[3] = SYM_WH_KM
;
2887 if (!efficiencyValid
) {
2888 buff
[0] = buff
[1] = buff
[2] = '-';
2895 buff
[0] = SYM_GFORCE
;
2896 osdFormatCentiNumber(buff
+ 1, GForce
, 0, 2, 0, 3);
2897 if (GForce
> osdConfig()->gforce_alarm
* 100) {
2898 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2907 float GForceValue
= GForceAxis
[item
- OSD_GFORCE_X
];
2908 buff
[0] = SYM_GFORCE_X
+ item
- OSD_GFORCE_X
;
2909 osdFormatCentiNumber(buff
+ 1, GForceValue
, 0, 2, 0, 4);
2910 if ((GForceValue
< osdConfig()->gforce_axis_alarm_min
* 100) || (GForceValue
> osdConfig()->gforce_axis_alarm_max
* 100)) {
2911 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2918 * Longest representable string is -2147483648 does not fit in the screen.
2919 * Only 7 digits for negative and 8 digits for positive values allowed
2921 for (uint8_t bufferIndex
= 0; bufferIndex
< DEBUG32_VALUE_COUNT
; ++elemPosY
, bufferIndex
+= 2) {
2924 "[%u]=%8ld [%u]=%8ld",
2926 constrain(debug
[bufferIndex
], -9999999, 99999999),
2928 constrain(debug
[bufferIndex
+1], -9999999, 99999999)
2930 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2935 case OSD_IMU_TEMPERATURE
:
2937 int16_t temperature
;
2938 const bool valid
= getIMUTemperature(&temperature
);
2939 osdDisplayTemperature(elemPosX
, elemPosY
, SYM_IMU_TEMP
, NULL
, valid
, temperature
, osdConfig()->imu_temp_alarm_min
, osdConfig()->imu_temp_alarm_max
);
2943 case OSD_BARO_TEMPERATURE
:
2945 int16_t temperature
;
2946 const bool valid
= getBaroTemperature(&temperature
);
2947 osdDisplayTemperature(elemPosX
, elemPosY
, SYM_BARO_TEMP
, NULL
, valid
, temperature
, osdConfig()->imu_temp_alarm_min
, osdConfig()->imu_temp_alarm_max
);
2951 #ifdef USE_TEMPERATURE_SENSOR
2952 case OSD_TEMP_SENSOR_0_TEMPERATURE
:
2953 case OSD_TEMP_SENSOR_1_TEMPERATURE
:
2954 case OSD_TEMP_SENSOR_2_TEMPERATURE
:
2955 case OSD_TEMP_SENSOR_3_TEMPERATURE
:
2956 case OSD_TEMP_SENSOR_4_TEMPERATURE
:
2957 case OSD_TEMP_SENSOR_5_TEMPERATURE
:
2958 case OSD_TEMP_SENSOR_6_TEMPERATURE
:
2959 case OSD_TEMP_SENSOR_7_TEMPERATURE
:
2961 osdDisplayTemperatureSensor(elemPosX
, elemPosY
, item
- OSD_TEMP_SENSOR_0_TEMPERATURE
);
2964 #endif /* ifdef USE_TEMPERATURE_SENSOR */
2966 case OSD_WIND_SPEED_HORIZONTAL
:
2967 #ifdef USE_WIND_ESTIMATOR
2969 bool valid
= isEstimatedWindSpeedValid();
2970 float horizontalWindSpeed
;
2972 horizontalWindSpeed
= getEstimatedHorizontalWindSpeed(&angle
);
2973 int16_t windDirection
= osdGetHeadingAngle( CENTIDEGREES_TO_DEGREES((int)angle
) - DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
) + 22);
2974 buff
[0] = SYM_WIND_HORIZONTAL
;
2975 buff
[1] = SYM_DIRECTION
+ (windDirection
*2 / 90);
2976 osdFormatWindSpeedStr(buff
+ 2, horizontalWindSpeed
, valid
);
2983 case OSD_WIND_SPEED_VERTICAL
:
2984 #ifdef USE_WIND_ESTIMATOR
2986 buff
[0] = SYM_WIND_VERTICAL
;
2987 buff
[1] = SYM_BLANK
;
2988 bool valid
= isEstimatedWindSpeedValid();
2989 float verticalWindSpeed
;
2990 verticalWindSpeed
= -getEstimatedWindSpeed(Z
); //from NED to NEU
2991 if (verticalWindSpeed
< 0) {
2992 buff
[1] = SYM_AH_DECORATION_DOWN
;
2993 verticalWindSpeed
= -verticalWindSpeed
;
2995 buff
[1] = SYM_AH_DECORATION_UP
;
2997 osdFormatWindSpeedStr(buff
+ 2, verticalWindSpeed
, valid
);
3006 STATIC_ASSERT(GPS_DEGREES_DIVIDER
== OLC_DEG_MULTIPLIER
, invalid_olc_deg_multiplier
);
3007 int digits
= osdConfig()->plus_code_digits
;
3008 int digitsRemoved
= osdConfig()->plus_code_short
* 2;
3009 if (STATE(GPS_FIX
)) {
3010 olc_encode(gpsSol
.llh
.lat
, gpsSol
.llh
.lon
, digits
, buff
, sizeof(buff
));
3012 // +codes with > 8 digits have a + at the 9th digit
3013 // and we only support 10 and up.
3014 memset(buff
, '-', digits
+ 1);
3016 buff
[digits
+ 1] = '\0';
3018 // Optionally trim digits from the left
3019 memmove(buff
, buff
+digitsRemoved
, strlen(buff
) + digitsRemoved
);
3020 buff
[digits
+ 1 - digitsRemoved
] = '\0';
3027 buff
[0] = SYM_AZIMUTH
;
3028 if (osdIsHeadingValid()) {
3029 int16_t h
= GPS_directionToHome
;
3038 tfp_sprintf(&buff
[1], "%3d", h
);
3040 buff
[1] = buff
[2] = buff
[3] = '-';
3042 buff
[4] = SYM_DEGREES
;
3050 int scaleUnitDivisor
;
3055 switch (osdConfig()->units
) {
3058 case OSD_UNIT_IMPERIAL
:
3059 scaleToUnit
= 100 / 1609.3440f
; // scale to 0.01mi for osdFormatCentiNumber()
3060 scaleUnitDivisor
= 0;
3061 symUnscaled
= SYM_MI
;
3066 scaleToUnit
= 100 / 1852.0010f
; // scale to 0.01mi for osdFormatCentiNumber()
3067 scaleUnitDivisor
= 0;
3068 symUnscaled
= SYM_NM
;
3073 case OSD_UNIT_METRIC_MPH
:
3075 case OSD_UNIT_METRIC
:
3076 scaleToUnit
= 100; // scale to cm for osdFormatCentiNumber()
3077 scaleUnitDivisor
= 1000; // Convert to km when scale gets bigger than 999m
3078 symUnscaled
= SYM_M
;
3083 buff
[0] = SYM_SCALE
;
3084 if (osdMapData
.scale
> 0) {
3085 bool scaled
= osdFormatCentiNumber(&buff
[1], osdMapData
.scale
* scaleToUnit
, scaleUnitDivisor
, maxDecimals
, 2, 3);
3086 buff
[4] = scaled
? symScaled
: symUnscaled
;
3087 // Make sure this is cleared if the map stops being drawn
3088 osdMapData
.scale
= 0;
3090 memset(&buff
[1], '-', 4);
3095 case OSD_MAP_REFERENCE
:
3097 char referenceSymbol
;
3098 if (osdMapData
.referenceSymbol
) {
3099 referenceSymbol
= osdMapData
.referenceSymbol
;
3100 // Make sure this is cleared if the map stops being drawn
3101 osdMapData
.referenceSymbol
= 0;
3103 referenceSymbol
= '-';
3105 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
, SYM_DIRECTION
);
3106 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
+ 1, referenceSymbol
);
3112 osdFormatGVar(buff
, 0);
3117 osdFormatGVar(buff
, 1);
3122 osdFormatGVar(buff
, 2);
3127 osdFormatGVar(buff
, 3);
3131 #if defined(USE_RX_MSP) && defined(USE_MSP_RC_OVERRIDE)
3134 const char *source_text
= IS_RC_MODE_ACTIVE(BOXMSPRCOVERRIDE
) && !mspOverrideIsInFailsafe() ? "MSP" : "STD";
3135 if (IS_RC_MODE_ACTIVE(BOXMSPRCOVERRIDE
) && mspOverrideIsInFailsafe()) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
3136 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, source_text
, elemAttr
);
3141 #if defined(USE_ESC_SENSOR)
3144 escSensorData_t
* escSensor
= escSensorGetData();
3145 if (escSensor
&& escSensor
->dataAge
<= ESC_DATA_MAX_AGE
) {
3146 osdFormatRpm(buff
, escSensor
->rpm
);
3149 osdFormatRpm(buff
, 0);
3153 case OSD_ESC_TEMPERATURE
:
3155 escSensorData_t
* escSensor
= escSensorGetData();
3156 bool escTemperatureValid
= escSensor
&& escSensor
->dataAge
<= ESC_DATA_MAX_AGE
;
3157 osdDisplayTemperature(elemPosX
, elemPosY
, SYM_ESC_TEMP
, NULL
, escTemperatureValid
, (escSensor
->temperature
)*10, osdConfig()->esc_temp_alarm_min
, osdConfig()->esc_temp_alarm_max
);
3164 textAttributes_t attr
;
3166 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "TPA");
3167 attr
= TEXT_ATTRIBUTES_NONE
;
3168 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->throttle
.dynPID
);
3169 if (isAdjustmentFunctionSelected(ADJUSTMENT_TPA
)) {
3170 TEXT_ATTRIBUTES_ADD_BLINK(attr
);
3172 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 5, elemPosY
, buff
, attr
);
3174 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
+ 1, "BP");
3175 attr
= TEXT_ATTRIBUTES_NONE
;
3176 tfp_sprintf(buff
, "%4d", currentControlRateProfile
->throttle
.pa_breakpoint
);
3177 if (isAdjustmentFunctionSelected(ADJUSTMENT_TPA_BREAKPOINT
)) {
3178 TEXT_ATTRIBUTES_ADD_BLINK(attr
);
3180 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
+ 1, buff
, attr
);
3184 case OSD_TPA_TIME_CONSTANT
:
3186 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "TPA TC", 0, currentControlRateProfile
->throttle
.fixedWingTauMs
, 4, 0, ADJUSTMENT_FW_TPA_TIME_CONSTANT
);
3189 case OSD_FW_LEVEL_TRIM
:
3191 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "LEVEL", 0, pidProfileMutable()->fixedWingLevelTrim
, 3, 1, ADJUSTMENT_FW_LEVEL_TRIM
);
3195 case OSD_NAV_FW_CONTROL_SMOOTHNESS
:
3197 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "CTL S", 0, navConfig()->fw
.control_smoothness
, 1, 0, ADJUSTMENT_NAV_FW_CONTROL_SMOOTHNESS
);
3200 #ifdef USE_MULTI_MISSION
3201 case OSD_NAV_WP_MULTI_MISSION_INDEX
:
3203 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "WP NO", 0, navConfig()->general
.waypoint_multi_mission_index
, 1, 0, ADJUSTMENT_NAV_WP_MULTI_MISSION_INDEX
);
3209 if (IS_RC_MODE_ACTIVE(BOXPLANWPMISSION
)) {
3211 switch (posControl
.wpMissionPlannerStatus
) {
3213 strcpy(buf
, "WAIT");
3216 strcpy(buf
, "SAVE");
3219 strcpy(buf
, " OK ");
3222 strcpy(buf
, "FULL");
3224 tfp_sprintf(buff
, "%s>%2uWP", buf
, posControl
.wpPlannerActiveWPIndex
);
3225 } else if (posControl
.wpPlannerActiveWPIndex
){
3226 tfp_sprintf(buff
, "PLAN>%2uWP", posControl
.waypointCount
); // mission planner mision active
3228 #ifdef USE_MULTI_MISSION
3230 if (ARMING_FLAG(ARMED
) && !(IS_RC_MODE_ACTIVE(BOXCHANGEMISSION
) && posControl
.multiMissionCount
> 1)){
3231 // Limit field size when Armed, only show selected mission
3232 tfp_sprintf(buff
, "M%u ", posControl
.loadedMultiMissionIndex
);
3233 } else if (posControl
.multiMissionCount
) {
3234 if (navConfig()->general
.waypoint_multi_mission_index
!= posControl
.loadedMultiMissionIndex
) {
3235 tfp_sprintf(buff
, "M%u/%u>LOAD", navConfig()->general
.waypoint_multi_mission_index
, posControl
.multiMissionCount
);
3237 if (posControl
.waypointListValid
&& posControl
.waypointCount
> 0) {
3238 tfp_sprintf(buff
, "M%u/%u>%2uWP", posControl
.loadedMultiMissionIndex
, posControl
.multiMissionCount
, posControl
.waypointCount
);
3240 tfp_sprintf(buff
, "M0/%u> 0WP", posControl
.multiMissionCount
);
3243 } else { // no multi mission loaded - show active WP count from other source
3244 tfp_sprintf(buff
, "WP CNT>%2u", posControl
.waypointCount
);
3248 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
3252 #ifdef USE_POWER_LIMITS
3253 case OSD_PLIMIT_REMAINING_BURST_TIME
:
3254 osdFormatCentiNumber(buff
, powerLimiterGetRemainingBurstTime() * 100, 0, 1, 0, 3);
3259 case OSD_PLIMIT_ACTIVE_CURRENT_LIMIT
:
3260 if (currentBatteryProfile
->powerLimits
.continuousCurrent
) {
3261 osdFormatCentiNumber(buff
, powerLimiterGetActiveCurrentLimit(), 0, 2, 0, 3);
3265 if (powerLimiterIsLimitingCurrent()) {
3266 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
3272 case OSD_PLIMIT_ACTIVE_POWER_LIMIT
:
3274 if (currentBatteryProfile
->powerLimits
.continuousPower
) {
3275 bool kiloWatt
= osdFormatCentiNumber(buff
, powerLimiterGetActivePowerLimit(), 1000, 2, 2, 3);
3276 buff
[3] = kiloWatt
? SYM_KILOWATT
: SYM_WATT
;
3279 if (powerLimiterIsLimitingPower()) {
3280 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
3286 #endif // USE_POWER_LIMITS
3292 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
3296 uint8_t osdIncElementIndex(uint8_t elementIndex
)
3300 if (elementIndex
== OSD_ARTIFICIAL_HORIZON
) { // always drawn last so skip
3304 #ifndef USE_TEMPERATURE_SENSOR
3305 if (elementIndex
== OSD_TEMP_SENSOR_0_TEMPERATURE
) {
3306 elementIndex
= OSD_ALTITUDE_MSL
;
3310 if (!(feature(FEATURE_VBAT
) && feature(FEATURE_CURRENT_METER
))) {
3311 if (elementIndex
== OSD_POWER
) {
3312 elementIndex
= OSD_GPS_LON
;
3314 if (elementIndex
== OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
) {
3315 elementIndex
= OSD_LEVEL_PIDS
;
3317 #ifdef USE_POWER_LIMITS
3318 if (elementIndex
== OSD_PLIMIT_REMAINING_BURST_TIME
) {
3319 elementIndex
= OSD_GLIDESLOPE
;
3324 #ifndef USE_POWER_LIMITS
3325 if (elementIndex
== OSD_PLIMIT_REMAINING_BURST_TIME
) {
3326 elementIndex
= OSD_GLIDESLOPE
;
3330 if (!feature(FEATURE_CURRENT_METER
)) {
3331 if (elementIndex
== OSD_CURRENT_DRAW
) {
3332 elementIndex
= OSD_GPS_SPEED
;
3334 if (elementIndex
== OSD_EFFICIENCY_MAH_PER_KM
) {
3335 elementIndex
= OSD_BATTERY_REMAINING_PERCENT
;
3337 if (elementIndex
== OSD_EFFICIENCY_WH_PER_KM
) {
3338 elementIndex
= OSD_TRIP_DIST
;
3340 if (elementIndex
== OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
) {
3341 elementIndex
= OSD_HOME_HEADING_ERROR
;
3343 if (elementIndex
== OSD_CLIMB_EFFICIENCY
) {
3344 elementIndex
= OSD_NAV_WP_MULTI_MISSION_INDEX
;
3348 if (!STATE(ESC_SENSOR_ENABLED
)) {
3349 if (elementIndex
== OSD_ESC_RPM
) {
3350 elementIndex
= OSD_AZIMUTH
;
3354 if (!feature(FEATURE_GPS
)) {
3355 if (elementIndex
== OSD_GPS_HDOP
|| elementIndex
== OSD_TRIP_DIST
|| elementIndex
== OSD_3D_SPEED
|| elementIndex
== OSD_MISSION
||
3356 elementIndex
== OSD_AZIMUTH
|| elementIndex
== OSD_BATTERY_REMAINING_CAPACITY
|| elementIndex
== OSD_EFFICIENCY_MAH_PER_KM
) {
3359 if (elementIndex
== OSD_HEADING_GRAPH
&& !sensors(SENSOR_MAG
)) {
3360 elementIndex
= feature(FEATURE_CURRENT_METER
) ? OSD_WH_DRAWN
: OSD_BATTERY_REMAINING_PERCENT
;
3362 if (elementIndex
== OSD_EFFICIENCY_WH_PER_KM
) {
3363 elementIndex
= OSD_ATTITUDE_PITCH
;
3365 if (elementIndex
== OSD_GPS_SPEED
) {
3366 elementIndex
= OSD_ALTITUDE
;
3368 if (elementIndex
== OSD_GPS_LON
) {
3369 elementIndex
= sensors(SENSOR_MAG
) ? OSD_HEADING
: OSD_VARIO
;
3371 if (elementIndex
== OSD_MAP_NORTH
) {
3372 elementIndex
= feature(FEATURE_CURRENT_METER
) ? OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
: OSD_LEVEL_PIDS
;
3374 if (elementIndex
== OSD_PLUS_CODE
) {
3375 elementIndex
= OSD_GFORCE
;
3377 if (elementIndex
== OSD_GLIDESLOPE
) {
3378 elementIndex
= OSD_AIR_MAX_SPEED
;
3380 if (elementIndex
== OSD_GLIDE_RANGE
) {
3381 elementIndex
= feature(FEATURE_CURRENT_METER
) ? OSD_CLIMB_EFFICIENCY
: OSD_ITEM_COUNT
;
3383 if (elementIndex
== OSD_NAV_WP_MULTI_MISSION_INDEX
) {
3384 elementIndex
= OSD_ITEM_COUNT
;
3388 if (!sensors(SENSOR_ACC
)) {
3389 if (elementIndex
== OSD_CROSSHAIRS
) {
3390 elementIndex
= OSD_ONTIME
;
3392 if (elementIndex
== OSD_GFORCE
) {
3393 elementIndex
= OSD_RC_SOURCE
;
3397 if (elementIndex
== OSD_ITEM_COUNT
) {
3400 return elementIndex
;
3403 void osdDrawNextElement(void)
3405 static uint8_t elementIndex
= 0;
3406 // Flag for end of loop, also prevents infinite loop when no elements are enabled
3407 uint8_t index
= elementIndex
;
3409 elementIndex
= osdIncElementIndex(elementIndex
);
3410 } while (!osdDrawSingleElement(elementIndex
) && index
!= elementIndex
);
3412 // Draw artificial horizon + tracking telemtry last
3413 osdDrawSingleElement(OSD_ARTIFICIAL_HORIZON
);
3414 if (osdConfig()->telemetry
>0){
3415 osdDisplayTelemetry();
3419 PG_RESET_TEMPLATE(osdConfig_t
, osdConfig
,
3420 .rssi_alarm
= SETTING_OSD_RSSI_ALARM_DEFAULT
,
3421 .time_alarm
= SETTING_OSD_TIME_ALARM_DEFAULT
,
3422 .alt_alarm
= SETTING_OSD_ALT_ALARM_DEFAULT
,
3423 .dist_alarm
= SETTING_OSD_DIST_ALARM_DEFAULT
,
3424 .neg_alt_alarm
= SETTING_OSD_NEG_ALT_ALARM_DEFAULT
,
3425 .current_alarm
= SETTING_OSD_CURRENT_ALARM_DEFAULT
,
3426 .imu_temp_alarm_min
= SETTING_OSD_IMU_TEMP_ALARM_MIN_DEFAULT
,
3427 .imu_temp_alarm_max
= SETTING_OSD_IMU_TEMP_ALARM_MAX_DEFAULT
,
3428 .esc_temp_alarm_min
= SETTING_OSD_ESC_TEMP_ALARM_MIN_DEFAULT
,
3429 .esc_temp_alarm_max
= SETTING_OSD_ESC_TEMP_ALARM_MAX_DEFAULT
,
3430 .gforce_alarm
= SETTING_OSD_GFORCE_ALARM_DEFAULT
,
3431 .gforce_axis_alarm_min
= SETTING_OSD_GFORCE_AXIS_ALARM_MIN_DEFAULT
,
3432 .gforce_axis_alarm_max
= SETTING_OSD_GFORCE_AXIS_ALARM_MAX_DEFAULT
,
3434 .baro_temp_alarm_min
= SETTING_OSD_BARO_TEMP_ALARM_MIN_DEFAULT
,
3435 .baro_temp_alarm_max
= SETTING_OSD_BARO_TEMP_ALARM_MAX_DEFAULT
,
3437 #ifdef USE_SERIALRX_CRSF
3438 .snr_alarm
= SETTING_OSD_SNR_ALARM_DEFAULT
,
3439 .crsf_lq_format
= SETTING_OSD_CRSF_LQ_FORMAT_DEFAULT
,
3440 .link_quality_alarm
= SETTING_OSD_LINK_QUALITY_ALARM_DEFAULT
,
3441 .rssi_dbm_alarm
= SETTING_OSD_RSSI_DBM_ALARM_DEFAULT
,
3442 .rssi_dbm_max
= SETTING_OSD_RSSI_DBM_MAX_DEFAULT
,
3443 .rssi_dbm_min
= SETTING_OSD_RSSI_DBM_MIN_DEFAULT
,
3445 #ifdef USE_TEMPERATURE_SENSOR
3446 .temp_label_align
= SETTING_OSD_TEMP_LABEL_ALIGN_DEFAULT
,
3449 .airspeed_alarm_min
= SETTING_OSD_AIRSPEED_ALARM_MIN_DEFAULT
,
3450 .airspeed_alarm_max
= SETTING_OSD_AIRSPEED_ALARM_MAX_DEFAULT
,
3453 .video_system
= SETTING_OSD_VIDEO_SYSTEM_DEFAULT
,
3454 .row_shiftdown
= SETTING_OSD_ROW_SHIFTDOWN_DEFAULT
,
3455 .msp_displayport_fullframe_interval
= SETTING_OSD_MSP_DISPLAYPORT_FULLFRAME_INTERVAL_DEFAULT
,
3457 .ahi_reverse_roll
= SETTING_OSD_AHI_REVERSE_ROLL_DEFAULT
,
3458 .ahi_max_pitch
= SETTING_OSD_AHI_MAX_PITCH_DEFAULT
,
3459 .crosshairs_style
= SETTING_OSD_CROSSHAIRS_STYLE_DEFAULT
,
3460 .horizon_offset
= SETTING_OSD_HORIZON_OFFSET_DEFAULT
,
3461 .camera_uptilt
= SETTING_OSD_CAMERA_UPTILT_DEFAULT
,
3462 .ahi_camera_uptilt_comp
= SETTING_OSD_AHI_CAMERA_UPTILT_COMP_DEFAULT
,
3463 .camera_fov_h
= SETTING_OSD_CAMERA_FOV_H_DEFAULT
,
3464 .camera_fov_v
= SETTING_OSD_CAMERA_FOV_V_DEFAULT
,
3465 .hud_margin_h
= SETTING_OSD_HUD_MARGIN_H_DEFAULT
,
3466 .hud_margin_v
= SETTING_OSD_HUD_MARGIN_V_DEFAULT
,
3467 .hud_homing
= SETTING_OSD_HUD_HOMING_DEFAULT
,
3468 .hud_homepoint
= SETTING_OSD_HUD_HOMEPOINT_DEFAULT
,
3469 .hud_radar_disp
= SETTING_OSD_HUD_RADAR_DISP_DEFAULT
,
3470 .hud_radar_range_min
= SETTING_OSD_HUD_RADAR_RANGE_MIN_DEFAULT
,
3471 .hud_radar_range_max
= SETTING_OSD_HUD_RADAR_RANGE_MAX_DEFAULT
,
3472 .hud_radar_alt_difference_display_time
= SETTING_OSD_HUD_RADAR_ALT_DIFFERENCE_DISPLAY_TIME_DEFAULT
,
3473 .hud_radar_distance_display_time
= SETTING_OSD_HUD_RADAR_DISTANCE_DISPLAY_TIME_DEFAULT
,
3474 .hud_wp_disp
= SETTING_OSD_HUD_WP_DISP_DEFAULT
,
3475 .left_sidebar_scroll
= SETTING_OSD_LEFT_SIDEBAR_SCROLL_DEFAULT
,
3476 .right_sidebar_scroll
= SETTING_OSD_RIGHT_SIDEBAR_SCROLL_DEFAULT
,
3477 .sidebar_scroll_arrows
= SETTING_OSD_SIDEBAR_SCROLL_ARROWS_DEFAULT
,
3478 .sidebar_horizontal_offset
= SETTING_OSD_SIDEBAR_HORIZONTAL_OFFSET_DEFAULT
,
3479 .left_sidebar_scroll_step
= SETTING_OSD_LEFT_SIDEBAR_SCROLL_STEP_DEFAULT
,
3480 .right_sidebar_scroll_step
= SETTING_OSD_RIGHT_SIDEBAR_SCROLL_STEP_DEFAULT
,
3481 .sidebar_height
= SETTING_OSD_SIDEBAR_HEIGHT_DEFAULT
,
3482 .ahi_pitch_interval
= SETTING_OSD_AHI_PITCH_INTERVAL_DEFAULT
,
3483 .osd_home_position_arm_screen
= SETTING_OSD_HOME_POSITION_ARM_SCREEN_DEFAULT
,
3484 .pan_servo_index
= SETTING_OSD_PAN_SERVO_INDEX_DEFAULT
,
3485 .pan_servo_pwm2centideg
= SETTING_OSD_PAN_SERVO_PWM2CENTIDEG_DEFAULT
,
3486 .esc_rpm_precision
= SETTING_OSD_ESC_RPM_PRECISION_DEFAULT
,
3487 .mAh_used_precision
= SETTING_OSD_MAH_USED_PRECISION_DEFAULT
,
3488 .osd_switch_indicator0_name
= SETTING_OSD_SWITCH_INDICATOR_ZERO_NAME_DEFAULT
,
3489 .osd_switch_indicator0_channel
= SETTING_OSD_SWITCH_INDICATOR_ZERO_CHANNEL_DEFAULT
,
3490 .osd_switch_indicator1_name
= SETTING_OSD_SWITCH_INDICATOR_ONE_NAME_DEFAULT
,
3491 .osd_switch_indicator1_channel
= SETTING_OSD_SWITCH_INDICATOR_ONE_CHANNEL_DEFAULT
,
3492 .osd_switch_indicator2_name
= SETTING_OSD_SWITCH_INDICATOR_TWO_NAME_DEFAULT
,
3493 .osd_switch_indicator2_channel
= SETTING_OSD_SWITCH_INDICATOR_TWO_CHANNEL_DEFAULT
,
3494 .osd_switch_indicator3_name
= SETTING_OSD_SWITCH_INDICATOR_THREE_NAME_DEFAULT
,
3495 .osd_switch_indicator3_channel
= SETTING_OSD_SWITCH_INDICATOR_THREE_CHANNEL_DEFAULT
,
3496 .osd_switch_indicators_align_left
= SETTING_OSD_SWITCH_INDICATORS_ALIGN_LEFT_DEFAULT
,
3497 .system_msg_display_time
= SETTING_OSD_SYSTEM_MSG_DISPLAY_TIME_DEFAULT
,
3498 .units
= SETTING_OSD_UNITS_DEFAULT
,
3499 .main_voltage_decimals
= SETTING_OSD_MAIN_VOLTAGE_DECIMALS_DEFAULT
,
3501 #ifdef USE_WIND_ESTIMATOR
3502 .estimations_wind_compensation
= SETTING_OSD_ESTIMATIONS_WIND_COMPENSATION_DEFAULT
,
3505 .coordinate_digits
= SETTING_OSD_COORDINATE_DIGITS_DEFAULT
,
3507 .osd_failsafe_switch_layout
= SETTING_OSD_FAILSAFE_SWITCH_LAYOUT_DEFAULT
,
3509 .plus_code_digits
= SETTING_OSD_PLUS_CODE_DIGITS_DEFAULT
,
3510 .plus_code_short
= SETTING_OSD_PLUS_CODE_SHORT_DEFAULT
,
3512 .ahi_width
= SETTING_OSD_AHI_WIDTH_DEFAULT
,
3513 .ahi_height
= SETTING_OSD_AHI_HEIGHT_DEFAULT
,
3514 .ahi_vertical_offset
= SETTING_OSD_AHI_VERTICAL_OFFSET_DEFAULT
,
3515 .ahi_bordered
= SETTING_OSD_AHI_BORDERED_DEFAULT
,
3516 .ahi_style
= SETTING_OSD_AHI_STYLE_DEFAULT
,
3518 .force_grid
= SETTING_OSD_FORCE_GRID_DEFAULT
,
3520 .stats_energy_unit
= SETTING_OSD_STATS_ENERGY_UNIT_DEFAULT
,
3521 .stats_min_voltage_unit
= SETTING_OSD_STATS_MIN_VOLTAGE_UNIT_DEFAULT
,
3522 .stats_page_auto_swap_time
= SETTING_OSD_STATS_PAGE_AUTO_SWAP_TIME_DEFAULT
3525 void pgResetFn_osdLayoutsConfig(osdLayoutsConfig_t
*osdLayoutsConfig
)
3527 osdLayoutsConfig
->item_pos
[0][OSD_ALTITUDE
] = OSD_POS(1, 0) | OSD_VISIBLE_FLAG
;
3528 osdLayoutsConfig
->item_pos
[0][OSD_MAIN_BATT_VOLTAGE
] = OSD_POS(12, 0) | OSD_VISIBLE_FLAG
;
3529 osdLayoutsConfig
->item_pos
[0][OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
] = OSD_POS(12, 1);
3531 osdLayoutsConfig
->item_pos
[0][OSD_RSSI_VALUE
] = OSD_POS(23, 0) | OSD_VISIBLE_FLAG
;
3533 osdLayoutsConfig
->item_pos
[0][OSD_HOME_DIST
] = OSD_POS(1, 1);
3534 osdLayoutsConfig
->item_pos
[0][OSD_TRIP_DIST
] = OSD_POS(1, 2);
3535 osdLayoutsConfig
->item_pos
[0][OSD_MAIN_BATT_CELL_VOLTAGE
] = OSD_POS(12, 1);
3536 osdLayoutsConfig
->item_pos
[0][OSD_MAIN_BATT_SAG_COMPENSATED_CELL_VOLTAGE
] = OSD_POS(12, 1);
3537 osdLayoutsConfig
->item_pos
[0][OSD_GPS_SPEED
] = OSD_POS(23, 1);
3538 osdLayoutsConfig
->item_pos
[0][OSD_3D_SPEED
] = OSD_POS(23, 1);
3539 osdLayoutsConfig
->item_pos
[0][OSD_GLIDESLOPE
] = OSD_POS(23, 2);
3541 osdLayoutsConfig
->item_pos
[0][OSD_THROTTLE_POS
] = OSD_POS(1, 2) | OSD_VISIBLE_FLAG
;
3542 osdLayoutsConfig
->item_pos
[0][OSD_THROTTLE_POS_AUTO_THR
] = OSD_POS(6, 2);
3543 osdLayoutsConfig
->item_pos
[0][OSD_HEADING
] = OSD_POS(12, 2);
3544 osdLayoutsConfig
->item_pos
[0][OSD_GROUND_COURSE
] = OSD_POS(12, 3);
3545 osdLayoutsConfig
->item_pos
[0][OSD_COURSE_HOLD_ERROR
] = OSD_POS(12, 2);
3546 osdLayoutsConfig
->item_pos
[0][OSD_COURSE_HOLD_ADJUSTMENT
] = OSD_POS(12, 2);
3547 osdLayoutsConfig
->item_pos
[0][OSD_CROSS_TRACK_ERROR
] = OSD_POS(12, 3);
3548 osdLayoutsConfig
->item_pos
[0][OSD_HEADING_GRAPH
] = OSD_POS(18, 2);
3549 osdLayoutsConfig
->item_pos
[0][OSD_CURRENT_DRAW
] = OSD_POS(2, 3) | OSD_VISIBLE_FLAG
;
3550 osdLayoutsConfig
->item_pos
[0][OSD_MAH_DRAWN
] = OSD_POS(1, 4) | OSD_VISIBLE_FLAG
;
3551 osdLayoutsConfig
->item_pos
[0][OSD_WH_DRAWN
] = OSD_POS(1, 5);
3552 osdLayoutsConfig
->item_pos
[0][OSD_BATTERY_REMAINING_CAPACITY
] = OSD_POS(1, 6);
3553 osdLayoutsConfig
->item_pos
[0][OSD_BATTERY_REMAINING_PERCENT
] = OSD_POS(1, 7);
3554 osdLayoutsConfig
->item_pos
[0][OSD_POWER_SUPPLY_IMPEDANCE
] = OSD_POS(1, 8);
3556 osdLayoutsConfig
->item_pos
[0][OSD_EFFICIENCY_MAH_PER_KM
] = OSD_POS(1, 5);
3557 osdLayoutsConfig
->item_pos
[0][OSD_EFFICIENCY_WH_PER_KM
] = OSD_POS(1, 5);
3559 osdLayoutsConfig
->item_pos
[0][OSD_ATTITUDE_ROLL
] = OSD_POS(1, 7);
3560 osdLayoutsConfig
->item_pos
[0][OSD_ATTITUDE_PITCH
] = OSD_POS(1, 8);
3562 // avoid OSD_VARIO under OSD_CROSSHAIRS
3563 osdLayoutsConfig
->item_pos
[0][OSD_VARIO
] = OSD_POS(23, 5);
3564 // OSD_VARIO_NUM at the right of OSD_VARIO
3565 osdLayoutsConfig
->item_pos
[0][OSD_VARIO_NUM
] = OSD_POS(24, 7);
3566 osdLayoutsConfig
->item_pos
[0][OSD_HOME_DIR
] = OSD_POS(14, 11);
3567 osdLayoutsConfig
->item_pos
[0][OSD_ARTIFICIAL_HORIZON
] = OSD_POS(8, 6);
3568 osdLayoutsConfig
->item_pos
[0][OSD_HORIZON_SIDEBARS
] = OSD_POS(8, 6);
3570 osdLayoutsConfig
->item_pos
[0][OSD_CRAFT_NAME
] = OSD_POS(20, 2);
3571 osdLayoutsConfig
->item_pos
[0][OSD_PILOT_NAME
] = OSD_POS(20, 3);
3572 osdLayoutsConfig
->item_pos
[0][OSD_VTX_CHANNEL
] = OSD_POS(8, 6);
3574 #ifdef USE_SERIALRX_CRSF
3575 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_RSSI_DBM
] = OSD_POS(23, 12);
3576 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_LQ
] = OSD_POS(23, 11);
3577 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_SNR_DB
] = OSD_POS(24, 9);
3578 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_TX_POWER
] = OSD_POS(24, 10);
3581 osdLayoutsConfig
->item_pos
[0][OSD_ONTIME
] = OSD_POS(23, 8);
3582 osdLayoutsConfig
->item_pos
[0][OSD_FLYTIME
] = OSD_POS(23, 9);
3583 osdLayoutsConfig
->item_pos
[0][OSD_ONTIME_FLYTIME
] = OSD_POS(23, 11) | OSD_VISIBLE_FLAG
;
3584 osdLayoutsConfig
->item_pos
[0][OSD_RTC_TIME
] = OSD_POS(23, 12);
3585 osdLayoutsConfig
->item_pos
[0][OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
] = OSD_POS(23, 7);
3586 osdLayoutsConfig
->item_pos
[0][OSD_REMAINING_DISTANCE_BEFORE_RTH
] = OSD_POS(23, 6);
3588 osdLayoutsConfig
->item_pos
[0][OSD_MISSION
] = OSD_POS(0, 10);
3589 osdLayoutsConfig
->item_pos
[0][OSD_GPS_SATS
] = OSD_POS(0, 11) | OSD_VISIBLE_FLAG
;
3590 osdLayoutsConfig
->item_pos
[0][OSD_GPS_HDOP
] = OSD_POS(0, 10);
3592 osdLayoutsConfig
->item_pos
[0][OSD_GPS_LAT
] = OSD_POS(0, 12);
3593 // Put this on top of the latitude, since it's very unlikely
3594 // that users will want to use both at the same time.
3595 osdLayoutsConfig
->item_pos
[0][OSD_PLUS_CODE
] = OSD_POS(0, 12);
3596 osdLayoutsConfig
->item_pos
[0][OSD_FLYMODE
] = OSD_POS(13, 12) | OSD_VISIBLE_FLAG
;
3597 osdLayoutsConfig
->item_pos
[0][OSD_GPS_LON
] = OSD_POS(18, 12);
3599 osdLayoutsConfig
->item_pos
[0][OSD_AZIMUTH
] = OSD_POS(2, 12);
3601 osdLayoutsConfig
->item_pos
[0][OSD_ROLL_PIDS
] = OSD_POS(2, 10);
3602 osdLayoutsConfig
->item_pos
[0][OSD_PITCH_PIDS
] = OSD_POS(2, 11);
3603 osdLayoutsConfig
->item_pos
[0][OSD_YAW_PIDS
] = OSD_POS(2, 12);
3604 osdLayoutsConfig
->item_pos
[0][OSD_LEVEL_PIDS
] = OSD_POS(2, 12);
3605 osdLayoutsConfig
->item_pos
[0][OSD_POS_XY_PIDS
] = OSD_POS(2, 12);
3606 osdLayoutsConfig
->item_pos
[0][OSD_POS_Z_PIDS
] = OSD_POS(2, 12);
3607 osdLayoutsConfig
->item_pos
[0][OSD_VEL_XY_PIDS
] = OSD_POS(2, 12);
3608 osdLayoutsConfig
->item_pos
[0][OSD_VEL_Z_PIDS
] = OSD_POS(2, 12);
3609 osdLayoutsConfig
->item_pos
[0][OSD_HEADING_P
] = OSD_POS(2, 12);
3610 osdLayoutsConfig
->item_pos
[0][OSD_BOARD_ALIGN_ROLL
] = OSD_POS(2, 10);
3611 osdLayoutsConfig
->item_pos
[0][OSD_BOARD_ALIGN_PITCH
] = OSD_POS(2, 11);
3612 osdLayoutsConfig
->item_pos
[0][OSD_RC_EXPO
] = OSD_POS(2, 12);
3613 osdLayoutsConfig
->item_pos
[0][OSD_RC_YAW_EXPO
] = OSD_POS(2, 12);
3614 osdLayoutsConfig
->item_pos
[0][OSD_THROTTLE_EXPO
] = OSD_POS(2, 12);
3615 osdLayoutsConfig
->item_pos
[0][OSD_PITCH_RATE
] = OSD_POS(2, 12);
3616 osdLayoutsConfig
->item_pos
[0][OSD_ROLL_RATE
] = OSD_POS(2, 12);
3617 osdLayoutsConfig
->item_pos
[0][OSD_YAW_RATE
] = OSD_POS(2, 12);
3618 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_RC_EXPO
] = OSD_POS(2, 12);
3619 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_RC_YAW_EXPO
] = OSD_POS(2, 12);
3620 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_PITCH_RATE
] = OSD_POS(2, 12);
3621 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_ROLL_RATE
] = OSD_POS(2, 12);
3622 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_YAW_RATE
] = OSD_POS(2, 12);
3623 osdLayoutsConfig
->item_pos
[0][OSD_NAV_FW_CRUISE_THR
] = OSD_POS(2, 12);
3624 osdLayoutsConfig
->item_pos
[0][OSD_NAV_FW_PITCH2THR
] = OSD_POS(2, 12);
3625 osdLayoutsConfig
->item_pos
[0][OSD_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
] = OSD_POS(2, 12);
3626 osdLayoutsConfig
->item_pos
[0][OSD_FW_ALT_PID_OUTPUTS
] = OSD_POS(2, 12);
3627 osdLayoutsConfig
->item_pos
[0][OSD_FW_POS_PID_OUTPUTS
] = OSD_POS(2, 12);
3628 osdLayoutsConfig
->item_pos
[0][OSD_MC_VEL_X_PID_OUTPUTS
] = OSD_POS(2, 12);
3629 osdLayoutsConfig
->item_pos
[0][OSD_MC_VEL_Y_PID_OUTPUTS
] = OSD_POS(2, 12);
3630 osdLayoutsConfig
->item_pos
[0][OSD_MC_VEL_Z_PID_OUTPUTS
] = OSD_POS(2, 12);
3631 osdLayoutsConfig
->item_pos
[0][OSD_MC_POS_XYZ_P_OUTPUTS
] = OSD_POS(2, 12);
3633 osdLayoutsConfig
->item_pos
[0][OSD_POWER
] = OSD_POS(15, 1);
3635 osdLayoutsConfig
->item_pos
[0][OSD_IMU_TEMPERATURE
] = OSD_POS(19, 2);
3636 osdLayoutsConfig
->item_pos
[0][OSD_BARO_TEMPERATURE
] = OSD_POS(19, 3);
3637 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_0_TEMPERATURE
] = OSD_POS(19, 4);
3638 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_1_TEMPERATURE
] = OSD_POS(19, 5);
3639 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_2_TEMPERATURE
] = OSD_POS(19, 6);
3640 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_3_TEMPERATURE
] = OSD_POS(19, 7);
3641 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_4_TEMPERATURE
] = OSD_POS(19, 8);
3642 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_5_TEMPERATURE
] = OSD_POS(19, 9);
3643 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_6_TEMPERATURE
] = OSD_POS(19, 10);
3644 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_7_TEMPERATURE
] = OSD_POS(19, 11);
3646 osdLayoutsConfig
->item_pos
[0][OSD_AIR_SPEED
] = OSD_POS(3, 5);
3647 osdLayoutsConfig
->item_pos
[0][OSD_WIND_SPEED_HORIZONTAL
] = OSD_POS(3, 6);
3648 osdLayoutsConfig
->item_pos
[0][OSD_WIND_SPEED_VERTICAL
] = OSD_POS(3, 7);
3650 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE
] = OSD_POS(12, 4);
3651 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE_X
] = OSD_POS(12, 5);
3652 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE_Y
] = OSD_POS(12, 6);
3653 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE_Z
] = OSD_POS(12, 7);
3655 osdLayoutsConfig
->item_pos
[0][OSD_VTX_POWER
] = OSD_POS(3, 5);
3657 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_0
] = OSD_POS(1, 1);
3658 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_1
] = OSD_POS(1, 2);
3659 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_2
] = OSD_POS(1, 3);
3660 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_3
] = OSD_POS(1, 4);
3662 osdLayoutsConfig
->item_pos
[0][OSD_SWITCH_INDICATOR_0
] = OSD_POS(2, 7);
3663 osdLayoutsConfig
->item_pos
[0][OSD_SWITCH_INDICATOR_1
] = OSD_POS(2, 8);
3664 osdLayoutsConfig
->item_pos
[0][OSD_SWITCH_INDICATOR_2
] = OSD_POS(2, 9);
3665 osdLayoutsConfig
->item_pos
[0][OSD_SWITCH_INDICATOR_3
] = OSD_POS(2, 10);
3667 #if defined(USE_ESC_SENSOR)
3668 osdLayoutsConfig
->item_pos
[0][OSD_ESC_RPM
] = OSD_POS(1, 2);
3669 osdLayoutsConfig
->item_pos
[0][OSD_ESC_TEMPERATURE
] = OSD_POS(1, 3);
3672 #if defined(USE_RX_MSP) && defined(USE_MSP_RC_OVERRIDE)
3673 osdLayoutsConfig
->item_pos
[0][OSD_RC_SOURCE
] = OSD_POS(3, 4);
3676 #ifdef USE_POWER_LIMITS
3677 osdLayoutsConfig
->item_pos
[0][OSD_PLIMIT_REMAINING_BURST_TIME
] = OSD_POS(3, 4);
3678 osdLayoutsConfig
->item_pos
[0][OSD_PLIMIT_ACTIVE_CURRENT_LIMIT
] = OSD_POS(3, 5);
3679 osdLayoutsConfig
->item_pos
[0][OSD_PLIMIT_ACTIVE_POWER_LIMIT
] = OSD_POS(3, 6);
3682 // Under OSD_FLYMODE. TODO: Might not be visible on NTSC?
3683 osdLayoutsConfig
->item_pos
[0][OSD_MESSAGES
] = OSD_POS(1, 13) | OSD_VISIBLE_FLAG
;
3685 for (unsigned ii
= 1; ii
< OSD_LAYOUT_COUNT
; ii
++) {
3686 for (unsigned jj
= 0; jj
< ARRAYLEN(osdLayoutsConfig
->item_pos
[0]); jj
++) {
3687 osdLayoutsConfig
->item_pos
[ii
][jj
] = osdLayoutsConfig
->item_pos
[0][jj
] & ~OSD_VISIBLE_FLAG
;
3692 static void osdSetNextRefreshIn(uint32_t timeMs
) {
3693 resumeRefreshAt
= micros() + timeMs
* 1000;
3694 refreshWaitForResumeCmdRelease
= true;
3697 static void osdCompleteAsyncInitialization(void)
3699 if (!displayIsReady(osdDisplayPort
)) {
3700 // Update the display.
3701 // XXX: Rename displayDrawScreen() and associated functions
3702 // to displayUpdate()
3703 displayDrawScreen(osdDisplayPort
);
3707 osdDisplayIsReady
= true;
3709 #if defined(USE_CANVAS)
3710 if (osdConfig()->force_grid
) {
3711 osdDisplayHasCanvas
= false;
3713 osdDisplayHasCanvas
= displayGetCanvas(&osdCanvas
, osdDisplayPort
);
3717 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
3718 displayClearScreen(osdDisplayPort
);
3721 displayFontMetadata_t metadata
;
3722 bool fontHasMetadata
= displayGetFontMetadata(&metadata
, osdDisplayPort
);
3723 LOG_DEBUG(OSD
, "Font metadata version %s: %u (%u chars)",
3724 fontHasMetadata
? "Y" : "N", metadata
.version
, metadata
.charCount
);
3726 if (fontHasMetadata
&& metadata
.charCount
> 256) {
3727 hasExtendedFont
= true;
3728 unsigned logo_c
= SYM_LOGO_START
;
3729 unsigned logo_x
= OSD_CENTER_LEN(SYM_LOGO_WIDTH
);
3730 for (unsigned ii
= 0; ii
< SYM_LOGO_HEIGHT
; ii
++) {
3731 for (unsigned jj
= 0; jj
< SYM_LOGO_WIDTH
; jj
++) {
3732 displayWriteChar(osdDisplayPort
, logo_x
+ jj
, y
, logo_c
++);
3737 } else if (!fontHasMetadata
) {
3738 const char *m
= "INVALID FONT";
3739 displayWrite(osdDisplayPort
, OSD_CENTER_S(m
), 3, m
);
3743 if (fontHasMetadata
&& metadata
.version
< OSD_MIN_FONT_VERSION
) {
3744 const char *m
= "INVALID FONT VERSION";
3745 displayWrite(osdDisplayPort
, OSD_CENTER_S(m
), y
++, m
);
3748 char string_buffer
[30];
3749 tfp_sprintf(string_buffer
, "INAV VERSION: %s", FC_VERSION_STRING
);
3750 uint8_t xPos
= osdDisplayIsHD() ? 15 : 5;
3751 displayWrite(osdDisplayPort
, xPos
, y
++, string_buffer
);
3753 displayWrite(osdDisplayPort
, xPos
+2, y
++, CMS_STARTUP_HELP_TEXT1
);
3754 displayWrite(osdDisplayPort
, xPos
+6, y
++, CMS_STARTUP_HELP_TEXT2
);
3755 displayWrite(osdDisplayPort
, xPos
+6, y
++, CMS_STARTUP_HELP_TEXT3
);
3760 uint8_t statNameX
= osdDisplayIsHD() ? 14 : 4;
3761 uint8_t statValueX
= osdDisplayIsHD() ? 34 : 24;
3763 if (statsConfig()->stats_enabled
) {
3764 displayWrite(osdDisplayPort
, statNameX
, ++y
, "ODOMETER:");
3765 switch (osdConfig()->units
) {
3768 case OSD_UNIT_IMPERIAL
:
3769 tfp_sprintf(string_buffer
, "%5d", (int)(statsConfig()->stats_total_dist
/ METERS_PER_MILE
));
3770 string_buffer
[5] = SYM_MI
;
3774 tfp_sprintf(string_buffer
, "%5d", (int)(statsConfig()->stats_total_dist
/ METERS_PER_NAUTICALMILE
));
3775 string_buffer
[5] = SYM_NM
;
3777 case OSD_UNIT_METRIC_MPH
:
3779 case OSD_UNIT_METRIC
:
3780 tfp_sprintf(string_buffer
, "%5d", (int)(statsConfig()->stats_total_dist
/ METERS_PER_KILOMETER
));
3781 string_buffer
[5] = SYM_KM
;
3784 string_buffer
[6] = '\0';
3785 displayWrite(osdDisplayPort
, statValueX
-5, y
, string_buffer
);
3787 displayWrite(osdDisplayPort
, statNameX
, ++y
, "TOTAL TIME:");
3788 uint32_t tot_mins
= statsConfig()->stats_total_time
/ 60;
3789 tfp_sprintf(string_buffer
, "%2d:%02dHM", (int)(tot_mins
/ 60), (int)(tot_mins
% 60));
3790 displayWrite(osdDisplayPort
, statValueX
-5, y
, string_buffer
);
3793 if (feature(FEATURE_VBAT
) && feature(FEATURE_CURRENT_METER
)) {
3794 displayWrite(osdDisplayPort
, statNameX
, ++y
, "TOTAL ENERGY:");
3795 osdFormatCentiNumber(string_buffer
, statsConfig()->stats_total_energy
/ 10, 0, 2, 0, 4);
3796 strcat(string_buffer
, "\xAB"); // SYM_WH
3797 displayWrite(osdDisplayPort
, statValueX
-4, y
, string_buffer
);
3799 displayWrite(osdDisplayPort
, statNameX
, ++y
, "AVG EFFICIENCY:");
3800 if (statsConfig()->stats_total_dist
) {
3801 uint32_t avg_efficiency
= statsConfig()->stats_total_energy
/ (statsConfig()->stats_total_dist
/ METERS_PER_KILOMETER
); // mWh/km
3802 switch (osdConfig()->units
) {
3805 case OSD_UNIT_IMPERIAL
:
3806 osdFormatCentiNumber(string_buffer
, avg_efficiency
/ 10, 0, 2, 0, 3);
3807 string_buffer
[3] = SYM_WH_MI
;
3810 osdFormatCentiNumber(string_buffer
, avg_efficiency
/ 10, 0, 2, 0, 3);
3811 string_buffer
[3] = SYM_WH_NM
;
3814 case OSD_UNIT_METRIC_MPH
:
3816 case OSD_UNIT_METRIC
:
3817 osdFormatCentiNumber(string_buffer
, avg_efficiency
/ 10000 * METERS_PER_MILE
, 0, 2, 0, 3);
3818 string_buffer
[3] = SYM_WH_KM
;
3822 string_buffer
[0] = string_buffer
[1] = string_buffer
[2] = '-';
3824 string_buffer
[4] = '\0';
3825 displayWrite(osdDisplayPort
, statValueX
-3, y
, string_buffer
);
3831 displayCommitTransaction(osdDisplayPort
);
3832 displayResync(osdDisplayPort
);
3833 osdSetNextRefreshIn(SPLASH_SCREEN_DISPLAY_TIME
);
3836 void osdInit(displayPort_t
*osdDisplayPortToUse
)
3838 if (!osdDisplayPortToUse
)
3841 BUILD_BUG_ON(OSD_POS_MAX
!= OSD_POS(63,63));
3843 osdDisplayPort
= osdDisplayPortToUse
;
3846 cmsDisplayPortRegister(osdDisplayPort
);
3849 armState
= ARMING_FLAG(ARMED
);
3850 osdCompleteAsyncInitialization();
3853 static void osdResetStats(void)
3855 stats
.max_current
= 0;
3856 stats
.max_power
= 0;
3857 stats
.max_speed
= 0;
3858 stats
.max_3D_speed
= 0;
3859 stats
.max_air_speed
= 0;
3860 stats
.min_voltage
= 5000;
3861 stats
.min_rssi
= 99;
3863 stats
.min_rssi_dbm
= 0;
3864 stats
.max_altitude
= 0;
3867 static void osdUpdateStats(void)
3871 if (feature(FEATURE_GPS
)) {
3872 value
= osdGet3DSpeed();
3873 const float airspeed_estimate
= getAirspeedEstimate();
3875 if (stats
.max_3D_speed
< value
)
3876 stats
.max_3D_speed
= value
;
3878 if (stats
.max_speed
< gpsSol
.groundSpeed
)
3879 stats
.max_speed
= gpsSol
.groundSpeed
;
3881 if (stats
.max_air_speed
< airspeed_estimate
)
3882 stats
.max_air_speed
= airspeed_estimate
;
3884 if (stats
.max_distance
< GPS_distanceToHome
)
3885 stats
.max_distance
= GPS_distanceToHome
;
3888 value
= getBatteryVoltage();
3889 if (stats
.min_voltage
> value
)
3890 stats
.min_voltage
= value
;
3892 value
= abs(getAmperage());
3893 if (stats
.max_current
< value
)
3894 stats
.max_current
= value
;
3896 value
= labs(getPower());
3897 if (stats
.max_power
< value
)
3898 stats
.max_power
= value
;
3900 value
= osdConvertRSSI();
3901 if (stats
.min_rssi
> value
)
3902 stats
.min_rssi
= value
;
3904 value
= osdGetCrsfLQ();
3905 if (stats
.min_lq
> value
)
3906 stats
.min_lq
= value
;
3908 if (!failsafeIsReceivingRxData())
3911 value
= osdGetCrsfdBm();
3912 if (stats
.min_rssi_dbm
> value
)
3913 stats
.min_rssi_dbm
= value
;
3915 stats
.max_altitude
= MAX(stats
.max_altitude
, osdGetAltitude());
3918 static void osdShowStatsPage1(void)
3920 const char * disarmReasonStr
[DISARM_REASON_COUNT
] = { "UNKNOWN", "TIMEOUT", "STICKS", "SWITCH", "SWITCH", "KILLSW", "FAILSAFE", "NAV SYS", "LANDING"};
3921 uint8_t top
= 1; /* first fully visible line */
3922 const uint8_t statNameX
= osdDisplayIsHD() ? 11 : 1;
3923 const uint8_t statValuesX
= osdDisplayIsHD() ? 30 : 20;
3925 statsPagesCheck
= 1;
3927 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
3928 displayClearScreen(osdDisplayPort
);
3930 displayWrite(osdDisplayPort
, statNameX
, top
++, "--- STATS --- 1/2 ->");
3932 if (feature(FEATURE_GPS
)) {
3933 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX SPEED :");
3934 osdFormatVelocityStr(buff
, stats
.max_3D_speed
, true, false);
3935 osdLeftAlignString(buff
);
3936 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3938 displayWrite(osdDisplayPort
, statNameX
, top
, "AVG SPEED :");
3939 osdGenerateAverageVelocityStr(buff
);
3940 osdLeftAlignString(buff
);
3941 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3943 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX DISTANCE :");
3944 osdFormatDistanceStr(buff
, stats
.max_distance
*100);
3945 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3947 displayWrite(osdDisplayPort
, statNameX
, top
, "TRAVELED DISTANCE:");
3948 osdFormatDistanceStr(buff
, getTotalTravelDistance());
3949 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3952 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX ALTITUDE :");
3953 osdFormatAltitudeStr(buff
, stats
.max_altitude
);
3954 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3956 switch (rxConfig()->serialrx_provider
) {
3958 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN RSSI % :");
3959 itoa(stats
.min_rssi
, buff
, 10);
3961 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3963 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN RSSI DBM :");
3964 itoa(stats
.min_rssi_dbm
, buff
, 10);
3965 tfp_sprintf(buff
, "%s%c", buff
, SYM_DBM
);
3966 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3968 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN LQ :");
3969 itoa(stats
.min_lq
, buff
, 10);
3971 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3974 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN RSSI :");
3975 itoa(stats
.min_rssi
, buff
, 10);
3977 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3980 displayWrite(osdDisplayPort
, statNameX
, top
, "FLY TIME :");
3981 uint16_t flySeconds
= getFlightTime();
3982 uint16_t flyMinutes
= flySeconds
/ 60;
3984 uint16_t flyHours
= flyMinutes
/ 60;
3986 tfp_sprintf(buff
, "%02u:%02u:%02u", flyHours
, flyMinutes
, flySeconds
);
3987 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3989 displayWrite(osdDisplayPort
, statNameX
, top
, "DISARMED BY :");
3990 displayWrite(osdDisplayPort
, statValuesX
, top
++, disarmReasonStr
[getDisarmReason()]);
3992 if (savingSettings
== true) {
3993 displayWrite(osdDisplayPort
, statNameX
, top
++, OSD_MESSAGE_STR(OSD_MSG_SAVING_SETTNGS
));
3994 } else if (notify_settings_saved
> 0) {
3995 if (millis() > notify_settings_saved
) {
3996 notify_settings_saved
= 0;
3998 displayWrite(osdDisplayPort
, statNameX
, top
++, OSD_MESSAGE_STR(OSD_MSG_SETTINGS_SAVED
));
4002 displayCommitTransaction(osdDisplayPort
);
4005 static void osdShowStatsPage2(void)
4007 uint8_t top
= 1; /* first fully visible line */
4008 const uint8_t statNameX
= osdDisplayIsHD() ? 11 : 1;
4009 const uint8_t statValuesX
= osdDisplayIsHD() ? 30 : 20;
4011 statsPagesCheck
= 1;
4013 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
4014 displayClearScreen(osdDisplayPort
);
4016 displayWrite(osdDisplayPort
, statNameX
, top
++, "--- STATS --- <- 2/2");
4018 if (osdConfig()->stats_min_voltage_unit
== OSD_STATS_MIN_VOLTAGE_UNIT_BATTERY
) {
4019 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN BATTERY VOLT :");
4020 osdFormatCentiNumber(buff
, stats
.min_voltage
, 0, osdConfig()->main_voltage_decimals
, 0, osdConfig()->main_voltage_decimals
+ 2);
4022 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN CELL VOLTAGE :");
4023 osdFormatCentiNumber(buff
, stats
.min_voltage
/getBatteryCellCount(), 0, 2, 0, 3);
4025 tfp_sprintf(buff
, "%s%c", buff
, SYM_VOLT
);
4026 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
4028 if (feature(FEATURE_CURRENT_METER
)) {
4029 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX CURRENT :");
4030 osdFormatCentiNumber(buff
, stats
.max_current
, 0, 2, 0, 3);
4031 tfp_sprintf(buff
, "%s%c", buff
, SYM_AMP
);
4032 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
4034 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX POWER :");
4035 bool kiloWatt
= osdFormatCentiNumber(buff
, stats
.max_power
, 1000, 2, 2, 3);
4036 buff
[3] = kiloWatt
? SYM_KILOWATT
: SYM_WATT
;
4038 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
4040 displayWrite(osdDisplayPort
, statNameX
, top
, "USED CAPACITY :");
4041 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
4042 tfp_sprintf(buff
, "%d%c", (int)getMAhDrawn(), SYM_MAH
);
4044 osdFormatCentiNumber(buff
, getMWhDrawn() / 10, 0, 2, 0, 3);
4045 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH
);
4047 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
4049 int32_t totalDistance
= getTotalTravelDistance();
4050 bool moreThanAh
= false;
4051 bool efficiencyValid
= totalDistance
>= 10000;
4052 if (feature(FEATURE_GPS
)) {
4053 displayWrite(osdDisplayPort
, statNameX
, top
, "AVG EFFICIENCY :");
4054 switch (osdConfig()->units
) {
4057 case OSD_UNIT_IMPERIAL
:
4058 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
4059 moreThanAh
= osdFormatCentiNumber(buff
, (int32_t)(getMAhDrawn() * 10000.0f
* METERS_PER_MILE
/ totalDistance
), 1000, 0, 2, 3);
4061 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_MI_0
, SYM_MAH_MI_1
);
4063 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_MI
);
4065 if (!efficiencyValid
) {
4066 buff
[0] = buff
[1] = buff
[2] = '-';
4067 buff
[3] = SYM_MAH_MI_0
;
4068 buff
[4] = SYM_MAH_MI_1
;
4072 osdFormatCentiNumber(buff
, (int32_t)(getMWhDrawn() * 10.0f
* METERS_PER_MILE
/ totalDistance
), 0, 2, 0, 3);
4073 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH_MI
);
4074 if (!efficiencyValid
) {
4075 buff
[0] = buff
[1] = buff
[2] = '-';
4080 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
4081 moreThanAh
= osdFormatCentiNumber(buff
, (int32_t)(getMAhDrawn() * 10000.0f
* METERS_PER_NAUTICALMILE
/ totalDistance
), 1000, 0, 2, 3);
4083 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_NM_0
, SYM_MAH_NM_1
);
4085 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_NM
);
4087 if (!efficiencyValid
) {
4088 buff
[0] = buff
[1] = buff
[2] = '-';
4089 buff
[3] = SYM_MAH_NM_0
;
4090 buff
[4] = SYM_MAH_NM_1
;
4094 osdFormatCentiNumber(buff
, (int32_t)(getMWhDrawn() * 10.0f
* METERS_PER_NAUTICALMILE
/ totalDistance
), 0, 2, 0, 3);
4095 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH_NM
);
4096 if (!efficiencyValid
) {
4097 buff
[0] = buff
[1] = buff
[2] = '-';
4101 case OSD_UNIT_METRIC_MPH
:
4103 case OSD_UNIT_METRIC
:
4104 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
4105 moreThanAh
= osdFormatCentiNumber(buff
, (int32_t)(getMAhDrawn() * 10000000.0f
/ totalDistance
), 1000, 0, 2, 3);
4107 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_KM_0
, SYM_MAH_KM_1
);
4109 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_KM
);
4111 if (!efficiencyValid
) {
4112 buff
[0] = buff
[1] = buff
[2] = '-';
4113 buff
[3] = SYM_MAH_KM_0
;
4114 buff
[4] = SYM_MAH_KM_1
;
4118 osdFormatCentiNumber(buff
, (int32_t)(getMWhDrawn() * 10000.0f
/ totalDistance
), 0, 2, 0, 3);
4119 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH_KM
);
4120 if (!efficiencyValid
) {
4121 buff
[0] = buff
[1] = buff
[2] = '-';
4126 osdLeftAlignString(buff
);
4127 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
4131 const float max_gforce
= accGetMeasuredMaxG();
4132 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX G-FORCE :");
4133 osdFormatCentiNumber(buff
, max_gforce
* 100, 0, 2, 0, 3);
4134 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
4136 const acc_extremes_t
*acc_extremes
= accGetMeasuredExtremes();
4137 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN/MAX Z G-FORCE:");
4138 osdFormatCentiNumber(buff
, acc_extremes
[Z
].min
* 100, 0, 2, 0, 4);
4140 displayWrite(osdDisplayPort
, statValuesX
- 1, top
, buff
);
4141 osdFormatCentiNumber(buff
, acc_extremes
[Z
].max
* 100, 0, 2, 0, 3);
4142 displayWrite(osdDisplayPort
, statValuesX
+ 4, top
++, buff
);
4144 if (savingSettings
== true) {
4145 displayWrite(osdDisplayPort
, statNameX
, top
++, OSD_MESSAGE_STR(OSD_MSG_SAVING_SETTNGS
));
4146 } else if (notify_settings_saved
> 0) {
4147 if (millis() > notify_settings_saved
) {
4148 notify_settings_saved
= 0;
4150 displayWrite(osdDisplayPort
, statNameX
, top
++, OSD_MESSAGE_STR(OSD_MSG_SETTINGS_SAVED
));
4154 displayCommitTransaction(osdDisplayPort
);
4157 // called when motors armed
4158 static void osdShowArmed(void)
4161 char buf
[MAX(32, FORMATTED_DATE_TIME_BUFSIZE
)];
4162 char craftNameBuf
[MAX_NAME_LENGTH
];
4163 char versionBuf
[30];
4166 // We need 12 visible rows, start row never < first fully visible row 1
4167 uint8_t y
= osdDisplayPort
->rows
> 13 ? (osdDisplayPort
->rows
- 12) / 2 : 1;
4169 displayClearScreen(osdDisplayPort
);
4170 strcpy(buf
, "ARMED");
4171 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
4174 if (strlen(systemConfig()->craftName
) > 0) {
4175 osdFormatCraftName(craftNameBuf
);
4176 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(systemConfig()->craftName
)) / 2, y
, craftNameBuf
);
4179 if (posControl
.waypointListValid
&& posControl
.waypointCount
> 0) {
4180 #ifdef USE_MULTI_MISSION
4181 tfp_sprintf(buf
, "MISSION %u/%u (%u WP)", posControl
.loadedMultiMissionIndex
, posControl
.multiMissionCount
, posControl
.waypointCount
);
4182 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
4184 strcpy(buf
, "*MISSION LOADED*");
4185 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
4190 #if defined(USE_GPS)
4191 if (feature(FEATURE_GPS
)) {
4192 if (STATE(GPS_FIX_HOME
)) {
4193 if (osdConfig()->osd_home_position_arm_screen
){
4194 osdFormatCoordinate(buf
, SYM_LAT
, GPS_home
.lat
);
4195 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
4196 osdFormatCoordinate(buf
, SYM_LON
, GPS_home
.lon
);
4197 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
+ 1, buf
);
4198 int digits
= osdConfig()->plus_code_digits
;
4199 olc_encode(GPS_home
.lat
, GPS_home
.lon
, digits
, buf
, sizeof(buf
));
4200 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
+ 2, buf
);
4203 #if defined (USE_SAFE_HOME)
4204 if (safehome_distance
) { // safehome found during arming
4205 if (navConfig()->general
.flags
.safehome_usage_mode
== SAFEHOME_USAGE_OFF
) {
4206 strcpy(buf
, "SAFEHOME FOUND; MODE OFF");
4208 char buf2
[12]; // format the distance first
4209 osdFormatDistanceStr(buf2
, safehome_distance
);
4210 tfp_sprintf(buf
, "%c - %s -> SAFEHOME %u", SYM_HOME
, buf2
, safehome_index
);
4212 textAttributes_t elemAttr
= _TEXT_ATTRIBUTES_BLINK_BIT
;
4213 // write this message above the ARMED message to make it obvious
4214 displayWriteWithAttr(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
- 8, buf
, elemAttr
);
4218 strcpy(buf
, "!NO HOME POSITION!");
4219 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
4225 if (rtcGetDateTime(&dt
)) {
4226 dateTimeFormatLocal(buf
, &dt
);
4227 dateTimeSplitFormatted(buf
, &date
, &time
);
4229 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(date
)) / 2, y
, date
);
4230 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(time
)) / 2, y
+ 1, time
);
4234 tfp_sprintf(versionBuf
, "INAV VERSION: %s", FC_VERSION_STRING
);
4235 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(versionBuf
)) / 2, y
, versionBuf
);
4238 static void osdFilterData(timeUs_t currentTimeUs
) {
4239 static timeUs_t lastRefresh
= 0;
4240 float refresh_dT
= US2S(cmpTimeUs(currentTimeUs
, lastRefresh
));
4242 GForce
= fast_fsqrtf(vectorNormSquared(&imuMeasuredAccelBF
)) / GRAVITY_MSS
;
4243 for (uint8_t axis
= 0; axis
< XYZ_AXIS_COUNT
; ++axis
) GForceAxis
[axis
] = imuMeasuredAccelBF
.v
[axis
] / GRAVITY_MSS
;
4246 GForce
= pt1FilterApply3(&GForceFilter
, GForce
, refresh_dT
);
4247 for (uint8_t axis
= 0; axis
< XYZ_AXIS_COUNT
; ++axis
) pt1FilterApply3(GForceFilterAxis
+ axis
, GForceAxis
[axis
], refresh_dT
);
4249 pt1FilterInitRC(&GForceFilter
, GFORCE_FILTER_TC
, 0);
4250 pt1FilterReset(&GForceFilter
, GForce
);
4252 for (uint8_t axis
= 0; axis
< XYZ_AXIS_COUNT
; ++axis
) {
4253 pt1FilterInitRC(GForceFilterAxis
+ axis
, GFORCE_FILTER_TC
, 0);
4254 pt1FilterReset(GForceFilterAxis
+ axis
, GForceAxis
[axis
]);
4258 lastRefresh
= currentTimeUs
;
4261 static void osdRefresh(timeUs_t currentTimeUs
)
4263 osdFilterData(currentTimeUs
);
4266 if (IS_RC_MODE_ACTIVE(BOXOSD
) && (!cmsInMenu
) && !(osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
))) {
4268 if (IS_RC_MODE_ACTIVE(BOXOSD
) && !(osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
))) {
4270 displayClearScreen(osdDisplayPort
);
4271 armState
= ARMING_FLAG(ARMED
);
4275 // detect arm/disarm
4276 static uint8_t statsPageAutoSwapCntl
= 2;
4277 if (armState
!= ARMING_FLAG(ARMED
)) {
4278 if (ARMING_FLAG(ARMED
)) {
4280 statsPageAutoSwapCntl
= 2;
4281 osdShowArmed(); // reset statistic etc
4282 uint32_t delay
= ARMED_SCREEN_DISPLAY_TIME
;
4283 statsPagesCheck
= 0;
4284 #if defined(USE_SAFE_HOME)
4285 if (safehome_distance
)
4288 osdSetNextRefreshIn(delay
);
4290 osdShowStatsPage1(); // show first page of statistics
4291 osdSetNextRefreshIn(STATS_SCREEN_DISPLAY_TIME
);
4292 statsPageAutoSwapCntl
= osdConfig()->stats_page_auto_swap_time
> 0 ? 0 : 2; // disable swapping pages when time = 0
4295 armState
= ARMING_FLAG(ARMED
);
4298 if (resumeRefreshAt
) {
4299 // If we already reached he time for the next refresh,
4300 // or THR is high or PITCH is high, resume refreshing.
4301 // Clear the screen first to erase other elements which
4302 // might have been drawn while the OSD wasn't refreshing.
4304 // auto swap stats pages when first shown
4305 // auto swap cancelled using roll stick
4306 if (statsPageAutoSwapCntl
!= 2) {
4307 if (STATS_PAGE1
|| STATS_PAGE2
) {
4308 statsPageAutoSwapCntl
= 2;
4310 if (OSD_ALTERNATING_CHOICES((osdConfig()->stats_page_auto_swap_time
* 1000), 2)) {
4311 if (statsPageAutoSwapCntl
== 0) {
4312 osdShowStatsPage1();
4313 statsPageAutoSwapCntl
= 1;
4316 if (statsPageAutoSwapCntl
== 1) {
4317 osdShowStatsPage2();
4318 statsPageAutoSwapCntl
= 0;
4324 if (!DELAYED_REFRESH_RESUME_COMMAND
)
4325 refreshWaitForResumeCmdRelease
= false;
4327 if ((currentTimeUs
> resumeRefreshAt
) || ((!refreshWaitForResumeCmdRelease
) && DELAYED_REFRESH_RESUME_COMMAND
)) {
4328 displayClearScreen(osdDisplayPort
);
4329 resumeRefreshAt
= 0;
4330 } else if ((currentTimeUs
> resumeRefreshAt
) || ((!refreshWaitForResumeCmdRelease
) && STATS_PAGE1
)) {
4331 if (statsPagesCheck
== 1) {
4332 osdShowStatsPage1();
4334 } else if ((currentTimeUs
> resumeRefreshAt
) || ((!refreshWaitForResumeCmdRelease
) && STATS_PAGE2
)) {
4335 if (statsPagesCheck
== 1) {
4336 osdShowStatsPage2();
4339 displayHeartbeat(osdDisplayPort
);
4345 if (!displayIsGrabbed(osdDisplayPort
)) {
4346 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
4348 displayClearScreen(osdDisplayPort
);
4351 osdDrawNextElement();
4352 displayHeartbeat(osdDisplayPort
);
4353 displayCommitTransaction(osdDisplayPort
);
4354 #ifdef OSD_CALLS_CMS
4356 cmsUpdate(currentTimeUs
);
4363 * Called periodically by the scheduler
4365 void osdUpdate(timeUs_t currentTimeUs
)
4367 static uint32_t counter
= 0;
4369 // don't touch buffers if DMA transaction is in progress
4370 if (displayIsTransferInProgress(osdDisplayPort
)) {
4374 if (!osdDisplayIsReady
) {
4375 osdCompleteAsyncInitialization();
4379 #if defined(OSD_ALTERNATE_LAYOUT_COUNT) && OSD_ALTERNATE_LAYOUT_COUNT > 0
4380 // Check if the layout has changed. Higher numbered
4381 // boxes take priority.
4382 unsigned activeLayout
;
4383 if (layoutOverride
>= 0) {
4384 activeLayout
= layoutOverride
;
4385 // Check for timed override, it will go into effect on
4386 // the next OSD iteration
4387 if (layoutOverrideUntil
> 0 && millis() > layoutOverrideUntil
) {
4388 layoutOverrideUntil
= 0;
4389 layoutOverride
= -1;
4391 } else if (osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
)) {
4394 #if OSD_ALTERNATE_LAYOUT_COUNT > 2
4395 if (IS_RC_MODE_ACTIVE(BOXOSDALT3
))
4399 #if OSD_ALTERNATE_LAYOUT_COUNT > 1
4400 if (IS_RC_MODE_ACTIVE(BOXOSDALT2
))
4404 if (IS_RC_MODE_ACTIVE(BOXOSDALT1
))
4407 #ifdef USE_PROGRAMMING_FRAMEWORK
4408 if (LOGIC_CONDITION_GLOBAL_FLAG(LOGIC_CONDITION_GLOBAL_FLAG_OVERRIDE_OSD_LAYOUT
))
4409 activeLayout
= constrain(logicConditionValuesByType
[LOGIC_CONDITION_SET_OSD_LAYOUT
], 0, OSD_ALTERNATE_LAYOUT_COUNT
);
4414 if (currentLayout
!= activeLayout
) {
4415 currentLayout
= activeLayout
;
4416 osdStartFullRedraw();
4420 #define DRAW_FREQ_DENOM 4
4421 #define STATS_FREQ_DENOM 50
4424 if ((counter
% STATS_FREQ_DENOM
) == 0) {
4428 if ((counter
% DRAW_FREQ_DENOM
) == 0) {
4429 // redraw values in buffer
4430 osdRefresh(currentTimeUs
);
4432 // rest of time redraw screen
4433 displayDrawScreen(osdDisplayPort
);
4437 // do not allow ARM if we are in menu
4438 if (displayIsGrabbed(osdDisplayPort
)) {
4439 ENABLE_ARMING_FLAG(ARMING_DISABLED_OSD_MENU
);
4441 DISABLE_ARMING_FLAG(ARMING_DISABLED_OSD_MENU
);
4446 void osdStartFullRedraw(void)
4451 void osdOverrideLayout(int layout
, timeMs_t duration
)
4453 layoutOverride
= constrain(layout
, -1, ARRAYLEN(osdLayoutsConfig()->item_pos
) - 1);
4454 if (layoutOverride
>= 0 && duration
> 0) {
4455 layoutOverrideUntil
= millis() + duration
;
4457 layoutOverrideUntil
= 0;
4461 int osdGetActiveLayout(bool *overridden
)
4464 *overridden
= layoutOverride
>= 0;
4466 return currentLayout
;
4469 bool osdItemIsFixed(osd_items_e item
)
4471 return item
== OSD_CROSSHAIRS
||
4472 item
== OSD_ARTIFICIAL_HORIZON
||
4473 item
== OSD_HORIZON_SIDEBARS
;
4476 displayPort_t
*osdGetDisplayPort(void)
4478 return osdDisplayPort
;
4481 displayCanvas_t
*osdGetDisplayPortCanvas(void)
4483 #if defined(USE_CANVAS)
4484 if (osdDisplayHasCanvas
) {
4491 timeMs_t
systemMessageCycleTime(unsigned messageCount
, const char **messages
){
4493 float factor
= 1.0f
;
4494 while (i
< messageCount
) {
4495 if ((float)strlen(messages
[i
]) / 15.0f
> factor
) {
4496 factor
= (float)strlen(messages
[i
]) / 15.0f
;
4500 return osdConfig()->system_msg_display_time
* factor
;
4503 textAttributes_t
osdGetSystemMessage(char *buff
, size_t buff_size
, bool isCenteredText
)
4505 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
4508 const char *message
= NULL
;
4509 char messageBuf
[MAX(SETTING_MAX_NAME_LENGTH
, OSD_MESSAGE_LENGTH
+1)];
4510 // We might have up to 5 messages to show.
4511 const char *messages
[5];
4512 unsigned messageCount
= 0;
4513 const char *failsafeInfoMessage
= NULL
;
4514 const char *invertedInfoMessage
= NULL
;
4516 if (ARMING_FLAG(ARMED
)) {
4517 if (FLIGHT_MODE(FAILSAFE_MODE
) || FLIGHT_MODE(NAV_RTH_MODE
) || FLIGHT_MODE(NAV_WP_MODE
) || navigationIsExecutingAnEmergencyLanding()) {
4518 if (isWaypointMissionRTHActive()) {
4519 // if RTH activated whilst WP mode selected, remind pilot to cancel WP mode to exit RTH
4520 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_WP_RTH_CANCEL
);
4522 if (navGetCurrentStateFlags() & NAV_AUTO_WP_DONE
) {
4523 messages
[messageCount
++] = STATE(LANDING_DETECTED
) ? OSD_MESSAGE_STR(OSD_MSG_WP_LANDED
) : OSD_MESSAGE_STR(OSD_MSG_WP_FINISHED
);
4524 } else if (NAV_Status
.state
== MW_NAV_STATE_WP_ENROUTE
) {
4525 // Countdown display for remaining Waypoints
4527 osdFormatDistanceSymbol(buf
, posControl
.wpDistance
, 0);
4528 tfp_sprintf(messageBuf
, "TO WP %u/%u (%s)", getGeoWaypointNumber(posControl
.activeWaypointIndex
), posControl
.geoWaypointCount
, buf
);
4529 messages
[messageCount
++] = messageBuf
;
4530 } else if (NAV_Status
.state
== MW_NAV_STATE_HOLD_TIMED
) {
4531 if (navConfig()->general
.waypoint_enforce_altitude
&& !posControl
.wpAltitudeReached
) {
4532 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_ADJUSTING_WP_ALT
);
4534 // WP hold time countdown in seconds
4535 timeMs_t currentTime
= millis();
4536 int holdTimeRemaining
= posControl
.waypointList
[posControl
.activeWaypointIndex
].p1
- (int)(MS2S(currentTime
- posControl
.wpReachedTime
));
4537 holdTimeRemaining
= holdTimeRemaining
>= 0 ? holdTimeRemaining
: 0;
4539 tfp_sprintf(messageBuf
, "HOLDING WP FOR %2u S", holdTimeRemaining
);
4541 messages
[messageCount
++] = messageBuf
;
4544 const char *navStateMessage
= navigationStateMessage();
4545 if (navStateMessage
) {
4546 messages
[messageCount
++] = navStateMessage
;
4549 #if defined(USE_SAFE_HOME)
4550 const char *safehomeMessage
= divertingToSafehomeMessage();
4551 if (safehomeMessage
) {
4552 messages
[messageCount
++] = safehomeMessage
;
4555 if (FLIGHT_MODE(FAILSAFE_MODE
)) {
4556 // In FS mode while being armed too
4557 const char *failsafePhaseMessage
= osdFailsafePhaseMessage();
4558 failsafeInfoMessage
= osdFailsafeInfoMessage();
4560 if (failsafePhaseMessage
) {
4561 messages
[messageCount
++] = failsafePhaseMessage
;
4563 if (failsafeInfoMessage
) {
4564 messages
[messageCount
++] = failsafeInfoMessage
;
4567 } else { /* messages shown only when Failsafe, WP, RTH or Emergency Landing not active */
4568 if (STATE(FIXED_WING_LEGACY
) && (navGetCurrentStateFlags() & NAV_CTL_LAUNCH
)) {
4569 messages
[messageCount
++] = navConfig()->fw
.launch_manual_throttle
? OSD_MESSAGE_STR(OSD_MSG_AUTOLAUNCH_MANUAL
) :
4570 OSD_MESSAGE_STR(OSD_MSG_AUTOLAUNCH
);
4571 const char *launchStateMessage
= fixedWingLaunchStateMessage();
4572 if (launchStateMessage
) {
4573 messages
[messageCount
++] = launchStateMessage
;
4576 if (FLIGHT_MODE(NAV_ALTHOLD_MODE
) && !navigationRequiresAngleMode()) {
4577 // ALTHOLD might be enabled alongside ANGLE/HORIZON/ACRO
4578 // when it doesn't require ANGLE mode (required only in FW
4579 // right now). If if requires ANGLE, its display is handled
4581 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_ALTITUDE_HOLD
);
4583 if (IS_RC_MODE_ACTIVE(BOXAUTOTRIM
) && !feature(FEATURE_FW_AUTOTRIM
)) {
4584 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_AUTOTRIM
);
4586 if (IS_RC_MODE_ACTIVE(BOXAUTOTUNE
)) {
4587 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_AUTOTUNE
);
4588 if (FLIGHT_MODE(MANUAL_MODE
)) {
4589 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_AUTOTUNE_ACRO
);
4592 if (FLIGHT_MODE(HEADFREE_MODE
)) {
4593 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_HEADFREE
);
4595 if (FLIGHT_MODE(SOARING_MODE
)) {
4596 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_NAV_SOARING
);
4598 if (posControl
.flags
.wpMissionPlannerActive
) {
4599 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_MISSION_PLANNER
);
4601 if (STATE(LANDING_DETECTED
)) {
4602 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_LANDED
);
4606 } else if (ARMING_FLAG(ARMING_DISABLED_ALL_FLAGS
)) {
4607 unsigned invalidIndex
;
4609 // Check if we're unable to arm for some reason
4610 if (ARMING_FLAG(ARMING_DISABLED_INVALID_SETTING
) && !settingsValidate(&invalidIndex
)) {
4612 const setting_t
*setting
= settingGet(invalidIndex
);
4613 settingGetName(setting
, messageBuf
);
4614 for (int ii
= 0; messageBuf
[ii
]; ii
++) {
4615 messageBuf
[ii
] = sl_toupper(messageBuf
[ii
]);
4617 invertedInfoMessage
= messageBuf
;
4618 messages
[messageCount
++] = invertedInfoMessage
;
4620 invertedInfoMessage
= OSD_MESSAGE_STR(OSD_MSG_INVALID_SETTING
);
4621 messages
[messageCount
++] = invertedInfoMessage
;
4625 invertedInfoMessage
= OSD_MESSAGE_STR(OSD_MSG_UNABLE_ARM
);
4626 messages
[messageCount
++] = invertedInfoMessage
;
4628 // Show the reason for not arming
4629 messages
[messageCount
++] = osdArmingDisabledReasonMessage();
4632 } else if (!ARMING_FLAG(ARMED
)) {
4633 if (isWaypointListValid()) {
4634 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_WP_MISSION_LOADED
);
4638 /* Messages that are shown regardless of Arming state */
4640 if (savingSettings
== true) {
4641 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_SAVING_SETTNGS
);
4642 } else if (notify_settings_saved
> 0) {
4643 if (millis() > notify_settings_saved
) {
4644 notify_settings_saved
= 0;
4646 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_SETTINGS_SAVED
);
4650 #ifdef USE_DEV_TOOLS
4651 if (systemConfig()->groundTestMode
) {
4652 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_GRD_TEST_MODE
);
4656 if (messageCount
> 0) {
4657 message
= messages
[OSD_ALTERNATING_CHOICES(systemMessageCycleTime(messageCount
, messages
), messageCount
)];
4658 if (message
== failsafeInfoMessage
) {
4659 // failsafeInfoMessage is not useful for recovering
4660 // a lost model, but might help avoiding a crash.
4661 // Blink to grab user attention.
4662 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
4663 } else if (message
== invertedInfoMessage
) {
4664 TEXT_ATTRIBUTES_ADD_INVERTED(elemAttr
);
4666 // We're shoing either failsafePhaseMessage or
4667 // navStateMessage. Don't BLINK here since
4668 // having this text available might be crucial
4669 // during a lost aircraft recovery and blinking
4670 // will cause it to be missing from some frames.
4673 osdFormatMessage(buff
, buff_size
, message
, isCenteredText
);