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"
73 #include "io/vtx_string.h"
75 #include "fc/config.h"
76 #include "fc/controlrate_profile.h"
77 #include "fc/fc_core.h"
78 #include "fc/fc_tasks.h"
79 #include "fc/rc_adjustments.h"
80 #include "fc/rc_controls.h"
81 #include "fc/rc_modes.h"
82 #include "fc/runtime_config.h"
83 #include "fc/settings.h"
85 #include "flight/imu.h"
86 #include "flight/mixer.h"
87 #include "flight/pid.h"
88 #include "flight/power_limits.h"
89 #include "flight/rth_estimator.h"
90 #include "flight/secondary_imu.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
119 #define GFORCE_FILTER_TC 0.2
121 #define DELAYED_REFRESH_RESUME_COMMAND (checkStickPosition(THR_HI) || checkStickPosition(PIT_HI))
122 #define STATS_PAGE2 (checkStickPosition(ROL_HI))
123 #define STATS_PAGE1 (checkStickPosition(ROL_LO))
125 #define SPLASH_SCREEN_DISPLAY_TIME 4000 // ms
126 #define ARMED_SCREEN_DISPLAY_TIME 1500 // ms
127 #define STATS_SCREEN_DISPLAY_TIME 60000 // ms
129 #define EFFICIENCY_UPDATE_INTERVAL (5 * 1000)
131 // Adjust OSD_MESSAGE's default position when
132 // changing OSD_MESSAGE_LENGTH
133 #define OSD_MESSAGE_LENGTH 28
134 #define OSD_ALTERNATING_CHOICES(ms, num_choices) ((millis() / ms) % num_choices)
135 #define _CONST_STR_SIZE(s) ((sizeof(s)/sizeof(s[0]))-1) // -1 to avoid counting final '\0'
136 // Wrap all string constants intenteded for display as messages with
137 // this macro to ensure compile time length validation.
138 #define OSD_MESSAGE_STR(x) ({ \
139 STATIC_ASSERT(_CONST_STR_SIZE(x) <= OSD_MESSAGE_LENGTH, message_string_ ## __COUNTER__ ## _too_long); \
143 #define OSD_CHR_IS_NUM(c) (c >= '0' && c <= '9')
145 #define OSD_CENTER_LEN(x) ((osdDisplayPort->cols - x) / 2)
146 #define OSD_CENTER_S(s) OSD_CENTER_LEN(strlen(s))
148 #define OSD_MIN_FONT_VERSION 3
150 static unsigned currentLayout
= 0;
151 static int layoutOverride
= -1;
152 static bool hasExtendedFont
= false; // Wether the font supports characters > 256
153 static timeMs_t layoutOverrideUntil
= 0;
154 static pt1Filter_t GForceFilter
, GForceFilterAxis
[XYZ_AXIS_COUNT
];
155 static float GForce
, GForceAxis
[XYZ_AXIS_COUNT
];
157 typedef struct statistic_s
{
159 uint16_t max_3D_speed
;
160 uint16_t min_voltage
; // /100
164 int16_t min_lq
; // for CRSF
165 int16_t min_rssi_dbm
; // for CRSF
166 int32_t max_altitude
;
167 uint32_t max_distance
;
170 static statistic_t stats
;
172 static timeUs_t resumeRefreshAt
= 0;
173 static bool refreshWaitForResumeCmdRelease
;
175 static bool fullRedraw
= false;
177 static uint8_t armState
;
178 static uint8_t statsPagesCheck
= 0;
180 typedef struct osdMapData_s
{
182 char referenceSymbol
;
185 static osdMapData_t osdMapData
;
187 static displayPort_t
*osdDisplayPort
;
188 static bool osdDisplayIsReady
= false;
189 #if defined(USE_CANVAS)
190 static displayCanvas_t osdCanvas
;
191 static bool osdDisplayHasCanvas
;
193 #define osdDisplayHasCanvas false
196 #define AH_MAX_PITCH_DEFAULT 20 // Specify default maximum AHI pitch value displayed (degrees)
198 PG_REGISTER_WITH_RESET_TEMPLATE(osdConfig_t
, osdConfig
, PG_OSD_CONFIG
, 4);
199 PG_REGISTER_WITH_RESET_FN(osdLayoutsConfig_t
, osdLayoutsConfig
, PG_OSD_LAYOUTS_CONFIG
, 0);
201 static int digitCount(int32_t value
)
214 bool osdDisplayIsPAL(void)
216 return displayScreenSize(osdDisplayPort
) == VIDEO_BUFFER_CHARS_PAL
;
220 * Formats a number given in cents, to support non integer values
221 * without using floating point math. Value is always right aligned
222 * and spaces are inserted before the number to always yield a string
223 * of the same length. If the value doesn't fit into the provided length
224 * it will be divided by scale and true will be returned.
226 bool osdFormatCentiNumber(char *buff
, int32_t centivalue
, uint32_t scale
, int maxDecimals
, int maxScaledDecimals
, int length
)
230 int decimals
= maxDecimals
;
231 bool negative
= false;
236 if (centivalue
< 0) {
238 centivalue
= -centivalue
;
242 int32_t integerPart
= centivalue
/ 100;
244 int32_t millis
= (centivalue
% 100) * 10;
246 int digits
= digitCount(integerPart
);
247 int remaining
= length
- digits
;
249 if (remaining
< 0 && scale
> 0) {
252 decimals
= maxScaledDecimals
;
253 integerPart
= integerPart
/ scale
;
254 // Multiply by 10 to get 3 decimal digits
255 millis
= ((centivalue
% (100 * scale
)) * 10) / scale
;
256 digits
= digitCount(integerPart
);
257 remaining
= length
- digits
;
260 // 3 decimals at most
261 decimals
= MIN(remaining
, MIN(decimals
, 3));
262 remaining
-= decimals
;
264 // Done counting. Time to write the characters.
266 // Write spaces at the start
267 while (remaining
> 0) {
273 // Write the minus sign if required
278 // Now write the digits.
279 ui2a(integerPart
, 10, 0, ptr
);
282 *(ptr
-1) += SYM_ZERO_HALF_TRAILING_DOT
- '0';
284 int factor
= 3; // we're getting the decimal part in millis first
285 while (decimals
< factor
) {
289 int decimalDigits
= digitCount(millis
);
290 while (decimalDigits
< decimals
) {
295 ui2a(millis
, 10, 0, ptr
);
296 *dec
+= SYM_ZERO_HALF_LEADING_DOT
- '0';
302 * Aligns text to the left side. Adds spaces at the end to keep string length unchanged.
304 static void osdLeftAlignString(char *buff
)
306 uint8_t sp
= 0, ch
= 0;
307 uint8_t len
= strlen(buff
);
308 while (buff
[sp
] == ' ') sp
++;
309 for (ch
= 0; ch
< (len
- sp
); ch
++) buff
[ch
] = buff
[ch
+ sp
];
310 for (sp
= ch
; sp
< len
; sp
++) buff
[sp
] = ' ';
314 * Converts distance into a string based on the current unit system
315 * prefixed by a a symbol to indicate the unit used.
316 * @param dist Distance in centimeters
318 static void osdFormatDistanceSymbol(char *buff
, int32_t dist
, uint8_t decimals
)
320 switch ((osd_unit_e
)osdConfig()->units
) {
323 case OSD_UNIT_IMPERIAL
:
324 if (osdFormatCentiNumber(buff
, CENTIMETERS_TO_CENTIFEET(dist
), FEET_PER_MILE
, decimals
, 3, 3)) {
325 buff
[3] = SYM_DIST_MI
;
327 buff
[3] = SYM_DIST_FT
;
331 case OSD_UNIT_METRIC_MPH
:
333 case OSD_UNIT_METRIC
:
334 if (osdFormatCentiNumber(buff
, dist
, METERS_PER_KILOMETER
, decimals
, 3, 3)) {
335 buff
[3] = SYM_DIST_KM
;
337 buff
[3] = SYM_DIST_M
;
342 if (osdFormatCentiNumber(buff
, CENTIMETERS_TO_CENTIFEET(dist
), FEET_PER_NAUTICALMILE
, decimals
, 3, 3)) {
343 buff
[3] = SYM_DIST_NM
;
345 buff
[3] = SYM_DIST_FT
;
353 * Converts distance into a string based on the current unit system.
354 * @param dist Distance in centimeters
356 static void osdFormatDistanceStr(char *buff
, int32_t dist
)
359 switch ((osd_unit_e
)osdConfig()->units
) {
362 case OSD_UNIT_IMPERIAL
:
363 centifeet
= CENTIMETERS_TO_CENTIFEET(dist
);
364 if (abs(centifeet
) < FEET_PER_MILE
* 100 / 2) {
365 // Show feet when dist < 0.5mi
366 tfp_sprintf(buff
, "%d%c", (int)(centifeet
/ 100), SYM_FT
);
368 // Show miles when dist >= 0.5mi
369 tfp_sprintf(buff
, "%d.%02d%c", (int)(centifeet
/ (100*FEET_PER_MILE
)),
370 (abs(centifeet
) % (100 * FEET_PER_MILE
)) / FEET_PER_MILE
, SYM_MI
);
373 case OSD_UNIT_METRIC_MPH
:
375 case OSD_UNIT_METRIC
:
376 if (abs(dist
) < METERS_PER_KILOMETER
* 100) {
377 // Show meters when dist < 1km
378 tfp_sprintf(buff
, "%d%c", (int)(dist
/ 100), SYM_M
);
380 // Show kilometers when dist >= 1km
381 tfp_sprintf(buff
, "%d.%02d%c", (int)(dist
/ (100*METERS_PER_KILOMETER
)),
382 (abs(dist
) % (100 * METERS_PER_KILOMETER
)) / METERS_PER_KILOMETER
, SYM_KM
);
386 centifeet
= CENTIMETERS_TO_CENTIFEET(dist
);
387 if (abs(centifeet
) < 100000) {
388 // Show feet when dist < 1000ft
389 tfp_sprintf(buff
, "%d%c", (int)(centifeet
/ 100), SYM_FT
);
391 // Show nautical miles when dist >= 1000ft
392 tfp_sprintf(buff
, "%d.%02d%c", (int)(centifeet
/ (100 * FEET_PER_NAUTICALMILE
)),
393 (int)((abs(centifeet
) % (int)(100 * FEET_PER_NAUTICALMILE
)) / FEET_PER_NAUTICALMILE
), SYM_NM
);
400 * Converts velocity based on the current unit system (kmh or mph).
401 * @param alt Raw velocity (i.e. as taken from gpsSol.groundSpeed in centimeters/second)
403 static int32_t osdConvertVelocityToUnit(int32_t vel
)
405 switch ((osd_unit_e
)osdConfig()->units
) {
408 case OSD_UNIT_METRIC_MPH
:
410 case OSD_UNIT_IMPERIAL
:
411 return CMSEC_TO_CENTIMPH(vel
) / 100; // Convert to mph
412 case OSD_UNIT_METRIC
:
413 return CMSEC_TO_CENTIKPH(vel
) / 100; // Convert to kmh
415 return CMSEC_TO_CENTIKNOTS(vel
) / 100; // Convert to Knots
422 * Converts velocity into a string based on the current unit system.
423 * @param alt Raw velocity (i.e. as taken from gpsSol.groundSpeed in centimeters/seconds)
425 void osdFormatVelocityStr(char* buff
, int32_t vel
, bool _3D
, bool _max
)
427 switch ((osd_unit_e
)osdConfig()->units
) {
430 case OSD_UNIT_METRIC_MPH
:
432 case OSD_UNIT_IMPERIAL
:
434 tfp_sprintf(buff
, "%c%3d%c", SYM_MAX
, (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_MPH
: SYM_MPH
));
436 tfp_sprintf(buff
, "%3d%c", (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_MPH
: SYM_MPH
));
439 case OSD_UNIT_METRIC
:
441 tfp_sprintf(buff
, "%c%3d%c", SYM_MAX
, (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KMH
: SYM_KMH
));
443 tfp_sprintf(buff
, "%3d%c", (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KMH
: SYM_KMH
));
448 tfp_sprintf(buff
, "%c%3d%c", SYM_MAX
, (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KT
: SYM_KT
));
450 tfp_sprintf(buff
, "%3d%c", (int)osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KT
: SYM_KT
));
457 * Converts wind speed into a string based on the current unit system, using
458 * always 3 digits and an additional character for the unit at the right. buff
459 * is null terminated.
460 * @param ws Raw wind speed in cm/s
462 #ifdef USE_WIND_ESTIMATOR
463 static void osdFormatWindSpeedStr(char *buff
, int32_t ws
, bool isValid
)
467 switch (osdConfig()->units
) {
470 case OSD_UNIT_METRIC_MPH
:
472 case OSD_UNIT_IMPERIAL
:
473 centivalue
= CMSEC_TO_CENTIMPH(ws
);
477 centivalue
= CMSEC_TO_CENTIKNOTS(ws
);
481 case OSD_UNIT_METRIC
:
482 centivalue
= CMSEC_TO_CENTIKPH(ws
);
487 osdFormatCentiNumber(buff
, centivalue
, 0, 2, 0, 3);
489 buff
[0] = buff
[1] = buff
[2] = '-';
497 * Converts altitude into a string based on the current unit system
498 * prefixed by a a symbol to indicate the unit used.
499 * @param alt Raw altitude/distance (i.e. as taken from baro.BaroAlt in centimeters)
501 void osdFormatAltitudeSymbol(char *buff
, int32_t alt
)
510 switch ((osd_unit_e
)osdConfig()->units
) {
515 case OSD_UNIT_IMPERIAL
:
516 if (osdFormatCentiNumber(buff
+ 4 - digits
, CENTIMETERS_TO_CENTIFEET(alt
), 1000, 0, 2, digits
)) {
518 buff
[4] = SYM_ALT_KFT
;
521 buff
[4] = SYM_ALT_FT
;
525 case OSD_UNIT_METRIC_MPH
:
527 case OSD_UNIT_METRIC
:
528 // alt is alredy in cm
529 if (osdFormatCentiNumber(buff
+ 4 - digits
, alt
, 1000, 0, 2, digits
)) {
531 buff
[4] = SYM_ALT_KM
;
542 * Converts altitude into a string based on the current unit system.
543 * @param alt Raw altitude/distance (i.e. as taken from baro.BaroAlt in centimeters)
545 static void osdFormatAltitudeStr(char *buff
, int32_t alt
)
548 switch ((osd_unit_e
)osdConfig()->units
) {
553 case OSD_UNIT_IMPERIAL
:
554 value
= CENTIMETERS_TO_FEET(alt
);
555 tfp_sprintf(buff
, "%d%c", (int)value
, SYM_FT
);
557 case OSD_UNIT_METRIC_MPH
:
559 case OSD_UNIT_METRIC
:
560 value
= CENTIMETERS_TO_METERS(alt
);
561 tfp_sprintf(buff
, "%d%c", (int)value
, SYM_M
);
566 static void osdFormatTime(char *buff
, uint32_t seconds
, char sym_m
, char sym_h
)
568 uint32_t value
= seconds
;
570 // Maximum value we can show in minutes is 99 minutes and 59 seconds
571 if (seconds
> (99 * 60) + 59) {
573 value
= seconds
/ 60;
576 tfp_sprintf(buff
+ 1, "%02d:%02d", (int)(value
/ 60), (int)(value
% 60));
579 static inline void osdFormatOnTime(char *buff
)
581 osdFormatTime(buff
, micros() / 1000000, SYM_ON_M
, SYM_ON_H
);
584 static inline void osdFormatFlyTime(char *buff
, textAttributes_t
*attr
)
586 uint32_t seconds
= getFlightTime();
587 osdFormatTime(buff
, seconds
, SYM_FLY_M
, SYM_FLY_H
);
588 if (attr
&& osdConfig()->time_alarm
> 0) {
589 if (seconds
/ 60 >= osdConfig()->time_alarm
&& ARMING_FLAG(ARMED
)) {
590 TEXT_ATTRIBUTES_ADD_BLINK(*attr
);
596 * Converts RSSI into a % value used by the OSD.
598 static uint16_t osdConvertRSSI(void)
600 // change range to [0, 99]
601 return constrain(getRSSI() * 100 / RSSI_MAX_VALUE
, 0, 99);
604 static uint16_t osdGetCrsfLQ(void)
606 int16_t statsLQ
= rxLinkStatistics
.uplinkLQ
;
607 int16_t scaledLQ
= scaleRange(constrain(statsLQ
, 0, 100), 0, 100, 170, 300);
609 switch (osdConfig()->crsf_lq_format
) {
610 case OSD_CRSF_LQ_TYPE1
:
611 case OSD_CRSF_LQ_TYPE2
:
612 displayedLQ
= statsLQ
;
614 case OSD_CRSF_LQ_TYPE3
:
615 displayedLQ
= rxLinkStatistics
.rfMode
>= 2 ? scaledLQ
: statsLQ
;
621 static int16_t osdGetCrsfdBm(void)
623 return rxLinkStatistics
.uplinkRSSI
;
626 * Displays a temperature postfixed with a symbol depending on the current unit system
627 * @param label to display
628 * @param valid true if measurement is valid
629 * @param temperature in deciDegrees Celcius
631 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
)
633 char buff
[TEMPERATURE_LABEL_LEN
+ 2 < 6 ? 6 : TEMPERATURE_LABEL_LEN
+ 2];
634 textAttributes_t elemAttr
= valid
? TEXT_ATTRIBUTES_NONE
: _TEXT_ATTRIBUTES_BLINK_BIT
;
635 uint8_t valueXOffset
= 0;
640 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
643 #ifdef USE_TEMPERATURE_SENSOR
644 else if (label
[0] != '\0') {
645 uint8_t label_len
= strnlen(label
, TEMPERATURE_LABEL_LEN
);
646 memcpy(buff
, label
, label_len
);
647 memset(buff
+ label_len
, ' ', TEMPERATURE_LABEL_LEN
+ 1 - label_len
);
649 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
650 valueXOffset
= osdConfig()->temp_label_align
== OSD_ALIGN_LEFT
? 5 : label_len
+ 1;
658 if ((temperature
<= alarm_min
) || (temperature
>= alarm_max
)) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
659 if (osdConfig()->units
== OSD_UNIT_IMPERIAL
) temperature
= temperature
* 9 / 5.0f
+ 320;
660 tfp_sprintf(buff
, "%3d", temperature
/ 10);
665 buff
[3] = osdConfig()->units
== OSD_UNIT_IMPERIAL
? SYM_TEMP_F
: SYM_TEMP_C
;
668 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ valueXOffset
, elemPosY
, buff
, elemAttr
);
671 #ifdef USE_TEMPERATURE_SENSOR
672 static void osdDisplayTemperatureSensor(uint8_t elemPosX
, uint8_t elemPosY
, uint8_t sensorIndex
)
675 const bool valid
= getSensorTemperature(sensorIndex
, &temperature
);
676 const tempSensorConfig_t
*sensorConfig
= tempSensorConfig(sensorIndex
);
677 uint16_t symbol
= sensorConfig
->osdSymbol
? SYM_TEMP_SENSOR_FIRST
+ sensorConfig
->osdSymbol
- 1 : 0;
678 osdDisplayTemperature(elemPosX
, elemPosY
, symbol
, sensorConfig
->label
, valid
, temperature
, sensorConfig
->alarm_min
, sensorConfig
->alarm_max
);
682 static void osdFormatCoordinate(char *buff
, char sym
, int32_t val
)
684 // up to 4 for number + 1 for the symbol + null terminator + fill the rest with decimals
685 const int coordinateLength
= osdConfig()->coordinate_digits
+ 1;
688 int32_t integerPart
= val
/ GPS_DEGREES_DIVIDER
;
689 // Latitude maximum integer width is 3 (-90) while
690 // longitude maximum integer width is 4 (-180).
691 int integerDigits
= tfp_sprintf(buff
+ 1, (integerPart
== 0 && val
< 0) ? "-%d" : "%d", (int)integerPart
);
692 // We can show up to 7 digits in decimalPart.
693 int32_t decimalPart
= abs(val
% GPS_DEGREES_DIVIDER
);
694 STATIC_ASSERT(GPS_DEGREES_DIVIDER
== 1e7
, adjust_max_decimal_digits
);
695 int decimalDigits
= tfp_sprintf(buff
+ 1 + integerDigits
, "%07d", (int)decimalPart
);
696 // Embbed the decimal separator
697 buff
[1 + integerDigits
- 1] += SYM_ZERO_HALF_TRAILING_DOT
- '0';
698 buff
[1 + integerDigits
] += SYM_ZERO_HALF_LEADING_DOT
- '0';
699 // Fill up to coordinateLength with zeros
700 int total
= 1 + integerDigits
+ decimalDigits
;
701 while(total
< coordinateLength
) {
705 buff
[coordinateLength
] = '\0';
708 static void osdFormatCraftName(char *buff
)
710 if (strlen(systemConfig()->name
) == 0)
711 strcpy(buff
, "CRAFT_NAME");
713 for (int i
= 0; i
< MAX_NAME_LENGTH
; i
++) {
714 buff
[i
] = sl_toupper((unsigned char)systemConfig()->name
[i
]);
715 if (systemConfig()->name
[i
] == 0)
721 static const char * osdArmingDisabledReasonMessage(void)
723 const char *message
= NULL
;
724 char messageBuf
[MAX(SETTING_MAX_NAME_LENGTH
, OSD_MESSAGE_LENGTH
+1)];
726 switch (isArmingDisabledReason()) {
727 case ARMING_DISABLED_FAILSAFE_SYSTEM
:
728 // See handling of FAILSAFE_RX_LOSS_MONITORING in failsafe.c
729 if (failsafePhase() == FAILSAFE_RX_LOSS_MONITORING
) {
730 if (failsafeIsReceivingRxData()) {
731 // If we're not using sticks, it means the ARM switch
732 // hasn't been off since entering FAILSAFE_RX_LOSS_MONITORING
734 return OSD_MESSAGE_STR(OSD_MSG_TURN_ARM_SW_OFF
);
736 // Not receiving RX data
737 return OSD_MESSAGE_STR(OSD_MSG_RC_RX_LINK_LOST
);
739 return OSD_MESSAGE_STR(OSD_MSG_DISABLED_BY_FS
);
740 case ARMING_DISABLED_NOT_LEVEL
:
741 return OSD_MESSAGE_STR(OSD_MSG_AIRCRAFT_UNLEVEL
);
742 case ARMING_DISABLED_SENSORS_CALIBRATING
:
743 return OSD_MESSAGE_STR(OSD_MSG_SENSORS_CAL
);
744 case ARMING_DISABLED_SYSTEM_OVERLOADED
:
745 return OSD_MESSAGE_STR(OSD_MSG_SYS_OVERLOADED
);
746 case ARMING_DISABLED_NAVIGATION_UNSAFE
:
748 // Check the exact reason
749 switch (navigationIsBlockingArming(NULL
)) {
751 case NAV_ARMING_BLOCKER_NONE
:
753 case NAV_ARMING_BLOCKER_MISSING_GPS_FIX
:
754 return OSD_MESSAGE_STR(OSD_MSG_WAITING_GPS_FIX
);
755 case NAV_ARMING_BLOCKER_NAV_IS_ALREADY_ACTIVE
:
756 return OSD_MESSAGE_STR(OSD_MSG_DISABLE_NAV_FIRST
);
757 case NAV_ARMING_BLOCKER_FIRST_WAYPOINT_TOO_FAR
:
758 osdFormatDistanceSymbol(buf
, distanceToFirstWP(), 0);
759 tfp_sprintf(messageBuf
, "FIRST WP TOO FAR (%s)", buf
);
760 return message
= messageBuf
;
761 case NAV_ARMING_BLOCKER_JUMP_WAYPOINT_ERROR
:
762 return OSD_MESSAGE_STR(OSD_MSG_JUMP_WP_MISCONFIG
);
766 case ARMING_DISABLED_COMPASS_NOT_CALIBRATED
:
767 return OSD_MESSAGE_STR(OSD_MSG_MAG_NOT_CAL
);
768 case ARMING_DISABLED_ACCELEROMETER_NOT_CALIBRATED
:
769 return OSD_MESSAGE_STR(OSD_MSG_ACC_NOT_CAL
);
770 case ARMING_DISABLED_ARM_SWITCH
:
771 return OSD_MESSAGE_STR(OSD_MSG_DISARM_1ST
);
772 case ARMING_DISABLED_HARDWARE_FAILURE
:
774 if (!HW_SENSOR_IS_HEALTHY(getHwGyroStatus())) {
775 return OSD_MESSAGE_STR(OSD_MSG_GYRO_FAILURE
);
777 if (!HW_SENSOR_IS_HEALTHY(getHwAccelerometerStatus())) {
778 return OSD_MESSAGE_STR(OSD_MSG_ACC_FAIL
);
780 if (!HW_SENSOR_IS_HEALTHY(getHwCompassStatus())) {
781 return OSD_MESSAGE_STR(OSD_MSG_MAG_FAIL
);
783 if (!HW_SENSOR_IS_HEALTHY(getHwBarometerStatus())) {
784 return OSD_MESSAGE_STR(OSD_MSG_BARO_FAIL
);
786 if (!HW_SENSOR_IS_HEALTHY(getHwGPSStatus())) {
787 return OSD_MESSAGE_STR(OSD_MSG_GPS_FAIL
);
789 if (!HW_SENSOR_IS_HEALTHY(getHwRangefinderStatus())) {
790 return OSD_MESSAGE_STR(OSD_MSG_RANGEFINDER_FAIL
);
792 if (!HW_SENSOR_IS_HEALTHY(getHwPitotmeterStatus())) {
793 return OSD_MESSAGE_STR(OSD_MSG_PITOT_FAIL
);
796 return OSD_MESSAGE_STR(OSD_MSG_HW_FAIL
);
797 case ARMING_DISABLED_BOXFAILSAFE
:
798 return OSD_MESSAGE_STR(OSD_MSG_FS_EN
);
799 case ARMING_DISABLED_BOXKILLSWITCH
:
800 return OSD_MESSAGE_STR(OSD_MSG_KILL_SW_EN
);
801 case ARMING_DISABLED_RC_LINK
:
802 return OSD_MESSAGE_STR(OSD_MSG_NO_RC_LINK
);
803 case ARMING_DISABLED_THROTTLE
:
804 return OSD_MESSAGE_STR(OSD_MSG_THROTTLE_NOT_LOW
);
805 case ARMING_DISABLED_ROLLPITCH_NOT_CENTERED
:
806 return OSD_MESSAGE_STR(OSD_MSG_ROLLPITCH_OFFCENTER
);
807 case ARMING_DISABLED_SERVO_AUTOTRIM
:
808 return OSD_MESSAGE_STR(OSD_MSG_AUTOTRIM_ACTIVE
);
809 case ARMING_DISABLED_OOM
:
810 return OSD_MESSAGE_STR(OSD_MSG_NOT_ENOUGH_MEMORY
);
811 case ARMING_DISABLED_INVALID_SETTING
:
812 return OSD_MESSAGE_STR(OSD_MSG_INVALID_SETTING
);
813 case ARMING_DISABLED_CLI
:
814 return OSD_MESSAGE_STR(OSD_MSG_CLI_ACTIVE
);
815 case ARMING_DISABLED_PWM_OUTPUT_ERROR
:
816 return OSD_MESSAGE_STR(OSD_MSG_PWM_INIT_ERROR
);
817 case ARMING_DISABLED_NO_PREARM
:
818 return OSD_MESSAGE_STR(OSD_MSG_NO_PREARM
);
819 case ARMING_DISABLED_DSHOT_BEEPER
:
820 return OSD_MESSAGE_STR(OSD_MSG_DSHOT_BEEPER
);
821 // Cases without message
822 case ARMING_DISABLED_CMS_MENU
:
824 case ARMING_DISABLED_OSD_MENU
:
826 case ARMING_DISABLED_ALL_FLAGS
:
836 static const char * osdFailsafePhaseMessage(void)
838 // See failsafe.h for each phase explanation
839 switch (failsafePhase()) {
841 case FAILSAFE_RETURN_TO_HOME
:
842 // XXX: Keep this in sync with OSD_FLYMODE.
843 return OSD_MESSAGE_STR(OSD_MSG_RTH_FS
);
845 case FAILSAFE_LANDING
:
846 // This should be considered an emergengy landing
847 return OSD_MESSAGE_STR(OSD_MSG_EMERG_LANDING_FS
);
848 case FAILSAFE_RX_LOSS_MONITORING
:
849 // Only reachable from FAILSAFE_LANDED, which performs
850 // a disarm. Since aircraft has been disarmed, we no
851 // longer show failsafe details.
853 case FAILSAFE_LANDED
:
854 // Very brief, disarms and transitions into
855 // FAILSAFE_RX_LOSS_MONITORING. Note that it prevents
856 // further rearming via ARMING_DISABLED_FAILSAFE_SYSTEM,
857 // so we'll show the user how to re-arm in when
858 // that flag is the reason to prevent arming.
860 case FAILSAFE_RX_LOSS_IDLE
:
861 // This only happens when user has chosen NONE as FS
862 // procedure. The recovery messages should be enough.
865 // Failsafe not active
867 case FAILSAFE_RX_LOSS_DETECTED
:
868 // Very brief, changes to FAILSAFE_RX_LOSS_RECOVERED
869 // or the FS procedure immediately.
871 case FAILSAFE_RX_LOSS_RECOVERED
:
878 static const char * osdFailsafeInfoMessage(void)
880 if (failsafeIsReceivingRxData()) {
881 // User must move sticks to exit FS mode
882 return OSD_MESSAGE_STR(OSD_MSG_MOVE_EXIT_FS
);
884 return OSD_MESSAGE_STR(OSD_MSG_RC_RX_LINK_LOST
);
886 #if defined(USE_SAFE_HOME)
887 static const char * divertingToSafehomeMessage(void)
889 if (NAV_Status
.state
!= MW_NAV_STATE_HOVER_ABOVE_HOME
&& safehome_applied
) {
890 return OSD_MESSAGE_STR(OSD_MSG_DIVERT_SAFEHOME
);
896 static const char * navigationStateMessage(void)
898 switch (NAV_Status
.state
) {
899 case MW_NAV_STATE_NONE
:
901 case MW_NAV_STATE_RTH_START
:
902 return OSD_MESSAGE_STR(OSD_MSG_STARTING_RTH
);
903 case MW_NAV_STATE_RTH_CLIMB
:
904 return OSD_MESSAGE_STR(OSD_MSG_RTH_CLIMB
);
905 case MW_NAV_STATE_RTH_ENROUTE
:
906 return OSD_MESSAGE_STR(OSD_MSG_HEADING_HOME
);
907 case MW_NAV_STATE_HOLD_INFINIT
:
908 // Used by HOLD flight modes. No information to add.
910 case MW_NAV_STATE_HOLD_TIMED
:
911 // "HOLDING WP FOR xx S" Countdown added in osdGetSystemMessage
913 case MW_NAV_STATE_WP_ENROUTE
:
914 // "TO WP" + WP countdown added in osdGetSystemMessage
916 case MW_NAV_STATE_PROCESS_NEXT
:
917 return OSD_MESSAGE_STR(OSD_MSG_PREPARE_NEXT_WP
);
918 case MW_NAV_STATE_DO_JUMP
:
921 case MW_NAV_STATE_LAND_START
:
924 case MW_NAV_STATE_EMERGENCY_LANDING
:
925 return OSD_MESSAGE_STR(OSD_MSG_EMERG_LANDING
);
926 case MW_NAV_STATE_LAND_IN_PROGRESS
:
927 return OSD_MESSAGE_STR(OSD_MSG_LANDING
);
928 case MW_NAV_STATE_HOVER_ABOVE_HOME
:
929 if (STATE(FIXED_WING_LEGACY
)) {
930 #if defined(USE_SAFE_HOME)
931 if (safehome_applied
) {
932 return OSD_MESSAGE_STR(OSD_MSG_LOITERING_SAFEHOME
);
935 return OSD_MESSAGE_STR(OSD_MSG_LOITERING_HOME
);
937 return OSD_MESSAGE_STR(OSD_MSG_HOVERING
);
938 case MW_NAV_STATE_LANDED
:
939 return OSD_MESSAGE_STR(OSD_MSG_LANDED
);
940 case MW_NAV_STATE_LAND_SETTLE
:
941 return OSD_MESSAGE_STR(OSD_MSG_PREPARING_LAND
);
942 case MW_NAV_STATE_LAND_START_DESCENT
:
949 static void osdFormatMessage(char *buff
, size_t size
, const char *message
, bool isCenteredText
)
951 // String is always filled with Blanks
952 memset(buff
, SYM_BLANK
, size
);
954 size_t messageLength
= strlen(message
);
955 int rem
= isCenteredText
? MAX(0, (int)size
- (int)messageLength
) : 0;
956 strncpy(buff
+ rem
/ 2, message
, MIN((int)size
- rem
/ 2, (int)messageLength
));
958 // Ensure buff is zero terminated
959 buff
[size
- 1] = '\0';
963 * Draws the battery symbol filled in accordingly to the
964 * battery voltage to buff[0].
966 static void osdFormatBatteryChargeSymbol(char *buff
)
968 uint8_t p
= calculateBatteryPercentage();
969 p
= (100 - p
) / 16.6;
970 buff
[0] = SYM_BATT_FULL
+ p
;
973 static void osdUpdateBatteryCapacityOrVoltageTextAttributes(textAttributes_t
*attr
)
975 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && ((batteryUsesCapacityThresholds() && (getBatteryRemainingCapacity() <= currentBatteryProfile
->capacity
.warning
- currentBatteryProfile
->capacity
.critical
)) || ((!batteryUsesCapacityThresholds()) && (getBatteryVoltage() <= getBatteryWarningVoltage()))))
976 TEXT_ATTRIBUTES_ADD_BLINK(*attr
);
979 void osdCrosshairPosition(uint8_t *x
, uint8_t *y
)
981 *x
= osdDisplayPort
->cols
/ 2;
982 *y
= osdDisplayPort
->rows
/ 2;
983 *y
+= osdConfig()->horizon_offset
;
987 * Formats throttle position prefixed by its symbol.
988 * Shows output to motor, not stick position
990 static void osdFormatThrottlePosition(char *buff
, bool autoThr
, textAttributes_t
*elemAttr
)
994 if (autoThr
&& navigationIsControllingThrottle()) {
995 buff
[0] = SYM_AUTO_THR0
;
996 buff
[1] = SYM_AUTO_THR1
;
997 if (isFixedWingAutoThrottleManuallyIncreased()) {
998 TEXT_ATTRIBUTES_ADD_BLINK(*elemAttr
);
1001 #ifdef USE_POWER_LIMITS
1002 if (powerLimiterIsLimiting()) {
1003 TEXT_ATTRIBUTES_ADD_BLINK(*elemAttr
);
1006 tfp_sprintf(buff
+ 2, "%3d", getThrottlePercent());
1010 * Formats gvars prefixed by its number (0-indexed). If autoThr
1012 static void osdFormatGVar(char *buff
, uint8_t index
)
1015 buff
[1] = '0'+index
;
1017 #ifdef USE_PROGRAMMING_FRAMEWORK
1018 osdFormatCentiNumber(buff
+ 3, (int32_t)gvGet(index
)*(int32_t)100, 1, 0, 0, 5);
1022 #if defined(USE_ESC_SENSOR)
1023 static void osdFormatRpm(char *buff
, uint32_t rpm
)
1028 osdFormatCentiNumber(buff
+ 1, rpm
/ 10, 0, 1, 1, 2);
1033 tfp_sprintf(buff
+ 1, "%3lu", rpm
);
1037 strcpy(buff
+ 1, "---");
1042 int32_t osdGetAltitude(void)
1044 #if defined(USE_NAV)
1045 return getEstimatedActualPosition(Z
);
1046 #elif defined(USE_BARO)
1053 static inline int32_t osdGetAltitudeMsl(void)
1055 #if defined(USE_NAV)
1056 return getEstimatedActualPosition(Z
)+GPS_home
.alt
;
1057 #elif defined(USE_BARO)
1058 return baro
.alt
+GPS_home
.alt
;
1064 static bool osdIsHeadingValid(void)
1066 #ifdef USE_SECONDARY_IMU
1067 if (secondaryImuState
.active
&& secondaryImuConfig()->useForOsdHeading
) {
1070 return isImuHeadingValid();
1073 return isImuHeadingValid();
1077 int16_t osdGetHeading(void)
1079 #ifdef USE_SECONDARY_IMU
1080 if (secondaryImuState
.active
&& secondaryImuConfig()->useForOsdHeading
) {
1081 return secondaryImuState
.eulerAngles
.values
.yaw
;
1083 return attitude
.values
.yaw
;
1086 return attitude
.values
.yaw
;
1090 int16_t osdPanServoHomeDirectionOffset(void)
1092 int8_t servoIndex
= osdConfig()->pan_servo_index
;
1093 int16_t servoPosition
= servo
[servoIndex
];
1094 int16_t servoMiddle
= servoParams(servoIndex
)->middle
;
1095 return (int16_t)CENTIDEGREES_TO_DEGREES((servoPosition
- servoMiddle
) * osdConfig()->pan_servo_pwm2centideg
);
1098 // Returns a heading angle in degrees normalized to [0, 360).
1099 int osdGetHeadingAngle(int angle
)
1104 while (angle
>= 360) {
1110 #if defined(USE_GPS)
1112 /* Draws a map with the given symbol in the center and given point of interest
1113 * defined by its distance in meters and direction in degrees.
1114 * referenceHeading indicates the up direction in the map, in degrees, while
1115 * referenceSym (if non-zero) is drawn at the upper right corner below a small
1116 * arrow to indicate the map reference to the user. The drawn argument is an
1117 * in-out used to store the last position where the craft was drawn to avoid
1118 * erasing all screen on each redraw.
1120 static void osdDrawMap(int referenceHeading
, uint16_t referenceSym
, uint16_t centerSym
,
1121 uint32_t poiDistance
, int16_t poiDirection
, uint16_t poiSymbol
,
1122 uint16_t *drawn
, uint32_t *usedScale
)
1124 // TODO: These need to be tested with several setups. We might
1125 // need to make them configurable.
1126 const int hMargin
= 5;
1127 const int vMargin
= 3;
1129 // TODO: Get this from the display driver?
1130 const int charWidth
= 12;
1131 const int charHeight
= 18;
1133 uint8_t minX
= hMargin
;
1134 uint8_t maxX
= osdDisplayPort
->cols
- 1 - hMargin
;
1135 uint8_t minY
= vMargin
;
1136 uint8_t maxY
= osdDisplayPort
->rows
- 1 - vMargin
;
1137 uint8_t midX
= osdDisplayPort
->cols
/ 2;
1138 uint8_t midY
= osdDisplayPort
->rows
/ 2;
1141 displayWriteChar(osdDisplayPort
, midX
, midY
, centerSym
);
1143 // First, erase the previous drawing.
1144 if (OSD_VISIBLE(*drawn
)) {
1145 displayWriteChar(osdDisplayPort
, OSD_X(*drawn
), OSD_Y(*drawn
), SYM_BLANK
);
1149 uint32_t initialScale
;
1150 const unsigned scaleMultiplier
= 2;
1151 // We try to reduce the scale when the POI will be around half the distance
1152 // between the center and the closers map edge, to avoid too much jumping
1153 const int scaleReductionMultiplier
= MIN(midX
- hMargin
, midY
- vMargin
) / 2;
1155 switch (osdConfig()->units
) {
1158 case OSD_UNIT_IMPERIAL
:
1159 initialScale
= 16; // 16m ~= 0.01miles
1162 initialScale
= 18; // 18m ~= 0.01 nautical miles
1165 case OSD_UNIT_METRIC_MPH
:
1167 case OSD_UNIT_METRIC
:
1168 initialScale
= 10; // 10m as initial scale
1172 // Try to keep the same scale when getting closer until we draw over the center point
1173 uint32_t scale
= initialScale
;
1176 if (scale
> initialScale
&& poiDistance
< *usedScale
* scaleReductionMultiplier
) {
1177 scale
/= scaleMultiplier
;
1181 if (STATE(GPS_FIX
)) {
1183 int directionToPoi
= osdGetHeadingAngle(poiDirection
- referenceHeading
);
1184 float poiAngle
= DEGREES_TO_RADIANS(directionToPoi
);
1185 float poiSin
= sin_approx(poiAngle
);
1186 float poiCos
= cos_approx(poiAngle
);
1188 // Now start looking for a valid scale that lets us draw everything
1190 for (ii
= 0; ii
< 50; ii
++) {
1191 // Calculate location of the aircraft in map
1192 int points
= poiDistance
/ ((float)scale
/ charHeight
);
1194 float pointsX
= points
* poiSin
;
1195 int poiX
= midX
- roundf(pointsX
/ charWidth
);
1196 if (poiX
< minX
|| poiX
> maxX
) {
1197 scale
*= scaleMultiplier
;
1201 float pointsY
= points
* poiCos
;
1202 int poiY
= midY
+ roundf(pointsY
/ charHeight
);
1203 if (poiY
< minY
|| poiY
> maxY
) {
1204 scale
*= scaleMultiplier
;
1208 if (poiX
== midX
&& poiY
== midY
) {
1209 // We're over the map center symbol, so we would be drawing
1210 // over it even if we increased the scale. Alternate between
1211 // drawing the center symbol or drawing the POI.
1212 if (centerSym
!= SYM_BLANK
&& OSD_ALTERNATING_CHOICES(1000, 2) == 0) {
1218 if (displayReadCharWithAttr(osdDisplayPort
, poiX
, poiY
, &c
, NULL
) && c
!= SYM_BLANK
) {
1219 // Something else written here, increase scale. If the display doesn't support reading
1220 // back characters, we assume there's nothing.
1222 // If we're close to the center, decrease scale. Otherwise increase it.
1223 uint8_t centerDeltaX
= (maxX
- minX
) / (scaleMultiplier
* 2);
1224 uint8_t centerDeltaY
= (maxY
- minY
) / (scaleMultiplier
* 2);
1225 if (poiX
>= midX
- centerDeltaX
&& poiX
<= midX
+ centerDeltaX
&&
1226 poiY
>= midY
- centerDeltaY
&& poiY
<= midY
+ centerDeltaY
&&
1227 scale
> scaleMultiplier
) {
1229 scale
/= scaleMultiplier
;
1231 scale
*= scaleMultiplier
;
1237 // Draw the point on the map
1238 if (poiSymbol
== SYM_ARROW_UP
) {
1239 // Drawing aircraft, rotate
1240 int mapHeading
= osdGetHeadingAngle(DECIDEGREES_TO_DEGREES(osdGetHeading()) - referenceHeading
);
1241 poiSymbol
+= mapHeading
* 2 / 45;
1243 displayWriteChar(osdDisplayPort
, poiX
, poiY
, poiSymbol
);
1245 // Update saved location
1246 *drawn
= OSD_POS(poiX
, poiY
) | OSD_VISIBLE_FLAG
;
1253 // Update global map data for scale and reference
1254 osdMapData
.scale
= scale
;
1255 osdMapData
.referenceSymbol
= referenceSym
;
1258 /* Draws a map with the home in the center and the craft moving around.
1259 * See osdDrawMap() for reference.
1261 static void osdDrawHomeMap(int referenceHeading
, uint8_t referenceSym
, uint16_t *drawn
, uint32_t *usedScale
)
1263 osdDrawMap(referenceHeading
, referenceSym
, SYM_HOME
, GPS_distanceToHome
, GPS_directionToHome
, SYM_ARROW_UP
, drawn
, usedScale
);
1266 /* Draws a map with the aircraft in the center and the home moving around.
1267 * See osdDrawMap() for reference.
1269 static void osdDrawRadar(uint16_t *drawn
, uint32_t *usedScale
)
1271 int16_t reference
= DECIDEGREES_TO_DEGREES(osdGetHeading());
1272 int16_t poiDirection
= osdGetHeadingAngle(GPS_directionToHome
+ 180);
1273 osdDrawMap(reference
, 0, SYM_ARROW_UP
, GPS_distanceToHome
, poiDirection
, SYM_HOME
, drawn
, usedScale
);
1276 static uint16_t crc_accumulate(uint8_t data
, uint16_t crcAccum
)
1279 tmp
= data
^ (uint8_t)(crcAccum
& 0xff);
1281 crcAccum
= (crcAccum
>> 8) ^ (tmp
<< 8) ^ (tmp
<< 3) ^ (tmp
>> 4);
1286 static void osdDisplayTelemetry(void)
1289 uint16_t trk_crc
= 0;
1290 char trk_buffer
[31];
1291 static int16_t trk_elevation
= 127;
1292 static uint16_t trk_bearing
= 0;
1294 if (ARMING_FLAG(ARMED
)) {
1295 if (STATE(GPS_FIX
)){
1296 if (GPS_distanceToHome
> 5) {
1297 trk_bearing
= GPS_directionToHome
;
1298 trk_bearing
+= 360 + 180;
1300 int32_t alt
= CENTIMETERS_TO_METERS(osdGetAltitude());
1301 float at
= atan2(alt
, GPS_distanceToHome
);
1302 trk_elevation
= (float)at
* 57.2957795; // 57.2957795 = 1 rad
1303 trk_elevation
+= 37; // because elevation in telemetry should be from -37 to 90
1304 if (trk_elevation
< 0) {
1311 trk_elevation
= 127;
1315 trk_data
= 0; // bit 0 - packet type 0 = bearing/elevation, 1 = 2 byte data packet
1316 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.
1317 trk_data
= trk_data
| (uint32_t)trk_bearing
<< 8; // bits 8-17 - bearing angle to target. 0 = true north. 0 to 360
1318 trk_crc
= crc_accumulate(0xFF & trk_data
, trk_crc
); // CRC First Byte bits 0-7
1319 trk_crc
= crc_accumulate(0xFF & trk_bearing
, trk_crc
); // CRC Second Byte bits 8-15
1320 trk_crc
= crc_accumulate(trk_bearing
>> 8, trk_crc
); // CRC Third Byte bits 16-17
1321 trk_data
= trk_data
| (uint32_t)trk_crc
<< 17; // bits 18-29 CRC & 0x3FFFF
1323 for (uint8_t t_ctr
= 0; t_ctr
< 30; t_ctr
++) { // Prepare screen buffer and write data line.
1324 if (trk_data
& (uint32_t)1 << t_ctr
){
1325 trk_buffer
[29 - t_ctr
] = SYM_TELEMETRY_0
;
1328 trk_buffer
[29 - t_ctr
] = SYM_TELEMETRY_1
;
1332 displayWrite(osdDisplayPort
, 0, 0, trk_buffer
);
1333 if (osdConfig()->telemetry
>1){
1334 displayWrite(osdDisplayPort
, 0, 3, trk_buffer
); // Test display because normal telemetry line is not visible
1339 static void osdFormatPidControllerOutput(char *buff
, const char *label
, const pidController_t
*pidController
, uint8_t scale
, bool showDecimal
) {
1340 strcpy(buff
, label
);
1341 for (uint8_t i
= strlen(label
); i
< 5; ++i
) buff
[i
] = ' ';
1342 uint8_t decimals
= showDecimal
? 1 : 0;
1343 osdFormatCentiNumber(buff
+ 5, pidController
->proportional
* scale
, 0, decimals
, 0, 4);
1345 osdFormatCentiNumber(buff
+ 10, pidController
->integrator
* scale
, 0, decimals
, 0, 4);
1347 osdFormatCentiNumber(buff
+ 15, pidController
->derivative
* scale
, 0, decimals
, 0, 4);
1349 osdFormatCentiNumber(buff
+ 20, pidController
->output_constrained
* scale
, 0, decimals
, 0, 4);
1353 static void osdDisplayBatteryVoltage(uint8_t elemPosX
, uint8_t elemPosY
, uint16_t voltage
, uint8_t digits
, uint8_t decimals
)
1356 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
1358 osdFormatBatteryChargeSymbol(buff
);
1360 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1361 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
1363 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1364 digits
= MIN(digits
, 4);
1365 osdFormatCentiNumber(buff
, voltage
, 0, decimals
, 0, digits
);
1366 buff
[digits
] = SYM_VOLT
;
1367 buff
[digits
+1] = '\0';
1368 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && (getBatteryVoltage() <= getBatteryWarningVoltage()))
1369 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1370 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 1, elemPosY
, buff
, elemAttr
);
1373 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
)
1375 textAttributes_t elemAttr
;
1378 const pid8_t
*pid
= &pidBank()->pid
[pidIndex
];
1379 pidType_e pidType
= pidIndexGetType(pidIndex
);
1381 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1383 if (pidType
== PID_TYPE_NONE
) {
1384 // PID is not used in this configuration. Draw dashes.
1385 // XXX: Keep this in sync with the %3d format and spacing used below
1386 displayWrite(osdDisplayPort
, elemPosX
+ 6, elemPosY
, "- - - -");
1390 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1391 tfp_sprintf(buff
, "%3d", pid
->P
);
1392 if ((isAdjustmentFunctionSelected(adjFuncP
)) || (((adjFuncP
== ADJUSTMENT_ROLL_P
) || (adjFuncP
== ADJUSTMENT_PITCH_P
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_P
))))
1393 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1394 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1396 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1397 tfp_sprintf(buff
, "%3d", pid
->I
);
1398 if ((isAdjustmentFunctionSelected(adjFuncI
)) || (((adjFuncI
== ADJUSTMENT_ROLL_I
) || (adjFuncI
== ADJUSTMENT_PITCH_I
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_I
))))
1399 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1400 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 8, elemPosY
, buff
, elemAttr
);
1402 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1403 tfp_sprintf(buff
, "%3d", pid
->D
);
1404 if ((isAdjustmentFunctionSelected(adjFuncD
)) || (((adjFuncD
== ADJUSTMENT_ROLL_D
) || (adjFuncD
== ADJUSTMENT_PITCH_D
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_D
))))
1405 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1406 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 12, elemPosY
, buff
, elemAttr
);
1408 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1409 tfp_sprintf(buff
, "%3d", pid
->FF
);
1410 if ((isAdjustmentFunctionSelected(adjFuncFF
)) || (((adjFuncFF
== ADJUSTMENT_ROLL_FF
) || (adjFuncFF
== ADJUSTMENT_PITCH_FF
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_FF
))))
1411 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1412 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 16, elemPosY
, buff
, elemAttr
);
1415 static void osdDisplayNavPIDValues(uint8_t elemPosX
, uint8_t elemPosY
, const char *str
, pidIndex_e pidIndex
, adjustmentFunction_e adjFuncP
, adjustmentFunction_e adjFuncI
, adjustmentFunction_e adjFuncD
)
1417 textAttributes_t elemAttr
;
1420 const pid8_t
*pid
= &pidBank()->pid
[pidIndex
];
1421 pidType_e pidType
= pidIndexGetType(pidIndex
);
1423 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1425 if (pidType
== PID_TYPE_NONE
) {
1426 // PID is not used in this configuration. Draw dashes.
1427 // XXX: Keep this in sync with the %3d format and spacing used below
1428 displayWrite(osdDisplayPort
, elemPosX
+ 6, elemPosY
, "- - -");
1432 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1433 tfp_sprintf(buff
, "%3d", pid
->P
);
1434 if ((isAdjustmentFunctionSelected(adjFuncP
)) || (((adjFuncP
== ADJUSTMENT_ROLL_P
) || (adjFuncP
== ADJUSTMENT_PITCH_P
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_P
))))
1435 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1436 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1438 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1439 tfp_sprintf(buff
, "%3d", pid
->I
);
1440 if ((isAdjustmentFunctionSelected(adjFuncI
)) || (((adjFuncI
== ADJUSTMENT_ROLL_I
) || (adjFuncI
== ADJUSTMENT_PITCH_I
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_I
))))
1441 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1442 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 8, elemPosY
, buff
, elemAttr
);
1444 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1445 tfp_sprintf(buff
, "%3d", pidType
== PID_TYPE_PIFF
? pid
->FF
: pid
->D
);
1446 if ((isAdjustmentFunctionSelected(adjFuncD
)) || (((adjFuncD
== ADJUSTMENT_ROLL_D
) || (adjFuncD
== ADJUSTMENT_PITCH_D
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_D
))))
1447 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1448 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 12, elemPosY
, buff
, elemAttr
);
1451 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
) {
1453 textAttributes_t elemAttr
;
1454 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1456 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1457 osdFormatCentiNumber(buff
, value
* 100, 0, maxDecimals
, 0, MIN(valueLength
, 8));
1458 if (isAdjustmentFunctionSelected(adjFunc
))
1459 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1460 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ strlen(str
) + 1 + valueOffset
, elemPosY
, buff
, elemAttr
);
1463 int8_t getGeoWaypointNumber(int8_t waypointIndex
)
1465 static int8_t lastWaypointIndex
= 1;
1466 static int8_t geoWaypointIndex
;
1468 if (waypointIndex
!= lastWaypointIndex
) {
1469 lastWaypointIndex
= geoWaypointIndex
= waypointIndex
;
1470 for (uint8_t i
= 0; i
<= waypointIndex
; i
++) {
1471 if (posControl
.waypointList
[i
].action
== NAV_WP_ACTION_SET_POI
||
1472 posControl
.waypointList
[i
].action
== NAV_WP_ACTION_SET_HEAD
||
1473 posControl
.waypointList
[i
].action
== NAV_WP_ACTION_JUMP
) {
1474 geoWaypointIndex
-= 1;
1479 return geoWaypointIndex
+ 1;
1482 static bool osdDrawSingleElement(uint8_t item
)
1484 uint16_t pos
= osdLayoutsConfig()->item_pos
[currentLayout
][item
];
1485 if (!OSD_VISIBLE(pos
)) {
1488 uint8_t elemPosX
= OSD_X(pos
);
1489 uint8_t elemPosY
= OSD_Y(pos
);
1490 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
1491 char buff
[32] = {0};
1494 case OSD_RSSI_VALUE
:
1496 uint16_t osdRssi
= osdConvertRSSI();
1498 tfp_sprintf(buff
+ 1, "%2d", osdRssi
);
1499 if (osdRssi
< osdConfig()->rssi_alarm
) {
1500 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1505 case OSD_MAIN_BATT_VOLTAGE
:
1506 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatteryRawVoltage(), 2 + osdConfig()->main_voltage_decimals
, osdConfig()->main_voltage_decimals
);
1509 case OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
:
1510 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatterySagCompensatedVoltage(), 2 + osdConfig()->main_voltage_decimals
, osdConfig()->main_voltage_decimals
);
1513 case OSD_CURRENT_DRAW
:
1514 osdFormatCentiNumber(buff
, getAmperage(), 0, 2, 0, 3);
1518 uint8_t current_alarm
= osdConfig()->current_alarm
;
1519 if ((current_alarm
> 0) && ((getAmperage() / 100.0f
) > current_alarm
)) {
1520 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1525 tfp_sprintf(buff
, "%4d", (int)getMAhDrawn());
1528 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1532 osdFormatCentiNumber(buff
, getMWhDrawn() / 10, 0, 2, 0, 3);
1533 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1538 case OSD_BATTERY_REMAINING_CAPACITY
:
1540 if (currentBatteryProfile
->capacity
.value
== 0)
1541 tfp_sprintf(buff
, " NA");
1542 else if (!batteryWasFullWhenPluggedIn())
1543 tfp_sprintf(buff
, " NF");
1544 else if (currentBatteryProfile
->capacity
.unit
== BAT_CAPACITY_UNIT_MAH
)
1545 tfp_sprintf(buff
, "%4lu", getBatteryRemainingCapacity());
1546 else // currentBatteryProfile->capacity.unit == BAT_CAPACITY_UNIT_MWH
1547 osdFormatCentiNumber(buff
+ 1, getBatteryRemainingCapacity() / 10, 0, 2, 0, 3);
1549 buff
[4] = currentBatteryProfile
->capacity
.unit
== BAT_CAPACITY_UNIT_MAH
? SYM_MAH
: SYM_WH
;
1552 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && batteryUsesCapacityThresholds() && (getBatteryRemainingCapacity() <= currentBatteryProfile
->capacity
.warning
- currentBatteryProfile
->capacity
.critical
))
1553 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1557 case OSD_BATTERY_REMAINING_PERCENT
:
1558 tfp_sprintf(buff
, "%3d%%", calculateBatteryPercentage());
1559 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1562 case OSD_POWER_SUPPLY_IMPEDANCE
:
1563 if (isPowerSupplyImpedanceValid())
1564 tfp_sprintf(buff
, "%3d", getPowerSupplyImpedance());
1566 strcpy(buff
, "---");
1567 buff
[3] = SYM_MILLIOHM
;
1573 buff
[0] = SYM_SAT_L
;
1574 buff
[1] = SYM_SAT_R
;
1575 tfp_sprintf(buff
+ 2, "%2d", gpsSol
.numSat
);
1576 if (!STATE(GPS_FIX
)) {
1577 if (getHwGPSStatus() == HW_SENSOR_UNAVAILABLE
|| getHwGPSStatus() == HW_SENSOR_UNHEALTHY
) {
1578 strcpy(buff
+ 2, "X!");
1580 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1585 osdFormatVelocityStr(buff
, gpsSol
.groundSpeed
, false, false);
1588 case OSD_GPS_MAX_SPEED
:
1589 osdFormatVelocityStr(buff
, stats
.max_speed
, false, true);
1593 osdFormatVelocityStr(buff
, osdGet3DSpeed(), true, false);
1596 case OSD_3D_MAX_SPEED
:
1597 osdFormatVelocityStr(buff
, stats
.max_3D_speed
, true, true);
1600 case OSD_GLIDESLOPE
:
1602 float horizontalSpeed
= gpsSol
.groundSpeed
;
1603 float sinkRate
= -getEstimatedActualVelocity(Z
);
1604 static pt1Filter_t gsFilterState
;
1605 const timeMs_t currentTimeMs
= millis();
1606 static timeMs_t gsUpdatedTimeMs
;
1607 float glideSlope
= horizontalSpeed
/ sinkRate
;
1608 glideSlope
= pt1FilterApply4(&gsFilterState
, isnormal(glideSlope
) ? glideSlope
: 200, 0.5, MS2S(currentTimeMs
- gsUpdatedTimeMs
));
1609 gsUpdatedTimeMs
= currentTimeMs
;
1611 buff
[0] = SYM_GLIDESLOPE
;
1612 if (glideSlope
> 0.0f
&& glideSlope
< 100.0f
) {
1613 osdFormatCentiNumber(buff
+ 1, glideSlope
* 100.0f
, 0, 2, 0, 3);
1615 buff
[1] = buff
[2] = buff
[3] = '-';
1622 osdFormatCoordinate(buff
, SYM_LAT
, gpsSol
.llh
.lat
);
1626 osdFormatCoordinate(buff
, SYM_LON
, gpsSol
.llh
.lon
);
1631 if (STATE(GPS_FIX
) && STATE(GPS_FIX_HOME
) && isImuHeadingValid()) {
1632 if (GPS_distanceToHome
< (navConfig()->general
.min_rth_distance
/ 100) ) {
1633 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
, SYM_HOME_NEAR
);
1637 int16_t panHomeDirOffset
= 0;
1638 if (!(osdConfig()->pan_servo_pwm2centideg
== 0)){
1639 panHomeDirOffset
= osdPanServoHomeDirectionOffset();
1641 int homeDirection
= GPS_directionToHome
- DECIDEGREES_TO_DEGREES(osdGetHeading()) + panHomeDirOffset
;
1642 osdDrawDirArrow(osdDisplayPort
, osdGetDisplayPortCanvas(), OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), homeDirection
);
1645 // No home or no fix or unknown heading, blink.
1646 // If we're unarmed, show the arrow pointing up so users can see the arrow
1647 // while configuring the OSD. If we're armed, show a '-' indicating that
1648 // we don't know the direction to home.
1649 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1650 displayWriteCharWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, ARMING_FLAG(ARMED
) ? '-' : SYM_ARROW_UP
, elemAttr
);
1655 case OSD_HOME_HEADING_ERROR
:
1658 buff
[1] = SYM_HEADING
;
1660 if (isImuHeadingValid() && navigationPositionEstimateIsHealthy()) {
1661 int16_t h
= lrintf(CENTIDEGREES_TO_DEGREES((float)wrap_18000(DEGREES_TO_CENTIDEGREES((int32_t)GPS_directionToHome
) - DECIDEGREES_TO_CENTIDEGREES((int32_t)osdGetHeading()))));
1662 tfp_sprintf(buff
+ 2, "%4d", h
);
1664 strcpy(buff
+ 2, "----");
1667 buff
[6] = SYM_DEGREES
;
1675 osdFormatDistanceSymbol(&buff
[1], GPS_distanceToHome
* 100, 0);
1676 uint16_t dist_alarm
= osdConfig()->dist_alarm
;
1677 if (dist_alarm
> 0 && GPS_distanceToHome
> dist_alarm
) {
1678 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1684 buff
[0] = SYM_TOTAL
;
1685 osdFormatDistanceSymbol(buff
+ 1, getTotalTravelDistance(), 0);
1690 buff
[0] = SYM_HEADING
;
1691 if (osdIsHeadingValid()) {
1692 int16_t h
= DECIDEGREES_TO_DEGREES(osdGetHeading());
1696 tfp_sprintf(&buff
[1], "%3d", h
);
1698 buff
[1] = buff
[2] = buff
[3] = '-';
1700 buff
[4] = SYM_DEGREES
;
1705 case OSD_COURSE_HOLD_ERROR
:
1707 if (ARMING_FLAG(ARMED
) && !FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) {
1708 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, " ");
1712 buff
[0] = SYM_HEADING
;
1714 if ((!ARMING_FLAG(ARMED
)) || (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
) && isAdjustingPosition())) {
1715 buff
[1] = buff
[2] = buff
[3] = '-';
1716 } else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) {
1717 int16_t herr
= lrintf(CENTIDEGREES_TO_DEGREES((float)navigationGetHeadingError()));
1719 strcpy(buff
+ 1, ">99");
1721 tfp_sprintf(buff
+ 1, "%3d", herr
);
1724 buff
[4] = SYM_DEGREES
;
1729 case OSD_COURSE_HOLD_ADJUSTMENT
:
1731 int16_t heading_adjust
= lrintf(CENTIDEGREES_TO_DEGREES((float)getCruiseHeadingAdjustment()));
1733 if (ARMING_FLAG(ARMED
) && ((!FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) || !(isAdjustingPosition() || isAdjustingHeading() || (heading_adjust
!= 0)))) {
1734 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, " ");
1738 buff
[0] = SYM_HEADING
;
1740 if (!ARMING_FLAG(ARMED
)) {
1741 buff
[1] = buff
[2] = buff
[3] = buff
[4] = '-';
1742 } else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) {
1743 tfp_sprintf(buff
+ 1, "%4d", heading_adjust
);
1746 buff
[5] = SYM_DEGREES
;
1753 buff
[0] = SYM_HDP_L
;
1754 buff
[1] = SYM_HDP_R
;
1755 int32_t centiHDOP
= 100 * gpsSol
.hdop
/ HDOP_SCALE
;
1756 osdFormatCentiNumber(&buff
[2], centiHDOP
, 0, 1, 0, 2);
1762 static uint16_t drawn
= 0;
1763 static uint32_t scale
= 0;
1764 osdDrawHomeMap(0, 'N', &drawn
, &scale
);
1767 case OSD_MAP_TAKEOFF
:
1769 static uint16_t drawn
= 0;
1770 static uint32_t scale
= 0;
1771 osdDrawHomeMap(CENTIDEGREES_TO_DEGREES(navigationGetHomeHeading()), 'T', &drawn
, &scale
);
1776 static uint16_t drawn
= 0;
1777 static uint32_t scale
= 0;
1778 osdDrawRadar(&drawn
, &scale
);
1785 int32_t alt
= osdGetAltitude();
1786 osdFormatAltitudeSymbol(buff
, alt
);
1787 uint16_t alt_alarm
= osdConfig()->alt_alarm
;
1788 uint16_t neg_alt_alarm
= osdConfig()->neg_alt_alarm
;
1789 if ((alt_alarm
> 0 && CENTIMETERS_TO_METERS(alt
) > alt_alarm
) ||
1790 (neg_alt_alarm
> 0 && alt
< 0 && -CENTIMETERS_TO_METERS(alt
) > neg_alt_alarm
)) {
1792 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1797 case OSD_ALTITUDE_MSL
:
1799 int32_t alt
= osdGetAltitudeMsl();
1800 osdFormatAltitudeSymbol(buff
, alt
);
1804 #ifdef USE_RANGEFINDER
1805 case OSD_RANGEFINDER
:
1807 int32_t range
= rangefinderGetLatestRawAltitude();
1811 osdFormatDistanceSymbol(buff
, range
, 1);
1819 osdFormatOnTime(buff
);
1825 osdFormatFlyTime(buff
, &elemAttr
);
1829 case OSD_ONTIME_FLYTIME
:
1831 if (ARMING_FLAG(ARMED
)) {
1832 osdFormatFlyTime(buff
, &elemAttr
);
1834 osdFormatOnTime(buff
);
1839 case OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
:
1841 /*static int32_t updatedTimeSeconds = 0;*/
1842 static int32_t timeSeconds
= -1;
1843 #if defined(USE_ADC) && defined(USE_GPS)
1844 static timeUs_t updatedTimestamp
= 0;
1845 timeUs_t currentTimeUs
= micros();
1846 if (cmpTimeUs(currentTimeUs
, updatedTimestamp
) >= MS2US(1000)) {
1847 #ifdef USE_WIND_ESTIMATOR
1848 timeSeconds
= calculateRemainingFlightTimeBeforeRTH(osdConfig()->estimations_wind_compensation
);
1850 timeSeconds
= calculateRemainingFlightTimeBeforeRTH(false);
1852 updatedTimestamp
= currentTimeUs
;
1855 if ((!ARMING_FLAG(ARMED
)) || (timeSeconds
== -1)) {
1856 buff
[0] = SYM_FLY_M
;
1857 strcpy(buff
+ 1, "--:--");
1858 #if defined(USE_ADC) && defined(USE_GPS)
1859 updatedTimestamp
= 0;
1861 } else if (timeSeconds
== -2) {
1862 // Wind is too strong to come back with cruise throttle
1863 buff
[0] = SYM_FLY_M
;
1864 buff
[1] = buff
[2] = buff
[4] = buff
[5] = SYM_WIND_HORIZONTAL
;
1867 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1869 osdFormatTime(buff
, timeSeconds
, SYM_FLY_M
, SYM_FLY_H
);
1870 if (timeSeconds
== 0)
1871 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1876 case OSD_REMAINING_DISTANCE_BEFORE_RTH
:;
1877 static int32_t distanceMeters
= -1;
1878 #if defined(USE_ADC) && defined(USE_GPS)
1879 static timeUs_t updatedTimestamp
= 0;
1880 timeUs_t currentTimeUs
= micros();
1881 if (cmpTimeUs(currentTimeUs
, updatedTimestamp
) >= MS2US(1000)) {
1882 #ifdef USE_WIND_ESTIMATOR
1883 distanceMeters
= calculateRemainingDistanceBeforeRTH(osdConfig()->estimations_wind_compensation
);
1885 distanceMeters
= calculateRemainingDistanceBeforeRTH(false);
1887 updatedTimestamp
= currentTimeUs
;
1890 buff
[0] = SYM_TRIP_DIST
;
1891 if ((!ARMING_FLAG(ARMED
)) || (distanceMeters
== -1)) {
1892 buff
[4] = SYM_BLANK
;
1894 strcpy(buff
+ 1, "---");
1895 } else if (distanceMeters
== -2) {
1896 // Wind is too strong to come back with cruise throttle
1897 buff
[1] = buff
[2] = buff
[3] = SYM_WIND_HORIZONTAL
;
1898 switch ((osd_unit_e
)osdConfig()->units
){
1901 case OSD_UNIT_IMPERIAL
:
1902 buff
[4] = SYM_DIST_MI
;
1904 case OSD_UNIT_METRIC_MPH
:
1906 case OSD_UNIT_METRIC
:
1907 buff
[4] = SYM_DIST_KM
;
1910 buff
[4] = SYM_DIST_NM
;
1914 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1916 osdFormatDistanceSymbol(buff
+ 1, distanceMeters
* 100, 0);
1917 if (distanceMeters
== 0)
1918 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1926 if (FLIGHT_MODE(FAILSAFE_MODE
))
1928 else if (FLIGHT_MODE(MANUAL_MODE
))
1930 else if (FLIGHT_MODE(TURTLE_MODE
))
1932 else if (FLIGHT_MODE(NAV_RTH_MODE
))
1933 p
= isWaypointMissionRTHActive() ? "WRTH" : "RTH ";
1934 else if (FLIGHT_MODE(NAV_POSHOLD_MODE
))
1936 else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
) && FLIGHT_MODE(NAV_ALTHOLD_MODE
))
1938 else if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE
))
1940 else if (FLIGHT_MODE(NAV_WP_MODE
))
1942 else if (FLIGHT_MODE(NAV_ALTHOLD_MODE
) && navigationRequiresAngleMode()) {
1943 // If navigationRequiresAngleMode() returns false when ALTHOLD is active,
1944 // it means it can be combined with ANGLE, HORIZON, ACRO, etc...
1945 // and its display is handled by OSD_MESSAGES rather than OSD_FLYMODE.
1948 else if (FLIGHT_MODE(ANGLE_MODE
))
1950 else if (FLIGHT_MODE(HORIZON_MODE
))
1953 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, p
);
1957 case OSD_CRAFT_NAME
:
1958 osdFormatCraftName(buff
);
1961 case OSD_THROTTLE_POS
:
1963 osdFormatThrottlePosition(buff
, false, NULL
);
1967 case OSD_VTX_CHANNEL
:
1969 vtxDeviceOsdInfo_t osdInfo
;
1970 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
1972 tfp_sprintf(buff
, "CH:%c%s:", osdInfo
.bandLetter
, osdInfo
.channelName
);
1973 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
1975 tfp_sprintf(buff
, "%c", osdInfo
.powerIndexLetter
);
1976 if (isAdjustmentFunctionSelected(ADJUSTMENT_VTX_POWER_LEVEL
)) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1977 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 6, elemPosY
, buff
, elemAttr
);
1984 vtxDeviceOsdInfo_t osdInfo
;
1985 vtxCommonGetOsdInfo(vtxCommonDevice(), &osdInfo
);
1987 tfp_sprintf(buff
, "%c", osdInfo
.powerIndexLetter
);
1988 if (isAdjustmentFunctionSelected(ADJUSTMENT_VTX_POWER_LEVEL
)) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1989 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
1993 #if defined(USE_SERIALRX_CRSF)
1994 case OSD_CRSF_RSSI_DBM
:
1996 int16_t rssi
= rxLinkStatistics
.uplinkRSSI
;
1997 buff
[0] = (rxLinkStatistics
.activeAnt
== 0) ? SYM_RSSI
: SYM_2RSS
; // Separate symbols for each antenna
1999 tfp_sprintf(buff
+ 1, "%4d%c", rssi
, SYM_DBM
);
2001 tfp_sprintf(buff
+ 1, "%3d%c%c", rssi
, SYM_DBM
, ' ');
2003 if (!failsafeIsReceivingRxData()){
2004 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2005 } else if (osdConfig()->rssi_dbm_alarm
&& rssi
< osdConfig()->rssi_dbm_alarm
) {
2006 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2013 int16_t statsLQ
= rxLinkStatistics
.uplinkLQ
;
2014 int16_t scaledLQ
= scaleRange(constrain(statsLQ
, 0, 100), 0, 100, 170, 300);
2015 switch (osdConfig()->crsf_lq_format
) {
2016 case OSD_CRSF_LQ_TYPE1
:
2017 tfp_sprintf(buff
+1, "%3d", rxLinkStatistics
.uplinkLQ
);
2019 case OSD_CRSF_LQ_TYPE2
:
2020 tfp_sprintf(buff
+1, "%d:%3d", rxLinkStatistics
.rfMode
, rxLinkStatistics
.uplinkLQ
);
2022 case OSD_CRSF_LQ_TYPE3
:
2023 tfp_sprintf(buff
+1, "%3d", rxLinkStatistics
.rfMode
>= 2 ? scaledLQ
: rxLinkStatistics
.uplinkLQ
);
2026 if (!failsafeIsReceivingRxData()){
2027 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2028 } else if (rxLinkStatistics
.uplinkLQ
< osdConfig()->link_quality_alarm
) {
2029 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2034 case OSD_CRSF_SNR_DB
:
2036 static pt1Filter_t snrFilterState
;
2037 static timeMs_t snrUpdated
= 0;
2038 int8_t snrFiltered
= pt1FilterApply4(&snrFilterState
, rxLinkStatistics
.uplinkSNR
, 0.5f
, MS2S(millis() - snrUpdated
));
2039 snrUpdated
= millis();
2041 const char* showsnr
= "-20";
2042 const char* hidesnr
= " ";
2043 if (snrFiltered
> osdConfig()->snr_alarm
) {
2046 tfp_sprintf(buff
+ 1, "%s%c", showsnr
, SYM_DB
);
2048 buff
[0] = SYM_BLANK
;
2049 tfp_sprintf(buff
+ 1, "%s%c", hidesnr
, SYM_BLANK
);
2051 } else if (snrFiltered
<= osdConfig()->snr_alarm
) {
2053 if (snrFiltered
<= -10) {
2054 tfp_sprintf(buff
+ 1, "%3d%c", snrFiltered
, SYM_DB
);
2056 tfp_sprintf(buff
+ 1, "%2d%c%c", snrFiltered
, SYM_DB
, ' ');
2062 case OSD_CRSF_TX_POWER
:
2064 tfp_sprintf(buff
, "%4d%c", rxLinkStatistics
.uplinkTXPower
, SYM_MW
);
2069 case OSD_CROSSHAIRS
: // Hud is a sub-element of the crosshair
2071 osdCrosshairPosition(&elemPosX
, &elemPosY
);
2072 osdHudDrawCrosshair(osdGetDisplayPortCanvas(), elemPosX
, elemPosY
);
2074 if (osdConfig()->hud_homing
&& STATE(GPS_FIX
) && STATE(GPS_FIX_HOME
) && isImuHeadingValid()) {
2075 osdHudDrawHoming(elemPosX
, elemPosY
);
2078 if (STATE(GPS_FIX
) && isImuHeadingValid()) {
2080 if (osdConfig()->hud_homepoint
|| osdConfig()->hud_radar_disp
> 0 || osdConfig()->hud_wp_disp
> 0) {
2084 // -------- POI : Home point
2086 if (osdConfig()->hud_homepoint
) { // Display the home point (H)
2087 osdHudDrawPoi(GPS_distanceToHome
, GPS_directionToHome
, -osdGetAltitude() / 100, 0, SYM_HOME
, 0 , 0);
2090 // -------- POI : Nearby aircrafts from ESP32 radar
2092 if (osdConfig()->hud_radar_disp
> 0) { // Display the POI from the radar
2093 for (uint8_t i
= 0; i
< osdConfig()->hud_radar_disp
; i
++) {
2094 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
2096 geoConvertGeodeticToLocal(&poi
, &posControl
.gpsOrigin
, &radar_pois
[i
].gps
, GEO_ALT_RELATIVE
);
2097 radar_pois
[i
].distance
= calculateDistanceToDestination(&poi
) / 100; // In meters
2099 if (radar_pois
[i
].distance
>= osdConfig()->hud_radar_range_min
&& radar_pois
[i
].distance
<= osdConfig()->hud_radar_range_max
) {
2100 radar_pois
[i
].direction
= calculateBearingToDestination(&poi
) / 100; // In °
2101 radar_pois
[i
].altitude
= (radar_pois
[i
].gps
.alt
- osdGetAltitudeMsl()) / 100;
2102 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
);
2107 if (osdConfig()->hud_radar_nearest
> 0) { // Display extra datas for 1 POI closer than a set distance
2108 int poi_id
= radarGetNearestPOI();
2109 if (poi_id
>= 0 && radar_pois
[poi_id
].distance
<= osdConfig()->hud_radar_nearest
) {
2110 osdHudDrawExtras(poi_id
);
2115 // -------- POI : Next waypoints from navigation
2117 if (osdConfig()->hud_wp_disp
> 0 && posControl
.waypointListValid
&& posControl
.waypointCount
> 0) { // Display the next waypoints
2121 for (int i
= osdConfig()->hud_wp_disp
- 1; i
>= 0 ; i
--) { // Display in reverse order so the next WP is always written on top
2122 j
= posControl
.activeWaypointIndex
+ i
;
2123 if (j
> posControl
.waypointCount
- 1) { // limit to max WP index for mission
2126 if (posControl
.waypointList
[j
].lat
!= 0 && posControl
.waypointList
[j
].lon
!= 0) {
2127 wp2
.lat
= posControl
.waypointList
[j
].lat
;
2128 wp2
.lon
= posControl
.waypointList
[j
].lon
;
2129 wp2
.alt
= posControl
.waypointList
[j
].alt
;
2131 geoConvertGeodeticToLocal(&poi
, &posControl
.gpsOrigin
, &wp2
, waypointMissionAltConvMode(posControl
.waypointList
[j
].p3
));
2132 int32_t altConvModeAltitude
= waypointMissionAltConvMode(posControl
.waypointList
[j
].p3
) == GEO_ALT_ABSOLUTE
? osdGetAltitudeMsl() : osdGetAltitude();
2133 j
= getGeoWaypointNumber(j
);
2134 while (j
> 9) j
-= 10; // Only the last digit displayed if WP>=10, no room for more (48 = ascii 0)
2135 osdHudDrawPoi(calculateDistanceToDestination(&poi
) / 100, osdGetHeadingAngle(calculateBearingToDestination(&poi
) / 100), (posControl
.waypointList
[j
].alt
- altConvModeAltitude
)/ 100, 2, SYM_WAYPOINT
, 48 + j
, i
);
2144 case OSD_ATTITUDE_ROLL
:
2145 buff
[0] = SYM_ROLL_LEVEL
;
2146 if (ABS(attitude
.values
.roll
) >= 1)
2147 buff
[0] += (attitude
.values
.roll
< 0 ? -1 : 1);
2148 osdFormatCentiNumber(buff
+ 1, DECIDEGREES_TO_CENTIDEGREES(ABS(attitude
.values
.roll
)), 0, 1, 0, 3);
2151 case OSD_ATTITUDE_PITCH
:
2152 if (ABS(attitude
.values
.pitch
) < 1)
2154 else if (attitude
.values
.pitch
> 0)
2155 buff
[0] = SYM_PITCH_DOWN
;
2156 else if (attitude
.values
.pitch
< 0)
2157 buff
[0] = SYM_PITCH_UP
;
2158 osdFormatCentiNumber(buff
+ 1, DECIDEGREES_TO_CENTIDEGREES(ABS(attitude
.values
.pitch
)), 0, 1, 0, 3);
2161 case OSD_ARTIFICIAL_HORIZON
:
2166 #ifdef USE_SECONDARY_IMU
2167 if (secondaryImuState
.active
&& secondaryImuConfig()->useForOsdAHI
) {
2168 rollAngle
= DECIDEGREES_TO_RADIANS(secondaryImuState
.eulerAngles
.values
.roll
);
2169 pitchAngle
= DECIDEGREES_TO_RADIANS(secondaryImuState
.eulerAngles
.values
.pitch
);
2171 rollAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.roll
);
2172 pitchAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.pitch
);
2175 rollAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.roll
);
2176 pitchAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.pitch
);
2178 pitchAngle
-= osdConfig()->ahi_camera_uptilt_comp
? DEGREES_TO_RADIANS(osdConfig()->camera_uptilt
) : 0;
2179 pitchAngle
+= DEGREES_TO_RADIANS(getFixedWingLevelTrim());
2180 if (osdConfig()->ahi_reverse_roll
) {
2181 rollAngle
= -rollAngle
;
2183 osdDrawArtificialHorizon(osdDisplayPort
, osdGetDisplayPortCanvas(),
2184 OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), rollAngle
, pitchAngle
);
2185 osdDrawSingleElement(OSD_HORIZON_SIDEBARS
);
2186 osdDrawSingleElement(OSD_CROSSHAIRS
);
2191 case OSD_HORIZON_SIDEBARS
:
2193 osdDrawSidebars(osdDisplayPort
, osdGetDisplayPortCanvas());
2197 #if defined(USE_BARO) || defined(USE_GPS)
2200 float zvel
= getEstimatedActualVelocity(Z
);
2201 osdDrawVario(osdDisplayPort
, osdGetDisplayPortCanvas(), OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), zvel
);
2207 int16_t value
= getEstimatedActualVelocity(Z
);
2209 switch ((osd_unit_e
)osdConfig()->units
) {
2212 case OSD_UNIT_IMPERIAL
:
2213 // Convert to centifeet/s
2214 value
= CENTIMETERS_TO_CENTIFEET(value
);
2218 // Convert to centi-100feet/min
2219 value
= CENTIMETERS_TO_FEET(value
* 60);
2223 case OSD_UNIT_METRIC_MPH
:
2225 case OSD_UNIT_METRIC
:
2231 osdFormatCentiNumber(buff
, value
, 0, 1, 0, 3);
2239 osdDisplayFlightPIDValues(elemPosX
, elemPosY
, "ROL", PID_ROLL
, ADJUSTMENT_ROLL_P
, ADJUSTMENT_ROLL_I
, ADJUSTMENT_ROLL_D
, ADJUSTMENT_ROLL_FF
);
2242 case OSD_PITCH_PIDS
:
2243 osdDisplayFlightPIDValues(elemPosX
, elemPosY
, "PIT", PID_PITCH
, ADJUSTMENT_PITCH_P
, ADJUSTMENT_PITCH_I
, ADJUSTMENT_PITCH_D
, ADJUSTMENT_PITCH_FF
);
2247 osdDisplayFlightPIDValues(elemPosX
, elemPosY
, "YAW", PID_YAW
, ADJUSTMENT_YAW_P
, ADJUSTMENT_YAW_I
, ADJUSTMENT_YAW_D
, ADJUSTMENT_YAW_FF
);
2250 case OSD_LEVEL_PIDS
:
2251 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "LEV", PID_LEVEL
, ADJUSTMENT_LEVEL_P
, ADJUSTMENT_LEVEL_I
, ADJUSTMENT_LEVEL_D
);
2254 case OSD_POS_XY_PIDS
:
2255 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "PXY", PID_POS_XY
, ADJUSTMENT_POS_XY_P
, ADJUSTMENT_POS_XY_I
, ADJUSTMENT_POS_XY_D
);
2258 case OSD_POS_Z_PIDS
:
2259 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "PZ", PID_POS_Z
, ADJUSTMENT_POS_Z_P
, ADJUSTMENT_POS_Z_I
, ADJUSTMENT_POS_Z_D
);
2262 case OSD_VEL_XY_PIDS
:
2263 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "VXY", PID_VEL_XY
, ADJUSTMENT_VEL_XY_P
, ADJUSTMENT_VEL_XY_I
, ADJUSTMENT_VEL_XY_D
);
2266 case OSD_VEL_Z_PIDS
:
2267 osdDisplayNavPIDValues(elemPosX
, elemPosY
, "VZ", PID_VEL_Z
, ADJUSTMENT_VEL_Z_P
, ADJUSTMENT_VEL_Z_I
, ADJUSTMENT_VEL_Z_D
);
2271 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "HP", 0, pidBank()->pid
[PID_HEADING
].P
, 3, 0, ADJUSTMENT_HEADING_P
);
2274 case OSD_BOARD_ALIGN_ROLL
:
2275 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "AR", 0, DECIDEGREES_TO_DEGREES((float)boardAlignment()->rollDeciDegrees
), 4, 1, ADJUSTMENT_ROLL_BOARD_ALIGNMENT
);
2278 case OSD_BOARD_ALIGN_PITCH
:
2279 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "AP", 0, DECIDEGREES_TO_DEGREES((float)boardAlignment()->pitchDeciDegrees
), 4, 1, ADJUSTMENT_PITCH_BOARD_ALIGNMENT
);
2283 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "EXP", 0, currentControlRateProfile
->stabilized
.rcExpo8
, 3, 0, ADJUSTMENT_RC_EXPO
);
2286 case OSD_RC_YAW_EXPO
:
2287 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "YEX", 0, currentControlRateProfile
->stabilized
.rcYawExpo8
, 3, 0, ADJUSTMENT_RC_YAW_EXPO
);
2290 case OSD_THROTTLE_EXPO
:
2291 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "TEX", 0, currentControlRateProfile
->throttle
.rcExpo8
, 3, 0, ADJUSTMENT_THROTTLE_EXPO
);
2294 case OSD_PITCH_RATE
:
2295 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "SPR");
2297 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2298 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->stabilized
.rates
[FD_PITCH
]);
2299 if (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_RATE
))
2300 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2301 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2305 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "SRR");
2307 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2308 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->stabilized
.rates
[FD_ROLL
]);
2309 if (isAdjustmentFunctionSelected(ADJUSTMENT_ROLL_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_RATE
))
2310 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2311 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2315 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "SYR", 0, currentControlRateProfile
->stabilized
.rates
[FD_YAW
], 3, 0, ADJUSTMENT_YAW_RATE
);
2318 case OSD_MANUAL_RC_EXPO
:
2319 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MEX", 0, currentControlRateProfile
->manual
.rcExpo8
, 3, 0, ADJUSTMENT_MANUAL_RC_EXPO
);
2322 case OSD_MANUAL_RC_YAW_EXPO
:
2323 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MYX", 0, currentControlRateProfile
->manual
.rcYawExpo8
, 3, 0, ADJUSTMENT_MANUAL_RC_YAW_EXPO
);
2326 case OSD_MANUAL_PITCH_RATE
:
2327 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "MPR");
2329 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2330 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->manual
.rates
[FD_PITCH
]);
2331 if (isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_ROLL_RATE
))
2332 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2333 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2336 case OSD_MANUAL_ROLL_RATE
:
2337 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "MRR");
2339 elemAttr
= TEXT_ATTRIBUTES_NONE
;
2340 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->manual
.rates
[FD_ROLL
]);
2341 if (isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_ROLL_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_ROLL_RATE
))
2342 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2343 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
2346 case OSD_MANUAL_YAW_RATE
:
2347 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MYR", 0, currentControlRateProfile
->stabilized
.rates
[FD_YAW
], 3, 0, ADJUSTMENT_YAW_RATE
);
2350 case OSD_NAV_FW_CRUISE_THR
:
2351 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "CRZ", 0, currentBatteryProfile
->nav
.fw
.cruise_throttle
, 4, 0, ADJUSTMENT_NAV_FW_CRUISE_THR
);
2354 case OSD_NAV_FW_PITCH2THR
:
2355 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "P2T", 0, currentBatteryProfile
->nav
.fw
.pitch_to_throttle
, 3, 0, ADJUSTMENT_NAV_FW_PITCH2THR
);
2358 case OSD_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
:
2359 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "0TP", 0, (float)currentBatteryProfile
->fwMinThrottleDownPitchAngle
/ 10, 3, 1, ADJUSTMENT_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
);
2362 case OSD_FW_ALT_PID_OUTPUTS
:
2364 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2365 osdFormatPidControllerOutput(buff
, "PZO", &nav_pids
->fw_alt
, 10, true); // display requested pitch degrees
2369 case OSD_FW_POS_PID_OUTPUTS
:
2371 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers(); // display requested roll degrees
2372 osdFormatPidControllerOutput(buff
, "PXYO", &nav_pids
->fw_nav
, 1, true);
2376 case OSD_MC_VEL_Z_PID_OUTPUTS
:
2378 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2379 osdFormatPidControllerOutput(buff
, "VZO", &nav_pids
->vel
[Z
], 100, false); // display throttle adjustment µs
2383 case OSD_MC_VEL_X_PID_OUTPUTS
:
2385 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2386 osdFormatPidControllerOutput(buff
, "VXO", &nav_pids
->vel
[X
], 100, false); // display requested acceleration cm/s^2
2390 case OSD_MC_VEL_Y_PID_OUTPUTS
:
2392 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2393 osdFormatPidControllerOutput(buff
, "VYO", &nav_pids
->vel
[Y
], 100, false); // display requested acceleration cm/s^2
2397 case OSD_MC_POS_XYZ_P_OUTPUTS
:
2399 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2400 strcpy(buff
, "POSO ");
2401 // display requested velocity cm/s
2402 tfp_sprintf(buff
+ 5, "%4d", (int)lrintf(nav_pids
->pos
[X
].output_constrained
* 100));
2404 tfp_sprintf(buff
+ 10, "%4d", (int)lrintf(nav_pids
->pos
[Y
].output_constrained
* 100));
2406 tfp_sprintf(buff
+ 15, "%4d", (int)lrintf(nav_pids
->pos
[Z
].output_constrained
* 100));
2413 bool kiloWatt
= osdFormatCentiNumber(buff
, getPower(), 1000, 2, 2, 3);
2414 buff
[3] = kiloWatt
? SYM_KILOWATT
: SYM_WATT
;
2417 uint8_t current_alarm
= osdConfig()->current_alarm
;
2418 if ((current_alarm
> 0) && ((getAmperage() / 100.0f
) > current_alarm
)) {
2419 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2428 osdFormatVelocityStr(buff
+ 1, pitot
.airSpeed
, false, false);
2437 // RTC not configured will show 00:00
2438 dateTime_t dateTime
;
2439 rtcGetDateTimeLocal(&dateTime
);
2440 buff
[0] = SYM_CLOCK
;
2441 tfp_sprintf(buff
+ 1, "%02u:%02u:%02u", dateTime
.hours
, dateTime
.minutes
, dateTime
.seconds
);
2447 elemAttr
= osdGetSystemMessage(buff
, OSD_MESSAGE_LENGTH
, true);
2453 tfp_sprintf(buff
, "INAV %s", FC_VERSION_STRING
);
2454 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2458 case OSD_MAIN_BATT_CELL_VOLTAGE
:
2460 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatteryRawAverageCellVoltage(), 3, 2);
2464 case OSD_MAIN_BATT_SAG_COMPENSATED_CELL_VOLTAGE
:
2466 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatterySagCompensatedAverageCellVoltage(), 3, 2);
2470 case OSD_THROTTLE_POS_AUTO_THR
:
2472 osdFormatThrottlePosition(buff
, true, &elemAttr
);
2476 case OSD_HEADING_GRAPH
:
2478 if (osdIsHeadingValid()) {
2479 osdDrawHeadingGraph(osdDisplayPort
, osdGetDisplayPortCanvas(), OSD_DRAW_POINT_GRID(elemPosX
, elemPosY
), osdGetHeading());
2482 buff
[0] = buff
[2] = buff
[4] = buff
[6] = buff
[8] = SYM_HEADING_LINE
;
2483 buff
[1] = buff
[3] = buff
[5] = buff
[7] = SYM_HEADING_DIVIDED_LINE
;
2484 buff
[OSD_HEADING_GRAPH_WIDTH
] = '\0';
2489 case OSD_EFFICIENCY_MAH_PER_KM
:
2491 // amperage is in centi amps, speed is in cms/s. We want
2492 // mah/km. Only show when ground speed > 1m/s.
2493 static pt1Filter_t eFilterState
;
2494 static timeUs_t efficiencyUpdated
= 0;
2496 bool moreThanAh
= false;
2497 timeUs_t currentTimeUs
= micros();
2498 timeDelta_t efficiencyTimeDelta
= cmpTimeUs(currentTimeUs
, efficiencyUpdated
);
2499 if (STATE(GPS_FIX
) && gpsSol
.groundSpeed
> 0) {
2500 if (efficiencyTimeDelta
>= EFFICIENCY_UPDATE_INTERVAL
) {
2501 value
= pt1FilterApply4(&eFilterState
, ((float)getAmperage() / gpsSol
.groundSpeed
) / 0.0036f
,
2502 1, US2S(efficiencyTimeDelta
));
2504 efficiencyUpdated
= currentTimeUs
;
2506 value
= eFilterState
.state
;
2509 bool efficiencyValid
= (value
> 0) && (gpsSol
.groundSpeed
> 100);
2510 switch (osdConfig()->units
) {
2513 case OSD_UNIT_IMPERIAL
:
2514 moreThanAh
= osdFormatCentiNumber(buff
, value
* METERS_PER_MILE
/ 10, 1000, 0, 2, 3);
2516 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_MI_0
, SYM_MAH_MI_1
);
2518 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_MI
);
2520 if (!efficiencyValid
) {
2521 buff
[0] = buff
[1] = buff
[2] = '-';
2522 buff
[3] = SYM_MAH_MI_0
;
2523 buff
[4] = SYM_MAH_MI_1
;
2528 moreThanAh
= osdFormatCentiNumber(buff
, value
* METERS_PER_NAUTICALMILE
/ 10, 1000, 0, 2, 3);
2530 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_NM_0
, SYM_MAH_NM_1
);
2532 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_NM
);
2534 if (!efficiencyValid
) {
2535 buff
[0] = buff
[1] = buff
[2] = '-';
2536 buff
[3] = SYM_MAH_NM_0
;
2537 buff
[4] = SYM_MAH_NM_1
;
2541 case OSD_UNIT_METRIC_MPH
:
2543 case OSD_UNIT_METRIC
:
2544 moreThanAh
= osdFormatCentiNumber(buff
, value
* 100, 1000, 0, 2, 3);
2546 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_KM_0
, SYM_MAH_KM_1
);
2548 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_KM
);
2550 if (!efficiencyValid
) {
2551 buff
[0] = buff
[1] = buff
[2] = '-';
2552 buff
[3] = SYM_MAH_KM_0
;
2553 buff
[4] = SYM_MAH_KM_1
;
2561 case OSD_EFFICIENCY_WH_PER_KM
:
2563 // amperage is in centi amps, speed is in cms/s. We want
2564 // mWh/km. Only show when ground speed > 1m/s.
2565 static pt1Filter_t eFilterState
;
2566 static timeUs_t efficiencyUpdated
= 0;
2568 timeUs_t currentTimeUs
= micros();
2569 timeDelta_t efficiencyTimeDelta
= cmpTimeUs(currentTimeUs
, efficiencyUpdated
);
2570 if (STATE(GPS_FIX
) && gpsSol
.groundSpeed
> 0) {
2571 if (efficiencyTimeDelta
>= EFFICIENCY_UPDATE_INTERVAL
) {
2572 value
= pt1FilterApply4(&eFilterState
, ((float)getPower() / gpsSol
.groundSpeed
) / 0.0036f
,
2573 1, US2S(efficiencyTimeDelta
));
2575 efficiencyUpdated
= currentTimeUs
;
2577 value
= eFilterState
.state
;
2580 bool efficiencyValid
= (value
> 0) && (gpsSol
.groundSpeed
> 100);
2581 switch (osdConfig()->units
) {
2584 case OSD_UNIT_IMPERIAL
:
2585 osdFormatCentiNumber(buff
, value
* METERS_PER_MILE
/ 10000, 0, 2, 0, 3);
2586 buff
[3] = SYM_WH_MI
;
2589 osdFormatCentiNumber(buff
, value
* METERS_PER_NAUTICALMILE
/ 10000, 0, 2, 0, 3);
2590 buff
[3] = SYM_WH_NM
;
2592 case OSD_UNIT_METRIC_MPH
:
2594 case OSD_UNIT_METRIC
:
2595 osdFormatCentiNumber(buff
, value
/ 10, 0, 2, 0, 3);
2596 buff
[3] = SYM_WH_KM
;
2600 if (!efficiencyValid
) {
2601 buff
[0] = buff
[1] = buff
[2] = '-';
2608 buff
[0] = SYM_GFORCE
;
2609 osdFormatCentiNumber(buff
+ 1, GForce
, 0, 2, 0, 3);
2610 if (GForce
> osdConfig()->gforce_alarm
* 100) {
2611 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2620 float GForceValue
= GForceAxis
[item
- OSD_GFORCE_X
];
2621 buff
[0] = SYM_GFORCE_X
+ item
- OSD_GFORCE_X
;
2622 osdFormatCentiNumber(buff
+ 1, GForceValue
, 0, 2, 0, 4);
2623 if ((GForceValue
< osdConfig()->gforce_axis_alarm_min
* 100) || (GForceValue
> osdConfig()->gforce_axis_alarm_max
* 100)) {
2624 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2631 * Longest representable string is -2147483648 does not fit in the screen.
2632 * Only 7 digits for negative and 8 digits for positive values allowed
2634 for (uint8_t bufferIndex
= 0; bufferIndex
< DEBUG32_VALUE_COUNT
; ++elemPosY
, bufferIndex
+= 2) {
2637 "[%u]=%8ld [%u]=%8ld",
2639 constrain(debug
[bufferIndex
], -9999999, 99999999),
2641 constrain(debug
[bufferIndex
+1], -9999999, 99999999)
2643 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2648 case OSD_IMU_TEMPERATURE
:
2650 int16_t temperature
;
2651 const bool valid
= getIMUTemperature(&temperature
);
2652 osdDisplayTemperature(elemPosX
, elemPosY
, SYM_IMU_TEMP
, NULL
, valid
, temperature
, osdConfig()->imu_temp_alarm_min
, osdConfig()->imu_temp_alarm_max
);
2656 case OSD_BARO_TEMPERATURE
:
2658 int16_t temperature
;
2659 const bool valid
= getBaroTemperature(&temperature
);
2660 osdDisplayTemperature(elemPosX
, elemPosY
, SYM_BARO_TEMP
, NULL
, valid
, temperature
, osdConfig()->imu_temp_alarm_min
, osdConfig()->imu_temp_alarm_max
);
2664 #ifdef USE_TEMPERATURE_SENSOR
2665 case OSD_TEMP_SENSOR_0_TEMPERATURE
:
2666 case OSD_TEMP_SENSOR_1_TEMPERATURE
:
2667 case OSD_TEMP_SENSOR_2_TEMPERATURE
:
2668 case OSD_TEMP_SENSOR_3_TEMPERATURE
:
2669 case OSD_TEMP_SENSOR_4_TEMPERATURE
:
2670 case OSD_TEMP_SENSOR_5_TEMPERATURE
:
2671 case OSD_TEMP_SENSOR_6_TEMPERATURE
:
2672 case OSD_TEMP_SENSOR_7_TEMPERATURE
:
2674 osdDisplayTemperatureSensor(elemPosX
, elemPosY
, item
- OSD_TEMP_SENSOR_0_TEMPERATURE
);
2677 #endif /* ifdef USE_TEMPERATURE_SENSOR */
2679 case OSD_WIND_SPEED_HORIZONTAL
:
2680 #ifdef USE_WIND_ESTIMATOR
2682 bool valid
= isEstimatedWindSpeedValid();
2683 float horizontalWindSpeed
;
2686 horizontalWindSpeed
= getEstimatedHorizontalWindSpeed(&angle
);
2687 int16_t windDirection
= osdGetHeadingAngle((int)angle
- DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
2688 buff
[1] = SYM_DIRECTION
+ (windDirection
* 2 / 90);
2690 horizontalWindSpeed
= 0;
2691 buff
[1] = SYM_BLANK
;
2693 buff
[0] = SYM_WIND_HORIZONTAL
;
2694 osdFormatWindSpeedStr(buff
+ 2, horizontalWindSpeed
, valid
);
2701 case OSD_WIND_SPEED_VERTICAL
:
2702 #ifdef USE_WIND_ESTIMATOR
2704 buff
[0] = SYM_WIND_VERTICAL
;
2705 buff
[1] = SYM_BLANK
;
2706 bool valid
= isEstimatedWindSpeedValid();
2707 float verticalWindSpeed
;
2709 verticalWindSpeed
= getEstimatedWindSpeed(Z
);
2710 if (verticalWindSpeed
< 0) {
2711 buff
[1] = SYM_AH_DECORATION_DOWN
;
2712 verticalWindSpeed
= -verticalWindSpeed
;
2713 } else if (verticalWindSpeed
> 0) {
2714 buff
[1] = SYM_AH_DECORATION_UP
;
2717 verticalWindSpeed
= 0;
2719 osdFormatWindSpeedStr(buff
+ 2, verticalWindSpeed
, valid
);
2728 STATIC_ASSERT(GPS_DEGREES_DIVIDER
== OLC_DEG_MULTIPLIER
, invalid_olc_deg_multiplier
);
2729 int digits
= osdConfig()->plus_code_digits
;
2730 int digitsRemoved
= osdConfig()->plus_code_short
* 2;
2731 if (STATE(GPS_FIX
)) {
2732 olc_encode(gpsSol
.llh
.lat
, gpsSol
.llh
.lon
, digits
, buff
, sizeof(buff
));
2734 // +codes with > 8 digits have a + at the 9th digit
2735 // and we only support 10 and up.
2736 memset(buff
, '-', digits
+ 1);
2738 buff
[digits
+ 1] = '\0';
2740 // Optionally trim digits from the left
2741 memmove(buff
, buff
+digitsRemoved
, strlen(buff
) + digitsRemoved
);
2742 buff
[digits
+ 1 - digitsRemoved
] = '\0';
2749 buff
[0] = SYM_AZIMUTH
;
2750 if (osdIsHeadingValid()) {
2751 int16_t h
= GPS_directionToHome
;
2760 tfp_sprintf(&buff
[1], "%3d", h
);
2762 buff
[1] = buff
[2] = buff
[3] = '-';
2764 buff
[4] = SYM_DEGREES
;
2772 int scaleUnitDivisor
;
2777 switch (osdConfig()->units
) {
2780 case OSD_UNIT_IMPERIAL
:
2781 scaleToUnit
= 100 / 1609.3440f
; // scale to 0.01mi for osdFormatCentiNumber()
2782 scaleUnitDivisor
= 0;
2783 symUnscaled
= SYM_MI
;
2788 scaleToUnit
= 100 / 1852.0010f
; // scale to 0.01mi for osdFormatCentiNumber()
2789 scaleUnitDivisor
= 0;
2790 symUnscaled
= SYM_NM
;
2795 case OSD_UNIT_METRIC_MPH
:
2797 case OSD_UNIT_METRIC
:
2798 scaleToUnit
= 100; // scale to cm for osdFormatCentiNumber()
2799 scaleUnitDivisor
= 1000; // Convert to km when scale gets bigger than 999m
2800 symUnscaled
= SYM_M
;
2805 buff
[0] = SYM_SCALE
;
2806 if (osdMapData
.scale
> 0) {
2807 bool scaled
= osdFormatCentiNumber(&buff
[1], osdMapData
.scale
* scaleToUnit
, scaleUnitDivisor
, maxDecimals
, 2, 3);
2808 buff
[4] = scaled
? symScaled
: symUnscaled
;
2809 // Make sure this is cleared if the map stops being drawn
2810 osdMapData
.scale
= 0;
2812 memset(&buff
[1], '-', 4);
2817 case OSD_MAP_REFERENCE
:
2819 char referenceSymbol
;
2820 if (osdMapData
.referenceSymbol
) {
2821 referenceSymbol
= osdMapData
.referenceSymbol
;
2822 // Make sure this is cleared if the map stops being drawn
2823 osdMapData
.referenceSymbol
= 0;
2825 referenceSymbol
= '-';
2827 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
, SYM_DIRECTION
);
2828 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
+ 1, referenceSymbol
);
2834 osdFormatGVar(buff
, 0);
2839 osdFormatGVar(buff
, 1);
2844 osdFormatGVar(buff
, 2);
2849 osdFormatGVar(buff
, 3);
2853 #if defined(USE_RX_MSP) && defined(USE_MSP_RC_OVERRIDE)
2856 const char *source_text
= IS_RC_MODE_ACTIVE(BOXMSPRCOVERRIDE
) && !mspOverrideIsInFailsafe() ? "MSP" : "STD";
2857 if (IS_RC_MODE_ACTIVE(BOXMSPRCOVERRIDE
) && mspOverrideIsInFailsafe()) TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2858 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, source_text
, elemAttr
);
2863 #if defined(USE_ESC_SENSOR)
2866 escSensorData_t
* escSensor
= escSensorGetData();
2867 if (escSensor
&& escSensor
->dataAge
<= ESC_DATA_MAX_AGE
) {
2868 osdFormatRpm(buff
, escSensor
->rpm
);
2871 osdFormatRpm(buff
, 0);
2875 case OSD_ESC_TEMPERATURE
:
2877 escSensorData_t
* escSensor
= escSensorGetData();
2878 bool escTemperatureValid
= escSensor
&& escSensor
->dataAge
<= ESC_DATA_MAX_AGE
;
2879 osdDisplayTemperature(elemPosX
, elemPosY
, SYM_ESC_TEMP
, NULL
, escTemperatureValid
, (escSensor
->temperature
)*10, osdConfig()->esc_temp_alarm_min
, osdConfig()->esc_temp_alarm_max
);
2886 textAttributes_t attr
;
2888 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "TPA");
2889 attr
= TEXT_ATTRIBUTES_NONE
;
2890 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->throttle
.dynPID
);
2891 if (isAdjustmentFunctionSelected(ADJUSTMENT_TPA
)) {
2892 TEXT_ATTRIBUTES_ADD_BLINK(attr
);
2894 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 5, elemPosY
, buff
, attr
);
2896 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
+ 1, "BP");
2897 attr
= TEXT_ATTRIBUTES_NONE
;
2898 tfp_sprintf(buff
, "%4d", currentControlRateProfile
->throttle
.pa_breakpoint
);
2899 if (isAdjustmentFunctionSelected(ADJUSTMENT_TPA_BREAKPOINT
)) {
2900 TEXT_ATTRIBUTES_ADD_BLINK(attr
);
2902 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
+ 1, buff
, attr
);
2907 case OSD_NAV_FW_CONTROL_SMOOTHNESS
:
2908 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "CTL S", 0, navConfig()->fw
.control_smoothness
, 1, 0, ADJUSTMENT_NAV_FW_CONTROL_SMOOTHNESS
);
2911 #ifdef USE_POWER_LIMITS
2912 case OSD_PLIMIT_REMAINING_BURST_TIME
:
2913 osdFormatCentiNumber(buff
, powerLimiterGetRemainingBurstTime() * 100, 0, 1, 0, 3);
2918 case OSD_PLIMIT_ACTIVE_CURRENT_LIMIT
:
2919 if (currentBatteryProfile
->powerLimits
.continuousCurrent
) {
2920 osdFormatCentiNumber(buff
, powerLimiterGetActiveCurrentLimit(), 0, 2, 0, 3);
2924 if (powerLimiterIsLimitingCurrent()) {
2925 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2931 case OSD_PLIMIT_ACTIVE_POWER_LIMIT
:
2933 if (currentBatteryProfile
->powerLimits
.continuousPower
) {
2934 bool kiloWatt
= osdFormatCentiNumber(buff
, powerLimiterGetActivePowerLimit(), 1000, 2, 2, 3);
2935 buff
[3] = kiloWatt
? SYM_KILOWATT
: SYM_WATT
;
2938 if (powerLimiterIsLimitingPower()) {
2939 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2945 #endif // USE_POWER_LIMITS
2951 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
2955 static uint8_t osdIncElementIndex(uint8_t elementIndex
)
2959 if (elementIndex
== OSD_ARTIFICIAL_HORIZON
)
2962 #ifndef USE_TEMPERATURE_SENSOR
2963 if (elementIndex
== OSD_TEMP_SENSOR_0_TEMPERATURE
)
2964 elementIndex
= OSD_ALTITUDE_MSL
;
2967 if (!sensors(SENSOR_ACC
)) {
2968 if (elementIndex
== OSD_CROSSHAIRS
) {
2969 elementIndex
= OSD_ONTIME
;
2973 if (!feature(FEATURE_VBAT
)) {
2974 if (elementIndex
== OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
) {
2975 elementIndex
= OSD_LEVEL_PIDS
;
2979 if (!feature(FEATURE_CURRENT_METER
)) {
2980 if (elementIndex
== OSD_CURRENT_DRAW
) {
2981 elementIndex
= OSD_GPS_SPEED
;
2983 if (elementIndex
== OSD_EFFICIENCY_MAH_PER_KM
) {
2984 elementIndex
= OSD_TRIP_DIST
;
2986 if (elementIndex
== OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
) {
2987 elementIndex
= OSD_HOME_HEADING_ERROR
;
2989 if (elementIndex
== OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
) {
2990 elementIndex
= OSD_LEVEL_PIDS
;
2994 if (!feature(FEATURE_GPS
)) {
2995 if (elementIndex
== OSD_GPS_SPEED
) {
2996 elementIndex
= OSD_ALTITUDE
;
2998 if (elementIndex
== OSD_GPS_LON
) {
2999 elementIndex
= OSD_VARIO
;
3001 if (elementIndex
== OSD_GPS_HDOP
) {
3002 elementIndex
= OSD_MAIN_BATT_CELL_VOLTAGE
;
3004 if (elementIndex
== OSD_TRIP_DIST
) {
3005 elementIndex
= OSD_ATTITUDE_PITCH
;
3007 if (elementIndex
== OSD_WIND_SPEED_HORIZONTAL
) {
3008 elementIndex
= OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
;
3010 if (elementIndex
== OSD_3D_SPEED
) {
3015 if (!STATE(ESC_SENSOR_ENABLED
)) {
3016 if (elementIndex
== OSD_ESC_RPM
) {
3021 #ifndef USE_POWER_LIMITS
3022 if (elementIndex
== OSD_NAV_FW_CONTROL_SMOOTHNESS
) {
3023 elementIndex
= OSD_ITEM_COUNT
;
3027 if (elementIndex
== OSD_ITEM_COUNT
) {
3030 return elementIndex
;
3033 void osdDrawNextElement(void)
3035 static uint8_t elementIndex
= 0;
3036 // Prevent infinite loop when no elements are enabled
3037 uint8_t index
= elementIndex
;
3039 elementIndex
= osdIncElementIndex(elementIndex
);
3040 } while(!osdDrawSingleElement(elementIndex
) && index
!= elementIndex
);
3042 // Draw artificial horizon + tracking telemtry last
3043 osdDrawSingleElement(OSD_ARTIFICIAL_HORIZON
);
3044 if (osdConfig()->telemetry
>0){
3045 osdDisplayTelemetry();
3049 PG_RESET_TEMPLATE(osdConfig_t
, osdConfig
,
3050 .rssi_alarm
= SETTING_OSD_RSSI_ALARM_DEFAULT
,
3051 .time_alarm
= SETTING_OSD_TIME_ALARM_DEFAULT
,
3052 .alt_alarm
= SETTING_OSD_ALT_ALARM_DEFAULT
,
3053 .dist_alarm
= SETTING_OSD_DIST_ALARM_DEFAULT
,
3054 .neg_alt_alarm
= SETTING_OSD_NEG_ALT_ALARM_DEFAULT
,
3055 .current_alarm
= SETTING_OSD_CURRENT_ALARM_DEFAULT
,
3056 .imu_temp_alarm_min
= SETTING_OSD_IMU_TEMP_ALARM_MIN_DEFAULT
,
3057 .imu_temp_alarm_max
= SETTING_OSD_IMU_TEMP_ALARM_MAX_DEFAULT
,
3058 .esc_temp_alarm_min
= SETTING_OSD_ESC_TEMP_ALARM_MIN_DEFAULT
,
3059 .esc_temp_alarm_max
= SETTING_OSD_ESC_TEMP_ALARM_MAX_DEFAULT
,
3060 .gforce_alarm
= SETTING_OSD_GFORCE_ALARM_DEFAULT
,
3061 .gforce_axis_alarm_min
= SETTING_OSD_GFORCE_AXIS_ALARM_MIN_DEFAULT
,
3062 .gforce_axis_alarm_max
= SETTING_OSD_GFORCE_AXIS_ALARM_MAX_DEFAULT
,
3064 .baro_temp_alarm_min
= SETTING_OSD_BARO_TEMP_ALARM_MIN_DEFAULT
,
3065 .baro_temp_alarm_max
= SETTING_OSD_BARO_TEMP_ALARM_MAX_DEFAULT
,
3067 #ifdef USE_SERIALRX_CRSF
3068 .snr_alarm
= SETTING_OSD_SNR_ALARM_DEFAULT
,
3069 .crsf_lq_format
= SETTING_OSD_CRSF_LQ_FORMAT_DEFAULT
,
3070 .link_quality_alarm
= SETTING_OSD_LINK_QUALITY_ALARM_DEFAULT
,
3071 .rssi_dbm_alarm
= SETTING_OSD_RSSI_DBM_ALARM_DEFAULT
,
3073 #ifdef USE_TEMPERATURE_SENSOR
3074 .temp_label_align
= SETTING_OSD_TEMP_LABEL_ALIGN_DEFAULT
,
3077 .video_system
= SETTING_OSD_VIDEO_SYSTEM_DEFAULT
,
3078 .row_shiftdown
= SETTING_OSD_ROW_SHIFTDOWN_DEFAULT
,
3080 .ahi_reverse_roll
= SETTING_OSD_AHI_REVERSE_ROLL_DEFAULT
,
3081 .ahi_max_pitch
= SETTING_OSD_AHI_MAX_PITCH_DEFAULT
,
3082 .crosshairs_style
= SETTING_OSD_CROSSHAIRS_STYLE_DEFAULT
,
3083 .horizon_offset
= SETTING_OSD_HORIZON_OFFSET_DEFAULT
,
3084 .camera_uptilt
= SETTING_OSD_CAMERA_UPTILT_DEFAULT
,
3085 .ahi_camera_uptilt_comp
= SETTING_OSD_AHI_CAMERA_UPTILT_COMP_DEFAULT
,
3086 .camera_fov_h
= SETTING_OSD_CAMERA_FOV_H_DEFAULT
,
3087 .camera_fov_v
= SETTING_OSD_CAMERA_FOV_V_DEFAULT
,
3088 .hud_margin_h
= SETTING_OSD_HUD_MARGIN_H_DEFAULT
,
3089 .hud_margin_v
= SETTING_OSD_HUD_MARGIN_V_DEFAULT
,
3090 .hud_homing
= SETTING_OSD_HUD_HOMING_DEFAULT
,
3091 .hud_homepoint
= SETTING_OSD_HUD_HOMEPOINT_DEFAULT
,
3092 .hud_radar_disp
= SETTING_OSD_HUD_RADAR_DISP_DEFAULT
,
3093 .hud_radar_range_min
= SETTING_OSD_HUD_RADAR_RANGE_MIN_DEFAULT
,
3094 .hud_radar_range_max
= SETTING_OSD_HUD_RADAR_RANGE_MAX_DEFAULT
,
3095 .hud_radar_nearest
= SETTING_OSD_HUD_RADAR_NEAREST_DEFAULT
,
3096 .hud_wp_disp
= SETTING_OSD_HUD_WP_DISP_DEFAULT
,
3097 .left_sidebar_scroll
= SETTING_OSD_LEFT_SIDEBAR_SCROLL_DEFAULT
,
3098 .right_sidebar_scroll
= SETTING_OSD_RIGHT_SIDEBAR_SCROLL_DEFAULT
,
3099 .sidebar_scroll_arrows
= SETTING_OSD_SIDEBAR_SCROLL_ARROWS_DEFAULT
,
3100 .sidebar_horizontal_offset
= SETTING_OSD_SIDEBAR_HORIZONTAL_OFFSET_DEFAULT
,
3101 .left_sidebar_scroll_step
= SETTING_OSD_LEFT_SIDEBAR_SCROLL_STEP_DEFAULT
,
3102 .right_sidebar_scroll_step
= SETTING_OSD_RIGHT_SIDEBAR_SCROLL_STEP_DEFAULT
,
3103 .sidebar_height
= SETTING_OSD_SIDEBAR_HEIGHT_DEFAULT
,
3104 .osd_home_position_arm_screen
= SETTING_OSD_HOME_POSITION_ARM_SCREEN_DEFAULT
,
3105 .pan_servo_index
= SETTING_OSD_PAN_SERVO_INDEX_DEFAULT
,
3106 .pan_servo_pwm2centideg
= SETTING_OSD_PAN_SERVO_PWM2CENTIDEG_DEFAULT
,
3108 .units
= SETTING_OSD_UNITS_DEFAULT
,
3109 .main_voltage_decimals
= SETTING_OSD_MAIN_VOLTAGE_DECIMALS_DEFAULT
,
3111 #ifdef USE_WIND_ESTIMATOR
3112 .estimations_wind_compensation
= SETTING_OSD_ESTIMATIONS_WIND_COMPENSATION_DEFAULT
,
3115 .coordinate_digits
= SETTING_OSD_COORDINATE_DIGITS_DEFAULT
,
3117 .osd_failsafe_switch_layout
= SETTING_OSD_FAILSAFE_SWITCH_LAYOUT_DEFAULT
,
3119 .plus_code_digits
= SETTING_OSD_PLUS_CODE_DIGITS_DEFAULT
,
3120 .plus_code_short
= SETTING_OSD_PLUS_CODE_SHORT_DEFAULT
,
3122 .ahi_width
= SETTING_OSD_AHI_WIDTH_DEFAULT
,
3123 .ahi_height
= SETTING_OSD_AHI_HEIGHT_DEFAULT
,
3124 .ahi_vertical_offset
= SETTING_OSD_AHI_VERTICAL_OFFSET_DEFAULT
,
3125 .ahi_bordered
= SETTING_OSD_AHI_BORDERED_DEFAULT
,
3126 .ahi_style
= SETTING_OSD_AHI_STYLE_DEFAULT
,
3128 .force_grid
= SETTING_OSD_FORCE_GRID_DEFAULT
,
3130 .stats_energy_unit
= SETTING_OSD_STATS_ENERGY_UNIT_DEFAULT
,
3131 .stats_min_voltage_unit
= SETTING_OSD_STATS_MIN_VOLTAGE_UNIT_DEFAULT
,
3132 .stats_page_auto_swap_time
= SETTING_OSD_STATS_PAGE_AUTO_SWAP_TIME_DEFAULT
3135 void pgResetFn_osdLayoutsConfig(osdLayoutsConfig_t
*osdLayoutsConfig
)
3137 osdLayoutsConfig
->item_pos
[0][OSD_ALTITUDE
] = OSD_POS(1, 0) | OSD_VISIBLE_FLAG
;
3138 osdLayoutsConfig
->item_pos
[0][OSD_MAIN_BATT_VOLTAGE
] = OSD_POS(12, 0) | OSD_VISIBLE_FLAG
;
3139 osdLayoutsConfig
->item_pos
[0][OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
] = OSD_POS(12, 1);
3141 osdLayoutsConfig
->item_pos
[0][OSD_RSSI_VALUE
] = OSD_POS(23, 0) | OSD_VISIBLE_FLAG
;
3143 osdLayoutsConfig
->item_pos
[0][OSD_HOME_DIST
] = OSD_POS(1, 1);
3144 osdLayoutsConfig
->item_pos
[0][OSD_TRIP_DIST
] = OSD_POS(1, 2);
3145 osdLayoutsConfig
->item_pos
[0][OSD_MAIN_BATT_CELL_VOLTAGE
] = OSD_POS(12, 1);
3146 osdLayoutsConfig
->item_pos
[0][OSD_MAIN_BATT_SAG_COMPENSATED_CELL_VOLTAGE
] = OSD_POS(12, 1);
3147 osdLayoutsConfig
->item_pos
[0][OSD_GPS_SPEED
] = OSD_POS(23, 1);
3148 osdLayoutsConfig
->item_pos
[0][OSD_3D_SPEED
] = OSD_POS(23, 1);
3149 osdLayoutsConfig
->item_pos
[0][OSD_GLIDESLOPE
] = OSD_POS(23, 2);
3151 osdLayoutsConfig
->item_pos
[0][OSD_THROTTLE_POS
] = OSD_POS(1, 2) | OSD_VISIBLE_FLAG
;
3152 osdLayoutsConfig
->item_pos
[0][OSD_THROTTLE_POS_AUTO_THR
] = OSD_POS(6, 2);
3153 osdLayoutsConfig
->item_pos
[0][OSD_HEADING
] = OSD_POS(12, 2);
3154 osdLayoutsConfig
->item_pos
[0][OSD_COURSE_HOLD_ERROR
] = OSD_POS(12, 2);
3155 osdLayoutsConfig
->item_pos
[0][OSD_COURSE_HOLD_ADJUSTMENT
] = OSD_POS(12, 2);
3156 osdLayoutsConfig
->item_pos
[0][OSD_HEADING_GRAPH
] = OSD_POS(18, 2);
3157 osdLayoutsConfig
->item_pos
[0][OSD_CURRENT_DRAW
] = OSD_POS(2, 3) | OSD_VISIBLE_FLAG
;
3158 osdLayoutsConfig
->item_pos
[0][OSD_MAH_DRAWN
] = OSD_POS(1, 4) | OSD_VISIBLE_FLAG
;
3159 osdLayoutsConfig
->item_pos
[0][OSD_WH_DRAWN
] = OSD_POS(1, 5);
3160 osdLayoutsConfig
->item_pos
[0][OSD_BATTERY_REMAINING_CAPACITY
] = OSD_POS(1, 6);
3161 osdLayoutsConfig
->item_pos
[0][OSD_BATTERY_REMAINING_PERCENT
] = OSD_POS(1, 7);
3162 osdLayoutsConfig
->item_pos
[0][OSD_POWER_SUPPLY_IMPEDANCE
] = OSD_POS(1, 8);
3164 osdLayoutsConfig
->item_pos
[0][OSD_EFFICIENCY_MAH_PER_KM
] = OSD_POS(1, 5);
3165 osdLayoutsConfig
->item_pos
[0][OSD_EFFICIENCY_WH_PER_KM
] = OSD_POS(1, 5);
3167 osdLayoutsConfig
->item_pos
[0][OSD_ATTITUDE_ROLL
] = OSD_POS(1, 7);
3168 osdLayoutsConfig
->item_pos
[0][OSD_ATTITUDE_PITCH
] = OSD_POS(1, 8);
3170 // avoid OSD_VARIO under OSD_CROSSHAIRS
3171 osdLayoutsConfig
->item_pos
[0][OSD_VARIO
] = OSD_POS(23, 5);
3172 // OSD_VARIO_NUM at the right of OSD_VARIO
3173 osdLayoutsConfig
->item_pos
[0][OSD_VARIO_NUM
] = OSD_POS(24, 7);
3174 osdLayoutsConfig
->item_pos
[0][OSD_HOME_DIR
] = OSD_POS(14, 11);
3175 osdLayoutsConfig
->item_pos
[0][OSD_ARTIFICIAL_HORIZON
] = OSD_POS(8, 6);
3176 osdLayoutsConfig
->item_pos
[0][OSD_HORIZON_SIDEBARS
] = OSD_POS(8, 6);
3178 osdLayoutsConfig
->item_pos
[0][OSD_CRAFT_NAME
] = OSD_POS(20, 2);
3179 osdLayoutsConfig
->item_pos
[0][OSD_VTX_CHANNEL
] = OSD_POS(8, 6);
3181 #ifdef USE_SERIALRX_CRSF
3182 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_RSSI_DBM
] = OSD_POS(23, 12);
3183 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_LQ
] = OSD_POS(23, 11);
3184 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_SNR_DB
] = OSD_POS(24, 9);
3185 osdLayoutsConfig
->item_pos
[0][OSD_CRSF_TX_POWER
] = OSD_POS(24, 10);
3188 osdLayoutsConfig
->item_pos
[0][OSD_ONTIME
] = OSD_POS(23, 8);
3189 osdLayoutsConfig
->item_pos
[0][OSD_FLYTIME
] = OSD_POS(23, 9);
3190 osdLayoutsConfig
->item_pos
[0][OSD_ONTIME_FLYTIME
] = OSD_POS(23, 11) | OSD_VISIBLE_FLAG
;
3191 osdLayoutsConfig
->item_pos
[0][OSD_RTC_TIME
] = OSD_POS(23, 12);
3192 osdLayoutsConfig
->item_pos
[0][OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
] = OSD_POS(23, 7);
3193 osdLayoutsConfig
->item_pos
[0][OSD_REMAINING_DISTANCE_BEFORE_RTH
] = OSD_POS(23, 6);
3195 osdLayoutsConfig
->item_pos
[0][OSD_GPS_SATS
] = OSD_POS(0, 11) | OSD_VISIBLE_FLAG
;
3196 osdLayoutsConfig
->item_pos
[0][OSD_GPS_HDOP
] = OSD_POS(0, 10);
3198 osdLayoutsConfig
->item_pos
[0][OSD_GPS_LAT
] = OSD_POS(0, 12);
3199 // Put this on top of the latitude, since it's very unlikely
3200 // that users will want to use both at the same time.
3201 osdLayoutsConfig
->item_pos
[0][OSD_PLUS_CODE
] = OSD_POS(0, 12);
3202 osdLayoutsConfig
->item_pos
[0][OSD_FLYMODE
] = OSD_POS(13, 12) | OSD_VISIBLE_FLAG
;
3203 osdLayoutsConfig
->item_pos
[0][OSD_GPS_LON
] = OSD_POS(18, 12);
3205 osdLayoutsConfig
->item_pos
[0][OSD_AZIMUTH
] = OSD_POS(2, 12);
3207 osdLayoutsConfig
->item_pos
[0][OSD_ROLL_PIDS
] = OSD_POS(2, 10);
3208 osdLayoutsConfig
->item_pos
[0][OSD_PITCH_PIDS
] = OSD_POS(2, 11);
3209 osdLayoutsConfig
->item_pos
[0][OSD_YAW_PIDS
] = OSD_POS(2, 12);
3210 osdLayoutsConfig
->item_pos
[0][OSD_LEVEL_PIDS
] = OSD_POS(2, 12);
3211 osdLayoutsConfig
->item_pos
[0][OSD_POS_XY_PIDS
] = OSD_POS(2, 12);
3212 osdLayoutsConfig
->item_pos
[0][OSD_POS_Z_PIDS
] = OSD_POS(2, 12);
3213 osdLayoutsConfig
->item_pos
[0][OSD_VEL_XY_PIDS
] = OSD_POS(2, 12);
3214 osdLayoutsConfig
->item_pos
[0][OSD_VEL_Z_PIDS
] = OSD_POS(2, 12);
3215 osdLayoutsConfig
->item_pos
[0][OSD_HEADING_P
] = OSD_POS(2, 12);
3216 osdLayoutsConfig
->item_pos
[0][OSD_BOARD_ALIGN_ROLL
] = OSD_POS(2, 10);
3217 osdLayoutsConfig
->item_pos
[0][OSD_BOARD_ALIGN_PITCH
] = OSD_POS(2, 11);
3218 osdLayoutsConfig
->item_pos
[0][OSD_RC_EXPO
] = OSD_POS(2, 12);
3219 osdLayoutsConfig
->item_pos
[0][OSD_RC_YAW_EXPO
] = OSD_POS(2, 12);
3220 osdLayoutsConfig
->item_pos
[0][OSD_THROTTLE_EXPO
] = OSD_POS(2, 12);
3221 osdLayoutsConfig
->item_pos
[0][OSD_PITCH_RATE
] = OSD_POS(2, 12);
3222 osdLayoutsConfig
->item_pos
[0][OSD_ROLL_RATE
] = OSD_POS(2, 12);
3223 osdLayoutsConfig
->item_pos
[0][OSD_YAW_RATE
] = OSD_POS(2, 12);
3224 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_RC_EXPO
] = OSD_POS(2, 12);
3225 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_RC_YAW_EXPO
] = OSD_POS(2, 12);
3226 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_PITCH_RATE
] = OSD_POS(2, 12);
3227 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_ROLL_RATE
] = OSD_POS(2, 12);
3228 osdLayoutsConfig
->item_pos
[0][OSD_MANUAL_YAW_RATE
] = OSD_POS(2, 12);
3229 osdLayoutsConfig
->item_pos
[0][OSD_NAV_FW_CRUISE_THR
] = OSD_POS(2, 12);
3230 osdLayoutsConfig
->item_pos
[0][OSD_NAV_FW_PITCH2THR
] = OSD_POS(2, 12);
3231 osdLayoutsConfig
->item_pos
[0][OSD_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
] = OSD_POS(2, 12);
3232 osdLayoutsConfig
->item_pos
[0][OSD_FW_ALT_PID_OUTPUTS
] = OSD_POS(2, 12);
3233 osdLayoutsConfig
->item_pos
[0][OSD_FW_POS_PID_OUTPUTS
] = OSD_POS(2, 12);
3234 osdLayoutsConfig
->item_pos
[0][OSD_MC_VEL_X_PID_OUTPUTS
] = OSD_POS(2, 12);
3235 osdLayoutsConfig
->item_pos
[0][OSD_MC_VEL_Y_PID_OUTPUTS
] = OSD_POS(2, 12);
3236 osdLayoutsConfig
->item_pos
[0][OSD_MC_VEL_Z_PID_OUTPUTS
] = OSD_POS(2, 12);
3237 osdLayoutsConfig
->item_pos
[0][OSD_MC_POS_XYZ_P_OUTPUTS
] = OSD_POS(2, 12);
3239 osdLayoutsConfig
->item_pos
[0][OSD_POWER
] = OSD_POS(15, 1);
3241 osdLayoutsConfig
->item_pos
[0][OSD_IMU_TEMPERATURE
] = OSD_POS(19, 2);
3242 osdLayoutsConfig
->item_pos
[0][OSD_BARO_TEMPERATURE
] = OSD_POS(19, 3);
3243 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_0_TEMPERATURE
] = OSD_POS(19, 4);
3244 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_1_TEMPERATURE
] = OSD_POS(19, 5);
3245 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_2_TEMPERATURE
] = OSD_POS(19, 6);
3246 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_3_TEMPERATURE
] = OSD_POS(19, 7);
3247 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_4_TEMPERATURE
] = OSD_POS(19, 8);
3248 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_5_TEMPERATURE
] = OSD_POS(19, 9);
3249 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_6_TEMPERATURE
] = OSD_POS(19, 10);
3250 osdLayoutsConfig
->item_pos
[0][OSD_TEMP_SENSOR_7_TEMPERATURE
] = OSD_POS(19, 11);
3252 osdLayoutsConfig
->item_pos
[0][OSD_AIR_SPEED
] = OSD_POS(3, 5);
3253 osdLayoutsConfig
->item_pos
[0][OSD_WIND_SPEED_HORIZONTAL
] = OSD_POS(3, 6);
3254 osdLayoutsConfig
->item_pos
[0][OSD_WIND_SPEED_VERTICAL
] = OSD_POS(3, 7);
3256 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE
] = OSD_POS(12, 4);
3257 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE_X
] = OSD_POS(12, 5);
3258 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE_Y
] = OSD_POS(12, 6);
3259 osdLayoutsConfig
->item_pos
[0][OSD_GFORCE_Z
] = OSD_POS(12, 7);
3261 osdLayoutsConfig
->item_pos
[0][OSD_VTX_POWER
] = OSD_POS(3, 5);
3263 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_0
] = OSD_POS(1, 1);
3264 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_1
] = OSD_POS(1, 2);
3265 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_2
] = OSD_POS(1, 3);
3266 osdLayoutsConfig
->item_pos
[0][OSD_GVAR_3
] = OSD_POS(1, 4);
3268 #if defined(USE_ESC_SENSOR)
3269 osdLayoutsConfig
->item_pos
[0][OSD_ESC_RPM
] = OSD_POS(1, 2);
3270 osdLayoutsConfig
->item_pos
[0][OSD_ESC_TEMPERATURE
] = OSD_POS(1, 3);
3273 #if defined(USE_RX_MSP) && defined(USE_MSP_RC_OVERRIDE)
3274 osdLayoutsConfig
->item_pos
[0][OSD_RC_SOURCE
] = OSD_POS(3, 4);
3277 #ifdef USE_POWER_LIMITS
3278 osdLayoutsConfig
->item_pos
[0][OSD_PLIMIT_REMAINING_BURST_TIME
] = OSD_POS(3, 4);
3279 osdLayoutsConfig
->item_pos
[0][OSD_PLIMIT_ACTIVE_CURRENT_LIMIT
] = OSD_POS(3, 5);
3280 osdLayoutsConfig
->item_pos
[0][OSD_PLIMIT_ACTIVE_POWER_LIMIT
] = OSD_POS(3, 6);
3283 // Under OSD_FLYMODE. TODO: Might not be visible on NTSC?
3284 osdLayoutsConfig
->item_pos
[0][OSD_MESSAGES
] = OSD_POS(1, 13) | OSD_VISIBLE_FLAG
;
3286 for (unsigned ii
= 1; ii
< OSD_LAYOUT_COUNT
; ii
++) {
3287 for (unsigned jj
= 0; jj
< ARRAYLEN(osdLayoutsConfig
->item_pos
[0]); jj
++) {
3288 osdLayoutsConfig
->item_pos
[ii
][jj
] = osdLayoutsConfig
->item_pos
[0][jj
] & ~OSD_VISIBLE_FLAG
;
3293 static void osdSetNextRefreshIn(uint32_t timeMs
) {
3294 resumeRefreshAt
= micros() + timeMs
* 1000;
3295 refreshWaitForResumeCmdRelease
= true;
3298 static void osdCompleteAsyncInitialization(void)
3300 if (!displayIsReady(osdDisplayPort
)) {
3301 // Update the display.
3302 // XXX: Rename displayDrawScreen() and associated functions
3303 // to displayUpdate()
3304 displayDrawScreen(osdDisplayPort
);
3308 osdDisplayIsReady
= true;
3310 #if defined(USE_CANVAS)
3311 if (osdConfig()->force_grid
) {
3312 osdDisplayHasCanvas
= false;
3314 osdDisplayHasCanvas
= displayGetCanvas(&osdCanvas
, osdDisplayPort
);
3318 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
3319 displayClearScreen(osdDisplayPort
);
3322 displayFontMetadata_t metadata
;
3323 bool fontHasMetadata
= displayGetFontMetadata(&metadata
, osdDisplayPort
);
3324 LOG_D(OSD
, "Font metadata version %s: %u (%u chars)",
3325 fontHasMetadata
? "Y" : "N", metadata
.version
, metadata
.charCount
);
3327 if (fontHasMetadata
&& metadata
.charCount
> 256) {
3328 hasExtendedFont
= true;
3329 unsigned logo_c
= SYM_LOGO_START
;
3330 unsigned logo_x
= OSD_CENTER_LEN(SYM_LOGO_WIDTH
);
3331 for (unsigned ii
= 0; ii
< SYM_LOGO_HEIGHT
; ii
++) {
3332 for (unsigned jj
= 0; jj
< SYM_LOGO_WIDTH
; jj
++) {
3333 displayWriteChar(osdDisplayPort
, logo_x
+ jj
, y
, logo_c
++);
3338 } else if (!fontHasMetadata
) {
3339 const char *m
= "INVALID FONT";
3340 displayWrite(osdDisplayPort
, OSD_CENTER_S(m
), 3, m
);
3344 if (fontHasMetadata
&& metadata
.version
< OSD_MIN_FONT_VERSION
) {
3345 const char *m
= "INVALID FONT VERSION";
3346 displayWrite(osdDisplayPort
, OSD_CENTER_S(m
), y
++, m
);
3349 char string_buffer
[30];
3350 tfp_sprintf(string_buffer
, "INAV VERSION: %s", FC_VERSION_STRING
);
3351 displayWrite(osdDisplayPort
, 5, y
++, string_buffer
);
3353 displayWrite(osdDisplayPort
, 7, y
++, CMS_STARTUP_HELP_TEXT1
);
3354 displayWrite(osdDisplayPort
, 11, y
++, CMS_STARTUP_HELP_TEXT2
);
3355 displayWrite(osdDisplayPort
, 11, y
++, CMS_STARTUP_HELP_TEXT3
);
3359 #define STATS_LABEL_X_POS 4
3360 #define STATS_VALUE_X_POS 24
3361 if (statsConfig()->stats_enabled
) {
3362 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "ODOMETER:");
3363 switch (osdConfig()->units
) {
3366 case OSD_UNIT_IMPERIAL
:
3367 tfp_sprintf(string_buffer
, "%5d", (int)(statsConfig()->stats_total_dist
/ METERS_PER_MILE
));
3368 string_buffer
[5] = SYM_MI
;
3372 tfp_sprintf(string_buffer
, "%5d", (int)(statsConfig()->stats_total_dist
/ METERS_PER_NAUTICALMILE
));
3373 string_buffer
[5] = SYM_NM
;
3375 case OSD_UNIT_METRIC_MPH
:
3377 case OSD_UNIT_METRIC
:
3378 tfp_sprintf(string_buffer
, "%5d", (int)(statsConfig()->stats_total_dist
/ METERS_PER_KILOMETER
));
3379 string_buffer
[5] = SYM_KM
;
3382 string_buffer
[6] = '\0';
3383 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-5, y
, string_buffer
);
3385 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "TOTAL TIME:");
3386 uint32_t tot_mins
= statsConfig()->stats_total_time
/ 60;
3387 tfp_sprintf(string_buffer
, "%2d:%02dHM", (int)(tot_mins
/ 60), (int)(tot_mins
% 60));
3388 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-5, y
, string_buffer
);
3391 if (feature(FEATURE_VBAT
) && feature(FEATURE_CURRENT_METER
)) {
3392 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "TOTAL ENERGY:");
3393 osdFormatCentiNumber(string_buffer
, statsConfig()->stats_total_energy
/ 10, 0, 2, 0, 4);
3394 strcat(string_buffer
, "\xAB"); // SYM_WH
3395 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-4, y
, string_buffer
);
3397 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "AVG EFFICIENCY:");
3398 if (statsConfig()->stats_total_dist
) {
3399 uint32_t avg_efficiency
= statsConfig()->stats_total_energy
/ (statsConfig()->stats_total_dist
/ METERS_PER_KILOMETER
); // mWh/km
3400 switch (osdConfig()->units
) {
3403 case OSD_UNIT_IMPERIAL
:
3404 osdFormatCentiNumber(string_buffer
, avg_efficiency
/ 10, 0, 2, 0, 3);
3405 string_buffer
[3] = SYM_WH_MI
;
3408 osdFormatCentiNumber(string_buffer
, avg_efficiency
/ 10, 0, 2, 0, 3);
3409 string_buffer
[3] = SYM_WH_NM
;
3412 case OSD_UNIT_METRIC_MPH
:
3414 case OSD_UNIT_METRIC
:
3415 osdFormatCentiNumber(string_buffer
, avg_efficiency
/ 10000 * METERS_PER_MILE
, 0, 2, 0, 3);
3416 string_buffer
[3] = SYM_WH_KM
;
3420 string_buffer
[0] = string_buffer
[1] = string_buffer
[2] = '-';
3422 string_buffer
[4] = '\0';
3423 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-3, y
, string_buffer
);
3429 displayCommitTransaction(osdDisplayPort
);
3430 displayResync(osdDisplayPort
);
3431 osdSetNextRefreshIn(SPLASH_SCREEN_DISPLAY_TIME
);
3434 void osdInit(displayPort_t
*osdDisplayPortToUse
)
3436 if (!osdDisplayPortToUse
)
3439 BUILD_BUG_ON(OSD_POS_MAX
!= OSD_POS(31,31));
3441 osdDisplayPort
= osdDisplayPortToUse
;
3444 cmsDisplayPortRegister(osdDisplayPort
);
3447 armState
= ARMING_FLAG(ARMED
);
3448 osdCompleteAsyncInitialization();
3451 static void osdResetStats(void)
3453 stats
.max_current
= 0;
3454 stats
.max_power
= 0;
3455 stats
.max_speed
= 0;
3456 stats
.max_3D_speed
= 0;
3457 stats
.min_voltage
= 5000;
3458 stats
.min_rssi
= 99;
3460 stats
.min_rssi_dbm
= 0;
3461 stats
.max_altitude
= 0;
3464 static void osdUpdateStats(void)
3468 if (feature(FEATURE_GPS
)) {
3469 value
= osdGet3DSpeed();
3470 if (stats
.max_3D_speed
< value
)
3471 stats
.max_3D_speed
= value
;
3473 if (stats
.max_speed
< gpsSol
.groundSpeed
)
3474 stats
.max_speed
= gpsSol
.groundSpeed
;
3476 if (stats
.max_distance
< GPS_distanceToHome
)
3477 stats
.max_distance
= GPS_distanceToHome
;
3480 value
= getBatteryVoltage();
3481 if (stats
.min_voltage
> value
)
3482 stats
.min_voltage
= value
;
3484 value
= abs(getAmperage());
3485 if (stats
.max_current
< value
)
3486 stats
.max_current
= value
;
3488 value
= labs(getPower());
3489 if (stats
.max_power
< value
)
3490 stats
.max_power
= value
;
3492 value
= osdConvertRSSI();
3493 if (stats
.min_rssi
> value
)
3494 stats
.min_rssi
= value
;
3496 value
= osdGetCrsfLQ();
3497 if (stats
.min_lq
> value
)
3498 stats
.min_lq
= value
;
3500 value
= osdGetCrsfdBm();
3501 if (stats
.min_rssi_dbm
> value
)
3502 stats
.min_rssi_dbm
= value
;
3504 stats
.max_altitude
= MAX(stats
.max_altitude
, osdGetAltitude());
3507 static void osdShowStatsPage1(void)
3509 const char * disarmReasonStr
[DISARM_REASON_COUNT
] = { "UNKNOWN", "TIMEOUT", "STICKS", "SWITCH", "SWITCH", "KILLSW", "FAILSAFE", "NAV SYS" };
3510 uint8_t top
= 1; /* first fully visible line */
3511 const uint8_t statNameX
= 1;
3512 const uint8_t statValuesX
= 20;
3514 statsPagesCheck
= 1;
3516 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
3517 displayClearScreen(osdDisplayPort
);
3519 displayWrite(osdDisplayPort
, statNameX
, top
++, "--- STATS --- 1/2 ->");
3521 if (feature(FEATURE_GPS
)) {
3522 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX SPEED :");
3523 osdFormatVelocityStr(buff
, stats
.max_3D_speed
, true, false);
3524 osdLeftAlignString(buff
);
3525 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3527 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX DISTANCE :");
3528 osdFormatDistanceStr(buff
, stats
.max_distance
*100);
3529 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3531 displayWrite(osdDisplayPort
, statNameX
, top
, "TRAVELED DISTANCE:");
3532 osdFormatDistanceStr(buff
, getTotalTravelDistance());
3533 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3536 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX ALTITUDE :");
3537 osdFormatAltitudeStr(buff
, stats
.max_altitude
);
3538 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3540 switch (rxConfig()->serialrx_provider
) {
3542 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN RSSI % :");
3543 itoa(stats
.min_rssi
, buff
, 10);
3545 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3547 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN RSSI DBM :");
3548 itoa(stats
.min_rssi_dbm
, buff
, 10);
3549 tfp_sprintf(buff
, "%s%c", buff
, SYM_DBM
);
3550 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3552 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN LQ :");
3553 itoa(stats
.min_lq
, buff
, 10);
3555 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3558 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN RSSI :");
3559 itoa(stats
.min_rssi
, buff
, 10);
3561 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3564 displayWrite(osdDisplayPort
, statNameX
, top
, "FLY TIME :");
3565 uint16_t flySeconds
= getFlightTime();
3566 uint16_t flyMinutes
= flySeconds
/ 60;
3568 uint16_t flyHours
= flyMinutes
/ 60;
3570 tfp_sprintf(buff
, "%02u:%02u:%02u", flyHours
, flyMinutes
, flySeconds
);
3571 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3573 displayWrite(osdDisplayPort
, statNameX
, top
, "DISARMED BY :");
3574 displayWrite(osdDisplayPort
, statValuesX
, top
++, disarmReasonStr
[getDisarmReason()]);
3575 displayCommitTransaction(osdDisplayPort
);
3578 static void osdShowStatsPage2(void)
3580 uint8_t top
= 1; /* first fully visible line */
3581 const uint8_t statNameX
= 1;
3582 const uint8_t statValuesX
= 20;
3584 statsPagesCheck
= 1;
3586 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
3587 displayClearScreen(osdDisplayPort
);
3589 displayWrite(osdDisplayPort
, statNameX
, top
++, "--- STATS --- <- 2/2");
3591 if (osdConfig()->stats_min_voltage_unit
== OSD_STATS_MIN_VOLTAGE_UNIT_BATTERY
) {
3592 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN BATTERY VOLT :");
3593 osdFormatCentiNumber(buff
, stats
.min_voltage
, 0, osdConfig()->main_voltage_decimals
, 0, osdConfig()->main_voltage_decimals
+ 2);
3595 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN CELL VOLTAGE :");
3596 osdFormatCentiNumber(buff
, stats
.min_voltage
/getBatteryCellCount(), 0, 2, 0, 3);
3598 tfp_sprintf(buff
, "%s%c", buff
, SYM_VOLT
);
3599 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3601 if (feature(FEATURE_CURRENT_METER
)) {
3602 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX CURRENT :");
3603 osdFormatCentiNumber(buff
, stats
.max_current
, 0, 2, 0, 3);
3604 tfp_sprintf(buff
, "%s%c", buff
, SYM_AMP
);
3605 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3607 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX POWER :");
3608 bool kiloWatt
= osdFormatCentiNumber(buff
, stats
.max_power
, 1000, 2, 2, 3);
3609 buff
[3] = kiloWatt
? SYM_KILOWATT
: SYM_WATT
;
3611 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3613 displayWrite(osdDisplayPort
, statNameX
, top
, "USED CAPACITY :");
3614 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
3615 tfp_sprintf(buff
, "%d%c", (int)getMAhDrawn(), SYM_MAH
);
3617 osdFormatCentiNumber(buff
, getMWhDrawn() / 10, 0, 2, 0, 3);
3618 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH
);
3620 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3622 int32_t totalDistance
= getTotalTravelDistance();
3623 bool moreThanAh
= false;
3624 bool efficiencyValid
= totalDistance
>= 10000;
3625 if (feature(FEATURE_GPS
)) {
3626 displayWrite(osdDisplayPort
, statNameX
, top
, "AVG EFFICIENCY :");
3627 switch (osdConfig()->units
) {
3630 case OSD_UNIT_IMPERIAL
:
3631 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
3632 moreThanAh
= osdFormatCentiNumber(buff
, (int32_t)(getMAhDrawn() * 10000.0f
* METERS_PER_MILE
/ totalDistance
), 1000, 0, 2, 3);
3634 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_MI_0
, SYM_MAH_MI_1
);
3636 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_MI
);
3638 if (!efficiencyValid
) {
3639 buff
[0] = buff
[1] = buff
[2] = '-';
3640 buff
[3] = SYM_MAH_MI_0
;
3641 buff
[4] = SYM_MAH_MI_1
;
3645 osdFormatCentiNumber(buff
, (int32_t)(getMWhDrawn() * 10.0f
* METERS_PER_MILE
/ totalDistance
), 0, 2, 0, 3);
3646 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH_MI
);
3647 if (!efficiencyValid
) {
3648 buff
[0] = buff
[1] = buff
[2] = '-';
3653 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
3654 moreThanAh
= osdFormatCentiNumber(buff
, (int32_t)(getMAhDrawn() * 10000.0f
* METERS_PER_NAUTICALMILE
/ totalDistance
), 1000, 0, 2, 3);
3656 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_NM_0
, SYM_MAH_NM_1
);
3658 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_NM
);
3660 if (!efficiencyValid
) {
3661 buff
[0] = buff
[1] = buff
[2] = '-';
3662 buff
[3] = SYM_MAH_NM_0
;
3663 buff
[4] = SYM_MAH_NM_1
;
3667 osdFormatCentiNumber(buff
, (int32_t)(getMWhDrawn() * 10.0f
* METERS_PER_NAUTICALMILE
/ totalDistance
), 0, 2, 0, 3);
3668 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH_NM
);
3669 if (!efficiencyValid
) {
3670 buff
[0] = buff
[1] = buff
[2] = '-';
3674 case OSD_UNIT_METRIC_MPH
:
3676 case OSD_UNIT_METRIC
:
3677 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
3678 moreThanAh
= osdFormatCentiNumber(buff
, (int32_t)(getMAhDrawn() * 10000000.0f
/ totalDistance
), 1000, 0, 2, 3);
3680 tfp_sprintf(buff
, "%s%c%c", buff
, SYM_MAH_KM_0
, SYM_MAH_KM_1
);
3682 tfp_sprintf(buff
, "%s%c", buff
, SYM_AH_KM
);
3684 if (!efficiencyValid
) {
3685 buff
[0] = buff
[1] = buff
[2] = '-';
3686 buff
[3] = SYM_MAH_KM_0
;
3687 buff
[4] = SYM_MAH_KM_1
;
3691 osdFormatCentiNumber(buff
, (int32_t)(getMWhDrawn() * 10000.0f
/ totalDistance
), 0, 2, 0, 3);
3692 tfp_sprintf(buff
, "%s%c", buff
, SYM_WH_KM
);
3693 if (!efficiencyValid
) {
3694 buff
[0] = buff
[1] = buff
[2] = '-';
3699 osdLeftAlignString(buff
);
3700 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3704 const float max_gforce
= accGetMeasuredMaxG();
3705 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX G-FORCE :");
3706 osdFormatCentiNumber(buff
, max_gforce
* 100, 0, 2, 0, 3);
3707 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
3709 const acc_extremes_t
*acc_extremes
= accGetMeasuredExtremes();
3710 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN/MAX Z G-FORCE:");
3711 osdFormatCentiNumber(buff
, acc_extremes
[Z
].min
* 100, 0, 2, 0, 4);
3713 displayWrite(osdDisplayPort
, statValuesX
- 1, top
, buff
);
3714 osdFormatCentiNumber(buff
, acc_extremes
[Z
].max
* 100, 0, 2, 0, 3);
3715 displayWrite(osdDisplayPort
, statValuesX
+ 4, top
++, buff
);
3716 displayCommitTransaction(osdDisplayPort
);
3719 // called when motors armed
3720 static void osdShowArmed(void)
3723 char buf
[MAX(32, FORMATTED_DATE_TIME_BUFSIZE
)];
3724 char craftNameBuf
[MAX_NAME_LENGTH
];
3725 char versionBuf
[30];
3728 // We need 12 visible rows, start row never < first fully visible row 1
3729 uint8_t y
= osdDisplayPort
->rows
> 13 ? (osdDisplayPort
->rows
- 12) / 2 : 1;
3731 displayClearScreen(osdDisplayPort
);
3732 displayWrite(osdDisplayPort
, 12, y
, "ARMED");
3735 if (strlen(systemConfig()->name
) > 0) {
3736 osdFormatCraftName(craftNameBuf
);
3737 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(systemConfig() -> name
)) / 2, y
, craftNameBuf
);
3741 if (posControl
.waypointListValid
&& posControl
.waypointCount
> 0) {
3742 displayWrite(osdDisplayPort
, 7, y
, "*MISSION LOADED*");
3746 #if defined(USE_GPS)
3747 if (feature(FEATURE_GPS
)) {
3748 if (STATE(GPS_FIX_HOME
)) {
3749 if (osdConfig()->osd_home_position_arm_screen
){
3750 osdFormatCoordinate(buf
, SYM_LAT
, GPS_home
.lat
);
3751 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
3752 osdFormatCoordinate(buf
, SYM_LON
, GPS_home
.lon
);
3753 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
+ 1, buf
);
3754 int digits
= osdConfig()->plus_code_digits
;
3755 olc_encode(GPS_home
.lat
, GPS_home
.lon
, digits
, buf
, sizeof(buf
));
3756 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
+ 2, buf
);
3759 #if defined (USE_SAFE_HOME)
3760 if (safehome_distance
) { // safehome found during arming
3761 if (navConfig()->general
.flags
.safehome_usage_mode
== SAFEHOME_USAGE_OFF
) {
3762 strcpy(buf
, "SAFEHOME FOUND; MODE OFF");
3764 char buf2
[12]; // format the distance first
3765 osdFormatDistanceStr(buf2
, safehome_distance
);
3766 tfp_sprintf(buf
, "%c - %s -> SAFEHOME %u", SYM_HOME
, buf2
, safehome_index
);
3768 textAttributes_t elemAttr
= _TEXT_ATTRIBUTES_BLINK_BIT
;
3769 // write this message above the ARMED message to make it obvious
3770 displayWriteWithAttr(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
- 8, buf
, elemAttr
);
3774 strcpy(buf
, "!NO HOME POSITION!");
3775 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
3781 if (rtcGetDateTime(&dt
)) {
3782 dateTimeFormatLocal(buf
, &dt
);
3783 dateTimeSplitFormatted(buf
, &date
, &time
);
3785 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(date
)) / 2, y
, date
);
3786 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(time
)) / 2, y
+ 1, time
);
3790 tfp_sprintf(versionBuf
, "INAV VERSION: %s", FC_VERSION_STRING
);
3791 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(versionBuf
)) / 2, y
, versionBuf
);
3794 static void osdFilterData(timeUs_t currentTimeUs
) {
3795 static timeUs_t lastRefresh
= 0;
3796 float refresh_dT
= US2S(cmpTimeUs(currentTimeUs
, lastRefresh
));
3798 GForce
= fast_fsqrtf(vectorNormSquared(&imuMeasuredAccelBF
)) / GRAVITY_MSS
;
3799 for (uint8_t axis
= 0; axis
< XYZ_AXIS_COUNT
; ++axis
) GForceAxis
[axis
] = imuMeasuredAccelBF
.v
[axis
] / GRAVITY_MSS
;
3802 GForce
= pt1FilterApply3(&GForceFilter
, GForce
, refresh_dT
);
3803 for (uint8_t axis
= 0; axis
< XYZ_AXIS_COUNT
; ++axis
) pt1FilterApply3(GForceFilterAxis
+ axis
, GForceAxis
[axis
], refresh_dT
);
3805 pt1FilterInitRC(&GForceFilter
, GFORCE_FILTER_TC
, 0);
3806 pt1FilterReset(&GForceFilter
, GForce
);
3808 for (uint8_t axis
= 0; axis
< XYZ_AXIS_COUNT
; ++axis
) {
3809 pt1FilterInitRC(GForceFilterAxis
+ axis
, GFORCE_FILTER_TC
, 0);
3810 pt1FilterReset(GForceFilterAxis
+ axis
, GForceAxis
[axis
]);
3814 lastRefresh
= currentTimeUs
;
3817 static void osdRefresh(timeUs_t currentTimeUs
)
3819 osdFilterData(currentTimeUs
);
3822 if (IS_RC_MODE_ACTIVE(BOXOSD
) && (!cmsInMenu
) && !(osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
))) {
3824 if (IS_RC_MODE_ACTIVE(BOXOSD
) && !(osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
))) {
3826 displayClearScreen(osdDisplayPort
);
3827 armState
= ARMING_FLAG(ARMED
);
3831 // detect arm/disarm
3832 static uint8_t statsPageAutoSwapCntl
= 2;
3833 if (armState
!= ARMING_FLAG(ARMED
)) {
3834 if (ARMING_FLAG(ARMED
)) {
3836 statsPageAutoSwapCntl
= 2;
3837 osdShowArmed(); // reset statistic etc
3838 uint32_t delay
= ARMED_SCREEN_DISPLAY_TIME
;
3839 statsPagesCheck
= 0;
3840 #if defined(USE_SAFE_HOME)
3841 if (safehome_distance
)
3844 osdSetNextRefreshIn(delay
);
3846 osdShowStatsPage1(); // show first page of statistics
3847 osdSetNextRefreshIn(STATS_SCREEN_DISPLAY_TIME
);
3848 statsPageAutoSwapCntl
= osdConfig()->stats_page_auto_swap_time
> 0 ? 0 : 2; // disable swapping pages when time = 0
3851 armState
= ARMING_FLAG(ARMED
);
3854 if (resumeRefreshAt
) {
3855 // If we already reached he time for the next refresh,
3856 // or THR is high or PITCH is high, resume refreshing.
3857 // Clear the screen first to erase other elements which
3858 // might have been drawn while the OSD wasn't refreshing.
3860 // auto swap stats pages when first shown
3861 // auto swap cancelled using roll stick
3862 if (statsPageAutoSwapCntl
!= 2) {
3863 if (STATS_PAGE1
|| STATS_PAGE2
) {
3864 statsPageAutoSwapCntl
= 2;
3866 if (OSD_ALTERNATING_CHOICES((osdConfig()->stats_page_auto_swap_time
* 1000), 2)) {
3867 if (statsPageAutoSwapCntl
== 0) {
3868 osdShowStatsPage1();
3869 statsPageAutoSwapCntl
= 1;
3872 if (statsPageAutoSwapCntl
== 1) {
3873 osdShowStatsPage2();
3874 statsPageAutoSwapCntl
= 0;
3880 if (!DELAYED_REFRESH_RESUME_COMMAND
)
3881 refreshWaitForResumeCmdRelease
= false;
3883 if ((currentTimeUs
> resumeRefreshAt
) || ((!refreshWaitForResumeCmdRelease
) && DELAYED_REFRESH_RESUME_COMMAND
)) {
3884 displayClearScreen(osdDisplayPort
);
3885 resumeRefreshAt
= 0;
3886 } else if ((currentTimeUs
> resumeRefreshAt
) || ((!refreshWaitForResumeCmdRelease
) && STATS_PAGE1
)) {
3887 if (statsPagesCheck
== 1) {
3888 osdShowStatsPage1();
3890 } else if ((currentTimeUs
> resumeRefreshAt
) || ((!refreshWaitForResumeCmdRelease
) && STATS_PAGE2
)) {
3891 if (statsPagesCheck
== 1) {
3892 osdShowStatsPage2();
3895 displayHeartbeat(osdDisplayPort
);
3901 if (!displayIsGrabbed(osdDisplayPort
)) {
3902 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
3904 displayClearScreen(osdDisplayPort
);
3907 osdDrawNextElement();
3908 displayHeartbeat(osdDisplayPort
);
3909 displayCommitTransaction(osdDisplayPort
);
3910 #ifdef OSD_CALLS_CMS
3912 cmsUpdate(currentTimeUs
);
3919 * Called periodically by the scheduler
3921 void osdUpdate(timeUs_t currentTimeUs
)
3923 static uint32_t counter
= 0;
3925 // don't touch buffers if DMA transaction is in progress
3926 if (displayIsTransferInProgress(osdDisplayPort
)) {
3930 if (!osdDisplayIsReady
) {
3931 osdCompleteAsyncInitialization();
3935 #if defined(OSD_ALTERNATE_LAYOUT_COUNT) && OSD_ALTERNATE_LAYOUT_COUNT > 0
3936 // Check if the layout has changed. Higher numbered
3937 // boxes take priority.
3938 unsigned activeLayout
;
3939 if (layoutOverride
>= 0) {
3940 activeLayout
= layoutOverride
;
3941 // Check for timed override, it will go into effect on
3942 // the next OSD iteration
3943 if (layoutOverrideUntil
> 0 && millis() > layoutOverrideUntil
) {
3944 layoutOverrideUntil
= 0;
3945 layoutOverride
= -1;
3947 } else if (osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
)) {
3950 #if OSD_ALTERNATE_LAYOUT_COUNT > 2
3951 if (IS_RC_MODE_ACTIVE(BOXOSDALT3
))
3955 #if OSD_ALTERNATE_LAYOUT_COUNT > 1
3956 if (IS_RC_MODE_ACTIVE(BOXOSDALT2
))
3960 if (IS_RC_MODE_ACTIVE(BOXOSDALT1
))
3963 #ifdef USE_PROGRAMMING_FRAMEWORK
3964 if (LOGIC_CONDITION_GLOBAL_FLAG(LOGIC_CONDITION_GLOBAL_FLAG_OVERRIDE_OSD_LAYOUT
))
3965 activeLayout
= constrain(logicConditionValuesByType
[LOGIC_CONDITION_SET_OSD_LAYOUT
], 0, OSD_ALTERNATE_LAYOUT_COUNT
);
3970 if (currentLayout
!= activeLayout
) {
3971 currentLayout
= activeLayout
;
3972 osdStartFullRedraw();
3976 #define DRAW_FREQ_DENOM 4
3977 #define STATS_FREQ_DENOM 50
3980 if ((counter
% STATS_FREQ_DENOM
) == 0) {
3984 if ((counter
& DRAW_FREQ_DENOM
) == 0) {
3985 // redraw values in buffer
3986 osdRefresh(currentTimeUs
);
3988 // rest of time redraw screen
3989 displayDrawScreen(osdDisplayPort
);
3993 // do not allow ARM if we are in menu
3994 if (displayIsGrabbed(osdDisplayPort
)) {
3995 ENABLE_ARMING_FLAG(ARMING_DISABLED_OSD_MENU
);
3997 DISABLE_ARMING_FLAG(ARMING_DISABLED_OSD_MENU
);
4002 void osdStartFullRedraw(void)
4007 void osdOverrideLayout(int layout
, timeMs_t duration
)
4009 layoutOverride
= constrain(layout
, -1, ARRAYLEN(osdLayoutsConfig()->item_pos
) - 1);
4010 if (layoutOverride
>= 0 && duration
> 0) {
4011 layoutOverrideUntil
= millis() + duration
;
4013 layoutOverrideUntil
= 0;
4017 int osdGetActiveLayout(bool *overridden
)
4020 *overridden
= layoutOverride
>= 0;
4022 return currentLayout
;
4025 bool osdItemIsFixed(osd_items_e item
)
4027 return item
== OSD_CROSSHAIRS
||
4028 item
== OSD_ARTIFICIAL_HORIZON
||
4029 item
== OSD_HORIZON_SIDEBARS
;
4032 displayPort_t
*osdGetDisplayPort(void)
4034 return osdDisplayPort
;
4037 displayCanvas_t
*osdGetDisplayPortCanvas(void)
4039 #if defined(USE_CANVAS)
4040 if (osdDisplayHasCanvas
) {
4047 textAttributes_t
osdGetSystemMessage(char *buff
, size_t buff_size
, bool isCenteredText
)
4049 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
4052 const char *message
= NULL
;
4053 char messageBuf
[MAX(SETTING_MAX_NAME_LENGTH
, OSD_MESSAGE_LENGTH
+1)];
4054 if (ARMING_FLAG(ARMED
)) {
4055 // Aircraft is armed. We might have up to 5
4056 // messages to show.
4057 const char *messages
[5];
4058 unsigned messageCount
= 0;
4059 if (FLIGHT_MODE(FAILSAFE_MODE
)) {
4060 // In FS mode while being armed too
4061 const char *failsafePhaseMessage
= osdFailsafePhaseMessage();
4062 const char *failsafeInfoMessage
= osdFailsafeInfoMessage();
4063 const char *navStateFSMessage
= navigationStateMessage();
4065 if (failsafePhaseMessage
) {
4066 messages
[messageCount
++] = failsafePhaseMessage
;
4068 if (failsafeInfoMessage
) {
4069 messages
[messageCount
++] = failsafeInfoMessage
;
4071 if (navStateFSMessage
) {
4072 messages
[messageCount
++] = navStateFSMessage
;
4074 #if defined(USE_SAFE_HOME)
4075 const char *safehomeMessage
= divertingToSafehomeMessage();
4076 if (safehomeMessage
) {
4077 messages
[messageCount
++] = safehomeMessage
;
4080 if (messageCount
> 0) {
4081 message
= messages
[OSD_ALTERNATING_CHOICES(1000, messageCount
)];
4082 if (message
== failsafeInfoMessage
) {
4083 // failsafeInfoMessage is not useful for recovering
4084 // a lost model, but might help avoiding a crash.
4085 // Blink to grab user attention.
4086 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
4088 // We're shoing either failsafePhaseMessage or
4089 // navStateFSMessage. Don't BLINK here since
4090 // having this text available might be crucial
4091 // during a lost aircraft recovery and blinking
4092 // will cause it to be missing from some frames.
4095 if (FLIGHT_MODE(NAV_RTH_MODE
) || FLIGHT_MODE(NAV_WP_MODE
) || navigationIsExecutingAnEmergencyLanding()) {
4096 if (isWaypointMissionRTHActive()) {
4097 // if RTH activated whilst WP mode selected, remind pilot to cancel WP mode to exit RTH
4098 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_WP_RTH_CANCEL
);
4101 if (navGetCurrentStateFlags() & NAV_AUTO_WP_DONE
) {
4102 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_WP_FINISHED
);
4103 } else if (NAV_Status
.state
== MW_NAV_STATE_WP_ENROUTE
) {
4104 // Countdown display for remaining Waypoints
4106 osdFormatDistanceSymbol(buf
, posControl
.wpDistance
, 0);
4107 tfp_sprintf(messageBuf
, "TO WP %u/%u (%s)", getGeoWaypointNumber(posControl
.activeWaypointIndex
), posControl
.geoWaypointCount
, buf
);
4108 messages
[messageCount
++] = messageBuf
;
4109 } else if (NAV_Status
.state
== MW_NAV_STATE_HOLD_TIMED
) {
4110 // WP hold time countdown in seconds
4111 timeMs_t currentTime
= millis();
4112 int holdTimeRemaining
= posControl
.waypointList
[posControl
.activeWaypointIndex
].p1
- (int)((currentTime
- posControl
.wpReachedTime
)/1000);
4113 if (holdTimeRemaining
>=0) {
4114 tfp_sprintf(messageBuf
, "HOLDING WP FOR %2u S", holdTimeRemaining
);
4115 messages
[messageCount
++] = messageBuf
;
4118 const char *navStateMessage
= navigationStateMessage();
4119 if (navStateMessage
) {
4120 messages
[messageCount
++] = navStateMessage
;
4123 #if defined(USE_SAFE_HOME)
4124 const char *safehomeMessage
= divertingToSafehomeMessage();
4125 if (safehomeMessage
) {
4126 messages
[messageCount
++] = safehomeMessage
;
4129 } else if (STATE(FIXED_WING_LEGACY
) && (navGetCurrentStateFlags() & NAV_CTL_LAUNCH
)) {
4130 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_AUTOLAUNCH
);
4131 const char *launchStateMessage
= fixedWingLaunchStateMessage();
4132 if (launchStateMessage
) {
4133 messages
[messageCount
++] = launchStateMessage
;
4136 if (FLIGHT_MODE(NAV_ALTHOLD_MODE
) && !navigationRequiresAngleMode()) {
4137 // ALTHOLD might be enabled alongside ANGLE/HORIZON/ACRO
4138 // when it doesn't require ANGLE mode (required only in FW
4139 // right now). If if requires ANGLE, its display is handled
4141 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_ALTITUDE_HOLD
);
4143 if (IS_RC_MODE_ACTIVE(BOXAUTOTRIM
) && !feature(FEATURE_FW_AUTOTRIM
)) {
4144 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_AUTOTRIM
);
4146 if (IS_RC_MODE_ACTIVE(BOXAUTOTUNE
)) {
4147 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_AUTOTUNE
);
4148 if (FLIGHT_MODE(MANUAL_MODE
)) {
4149 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_AUTOTUNE_ACRO
);
4152 if (FLIGHT_MODE(HEADFREE_MODE
)) {
4153 messages
[messageCount
++] = OSD_MESSAGE_STR(OSD_MSG_HEADFREE
);
4156 // Pick one of the available messages. Each message lasts
4158 if (messageCount
> 0) {
4159 message
= messages
[OSD_ALTERNATING_CHOICES(1000, messageCount
)];
4162 } else if (ARMING_FLAG(ARMING_DISABLED_ALL_FLAGS
)) {
4163 unsigned invalidIndex
;
4164 // Check if we're unable to arm for some reason
4165 if (ARMING_FLAG(ARMING_DISABLED_INVALID_SETTING
) && !settingsValidate(&invalidIndex
)) {
4166 if (OSD_ALTERNATING_CHOICES(1000, 2) == 0) {
4167 const setting_t
*setting
= settingGet(invalidIndex
);
4168 settingGetName(setting
, messageBuf
);
4169 for (int ii
= 0; messageBuf
[ii
]; ii
++) {
4170 messageBuf
[ii
] = sl_toupper(messageBuf
[ii
]);
4172 message
= messageBuf
;
4174 message
= OSD_MESSAGE_STR(OSD_MSG_INVALID_SETTING
);
4175 TEXT_ATTRIBUTES_ADD_INVERTED(elemAttr
);
4178 if (OSD_ALTERNATING_CHOICES(1000, 2) == 0) {
4179 message
= OSD_MESSAGE_STR(OSD_MSG_UNABLE_ARM
);
4180 TEXT_ATTRIBUTES_ADD_INVERTED(elemAttr
);
4182 // Show the reason for not arming
4183 message
= osdArmingDisabledReasonMessage();
4187 osdFormatMessage(buff
, buff_size
, message
, isCenteredText
);