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
36 #include "build/debug.h"
37 #include "build/version.h"
40 #include "cms/cms_types.h"
41 #include "cms/cms_menu_osd.h"
43 #include "common/axis.h"
44 #include "common/filter.h"
45 #include "common/printf.h"
46 #include "common/string_light.h"
47 #include "common/time.h"
48 #include "common/typeconversion.h"
49 #include "common/utils.h"
51 #include "config/feature.h"
52 #include "config/parameter_group.h"
53 #include "config/parameter_group_ids.h"
55 #include "drivers/display.h"
56 #include "drivers/max7456_symbols.h"
57 #include "drivers/time.h"
58 #include "drivers/vtx_common.h"
60 #include "io/flashfs.h"
63 #include "io/vtx_string.h"
65 #include "fc/config.h"
66 #include "fc/controlrate_profile.h"
67 #include "fc/fc_core.h"
68 #include "fc/fc_tasks.h"
69 #include "fc/rc_adjustments.h"
70 #include "fc/rc_controls.h"
71 #include "fc/rc_modes.h"
72 #include "fc/runtime_config.h"
73 #include "fc/settings.h"
75 #include "flight/imu.h"
76 #include "flight/mixer.h"
77 #include "flight/pid.h"
78 #include "flight/rth_estimator.h"
79 #include "flight/wind_estimator.h"
81 #include "navigation/navigation.h"
85 #include "sensors/battery.h"
86 #include "sensors/boardalignment.h"
87 #include "sensors/diagnostics.h"
88 #include "sensors/sensors.h"
89 #include "sensors/pitotmeter.h"
90 #include "sensors/temperature.h"
92 #ifdef USE_HARDWARE_REVISION_DETECTION
93 #include "hardware_revision.h"
96 #define VIDEO_BUFFER_CHARS_PAL 480
97 #define IS_DISPLAY_PAL (displayScreenSize(osdDisplayPort) == VIDEO_BUFFER_CHARS_PAL)
99 #define CENTIMETERS_TO_CENTIFEET(cm) (cm * (328 / 100.0))
100 #define CENTIMETERS_TO_FEET(cm) (cm * (328 / 10000.0))
101 #define CENTIMETERS_TO_METERS(cm) (cm / 100)
102 #define FEET_PER_MILE 5280
103 #define FEET_PER_KILOFEET 1000 // Used for altitude
104 #define METERS_PER_KILOMETER 1000
105 #define METERS_PER_MILE 1609
107 #define DELAYED_REFRESH_RESUME_COMMAND (checkStickPosition(THR_HI) || checkStickPosition(PIT_HI))
109 #define SPLASH_SCREEN_DISPLAY_TIME 4000 // ms
110 #define ARMED_SCREEN_DISPLAY_TIME 1500 // ms
111 #define STATS_SCREEN_DISPLAY_TIME 60000 // ms
113 #define EFFICIENCY_UPDATE_INTERVAL (5 * 1000)
115 // Adjust OSD_MESSAGE's default position when
116 // changing OSD_MESSAGE_LENGTH
117 #define OSD_MESSAGE_LENGTH 28
118 #define OSD_ALTERNATING_CHOICES(ms, num_choices) ((millis() / ms) % num_choices)
119 #define _CONST_STR_SIZE(s) ((sizeof(s)/sizeof(s[0]))-1) // -1 to avoid counting final '\0'
120 // Wrap all string constants intenteded for display as messages with
121 // this macro to ensure compile time length validation.
122 #define OSD_MESSAGE_STR(x) ({ \
123 STATIC_ASSERT(_CONST_STR_SIZE(x) <= OSD_MESSAGE_LENGTH, message_string_ ## __COUNTER__ ## _too_long); \
127 static unsigned currentLayout
= 0;
128 static int layoutOverride
= -1;
130 typedef struct statistic_s
{
132 uint16_t min_voltage
; // /100
133 int16_t max_current
; // /100
134 int16_t max_power
; // /100
136 int32_t max_altitude
;
137 uint16_t max_distance
;
140 static statistic_t stats
;
143 OSD_SIDEBAR_ARROW_NONE
,
144 OSD_SIDEBAR_ARROW_UP
,
145 OSD_SIDEBAR_ARROW_DOWN
,
146 } osd_sidebar_arrow_e
;
148 typedef struct osd_sidebar_s
{
151 osd_sidebar_arrow_e arrow
;
155 static timeUs_t resumeRefreshAt
= 0;
156 static bool refreshWaitForResumeCmdRelease
;
158 static bool fullRedraw
= false;
160 static uint8_t armState
;
162 static displayPort_t
*osdDisplayPort
;
164 #define AH_MAX_PITCH_DEFAULT 20 // Specify default maximum AHI pitch value displayed (degrees)
167 #define AH_PREV_SIZE (AH_WIDTH > AH_HEIGHT ? AH_WIDTH : AH_HEIGHT)
168 #define AH_H_SYM_COUNT 9
169 #define AH_V_SYM_COUNT 6
170 #define AH_SIDEBAR_WIDTH_POS 7
171 #define AH_SIDEBAR_HEIGHT_POS 3
173 PG_REGISTER_WITH_RESET_FN(osdConfig_t
, osdConfig
, PG_OSD_CONFIG
, 5);
175 static int digitCount(int32_t value
)
189 * Formats a number given in cents, to support non integer values
190 * without using floating point math. Value is always right aligned
191 * and spaces are inserted before the number to always yield a string
192 * of the same length. If the value doesn't fit into the provided length
193 * it will be divided by scale and true will be returned.
195 static bool osdFormatCentiNumber(char *buff
, int32_t centivalue
, uint32_t scale
, int maxDecimals
, int maxScaledDecimals
, int length
)
199 int decimals
= maxDecimals
;
200 bool negative
= false;
205 if (centivalue
< 0) {
207 centivalue
= -centivalue
;
211 int32_t integerPart
= centivalue
/ 100;
213 int32_t millis
= (centivalue
% 100) * 10;
215 int digits
= digitCount(integerPart
);
216 int remaining
= length
- digits
;
218 if (remaining
< 0 && scale
> 0) {
221 decimals
= maxScaledDecimals
;
222 integerPart
= integerPart
/ scale
;
223 // Multiply by 10 to get 3 decimal digits
224 millis
= ((centivalue
% (100 * scale
)) * 10) / scale
;
225 digits
= digitCount(integerPart
);
226 remaining
= length
- digits
;
229 // 3 decimals at most
230 decimals
= MIN(remaining
, MIN(decimals
, 3));
231 remaining
-= decimals
;
233 // Done counting. Time to write the characters.
235 // Write spaces at the start
236 while (remaining
> 0) {
242 // Write the minus sign if required
247 // Now write the digits.
248 ui2a(integerPart
, 10, 0, ptr
);
251 *(ptr
-1) += SYM_ZERO_HALF_TRAILING_DOT
- '0';
253 int factor
= 3; // we're getting the decimal part in millis first
254 while (decimals
< factor
) {
258 int decimalDigits
= digitCount(millis
);
259 while (decimalDigits
< decimals
) {
264 ui2a(millis
, 10, 0, ptr
);
265 *dec
+= SYM_ZERO_HALF_LEADING_DOT
- '0';
271 * Converts distance into a string based on the current unit system
272 * prefixed by a a symbol to indicate the unit used.
273 * @param dist Distance in centimeters
275 static void osdFormatDistanceSymbol(char *buff
, int32_t dist
)
277 switch ((osd_unit_e
)osdConfig()->units
) {
278 case OSD_UNIT_IMPERIAL
:
279 if (osdFormatCentiNumber(buff
+ 1, CENTIMETERS_TO_CENTIFEET(dist
), FEET_PER_MILE
, 0, 3, 3)) {
280 buff
[0] = SYM_DIST_MI
;
282 buff
[0] = SYM_DIST_FT
;
287 case OSD_UNIT_METRIC
:
288 if (osdFormatCentiNumber(buff
+ 1, dist
, METERS_PER_KILOMETER
, 0, 3, 3)) {
289 buff
[0] = SYM_DIST_KM
;
291 buff
[0] = SYM_DIST_M
;
298 * Converts distance into a string based on the current unit system.
299 * @param dist Distance in centimeters
301 static void osdFormatDistanceStr(char *buff
, int32_t dist
)
304 switch ((osd_unit_e
)osdConfig()->units
) {
305 case OSD_UNIT_IMPERIAL
:
306 centifeet
= CENTIMETERS_TO_CENTIFEET(dist
);
307 if (abs(centifeet
) < FEET_PER_MILE
* 100 / 2) {
308 // Show feet when dist < 0.5mi
309 tfp_sprintf(buff
, "%d%c", centifeet
/ 100, SYM_FT
);
311 // Show miles when dist >= 0.5mi
312 tfp_sprintf(buff
, "%d.%02d%c", centifeet
/ (100*FEET_PER_MILE
),
313 (abs(centifeet
) % (100 * FEET_PER_MILE
)) / FEET_PER_MILE
, SYM_MI
);
318 case OSD_UNIT_METRIC
:
319 if (abs(dist
) < METERS_PER_KILOMETER
* 100) {
320 // Show meters when dist < 1km
321 tfp_sprintf(buff
, "%d%c", dist
/ 100, SYM_M
);
323 // Show kilometers when dist >= 1km
324 tfp_sprintf(buff
, "%d.%02d%c", dist
/ (100*METERS_PER_KILOMETER
),
325 (abs(dist
) % (100 * METERS_PER_KILOMETER
)) / METERS_PER_KILOMETER
, SYM_KM
);
332 * Converts velocity based on the current unit system (kmh or mph).
333 * @param alt Raw velocity (i.e. as taken from gpsSol.groundSpeed in centimeters/second)
335 static int32_t osdConvertVelocityToUnit(int32_t vel
)
337 switch ((osd_unit_e
)osdConfig()->units
) {
340 case OSD_UNIT_IMPERIAL
:
341 return (vel
* 224) / 10000; // Convert to mph
342 case OSD_UNIT_METRIC
:
343 return (vel
* 36) / 1000; // Convert to kmh
350 * Converts velocity into a string based on the current unit system.
351 * @param alt Raw velocity (i.e. as taken from gpsSol.groundSpeed in centimeters/seconds)
353 static void osdFormatVelocityStr(char* buff
, int32_t vel
, bool _3D
)
355 switch ((osd_unit_e
)osdConfig()->units
) {
358 case OSD_UNIT_IMPERIAL
:
359 tfp_sprintf(buff
, "%3d%c", osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_MPH
: SYM_MPH
));
361 case OSD_UNIT_METRIC
:
362 tfp_sprintf(buff
, "%3d%c", osdConvertVelocityToUnit(vel
), (_3D
? SYM_3D_KMH
: SYM_KMH
));
368 * Converts wind speed into a string based on the current unit system, using
369 * always 3 digits and an additional character for the unit at the right. buff
370 * is null terminated.
371 * @param ws Raw wind speed in cm/s
373 #ifdef USE_WIND_ESTIMATOR
374 static void osdFormatWindSpeedStr(char *buff
, int32_t ws
, bool isValid
)
378 switch (osdConfig()->units
) {
381 case OSD_UNIT_IMPERIAL
:
382 centivalue
= (ws
* 224) / 100;
385 case OSD_UNIT_METRIC
:
386 centivalue
= (ws
* 36) / 10;
391 osdFormatCentiNumber(buff
, centivalue
, 0, 2, 0, 3);
393 buff
[0] = buff
[1] = buff
[2] = '-';
401 * Converts altitude into a string based on the current unit system
402 * prefixed by a a symbol to indicate the unit used.
403 * @param alt Raw altitude/distance (i.e. as taken from baro.BaroAlt in centimeters)
405 static void osdFormatAltitudeSymbol(char *buff
, int32_t alt
)
407 switch ((osd_unit_e
)osdConfig()->units
) {
408 case OSD_UNIT_IMPERIAL
:
409 if (osdFormatCentiNumber(buff
+ 1, CENTIMETERS_TO_CENTIFEET(alt
), 1000, 0, 2, 3)) {
411 buff
[0] = SYM_ALT_KFT
;
414 buff
[0] = SYM_ALT_FT
;
419 case OSD_UNIT_METRIC
:
420 // alt is alredy in cm
421 if (osdFormatCentiNumber(buff
+1, alt
, 1000, 0, 2, 3)) {
423 buff
[0] = SYM_ALT_KM
;
433 * Converts altitude into a string based on the current unit system.
434 * @param alt Raw altitude/distance (i.e. as taken from baro.BaroAlt in centimeters)
436 static void osdFormatAltitudeStr(char *buff
, int32_t alt
)
439 switch ((osd_unit_e
)osdConfig()->units
) {
440 case OSD_UNIT_IMPERIAL
:
441 value
= CENTIMETERS_TO_FEET(alt
);
442 tfp_sprintf(buff
, "%d%c", value
, SYM_FT
);
446 case OSD_UNIT_METRIC
:
447 value
= CENTIMETERS_TO_METERS(alt
);
448 tfp_sprintf(buff
, "%d%c", value
, SYM_M
);
453 static void osdFormatTime(char *buff
, uint32_t seconds
, char sym_m
, char sym_h
)
455 uint32_t value
= seconds
;
457 // Maximum value we can show in minutes is 99 minutes and 59 seconds
458 if (seconds
> (99 * 60) + 59) {
460 value
= seconds
/ 60;
463 tfp_sprintf(buff
+ 1, "%02d:%02d", value
/ 60, value
% 60);
466 static inline void osdFormatOnTime(char *buff
)
468 osdFormatTime(buff
, micros() / 1000000, SYM_ON_M
, SYM_ON_H
);
471 static inline void osdFormatFlyTime(char *buff
, textAttributes_t
*attr
)
473 uint32_t seconds
= getFlightTime();
474 osdFormatTime(buff
, seconds
, SYM_FLY_M
, SYM_FLY_H
);
475 if (attr
&& osdConfig()->time_alarm
> 0) {
476 if (seconds
/ 60 >= osdConfig()->time_alarm
&& ARMING_FLAG(ARMED
)) {
477 TEXT_ATTRIBUTES_ADD_BLINK(*attr
);
483 * Converts RSSI into a % value used by the OSD.
485 static uint16_t osdConvertRSSI(void)
487 // change range to [0, 99]
488 return constrain(getRSSI() * 100 / RSSI_MAX_VALUE
, 0, 99);
492 * Converts temperature into a string based on the current unit system
493 * postfixed with a symbol to indicate the unit used.
494 * @param temperature Raw temperature (i.e. as taken from getCurrentTemperature() in degC)
496 static void osdFormatTemperatureSymbol(char *buff
, float temperature
)
499 switch ((osd_unit_e
)osdConfig()->units
) {
500 case OSD_UNIT_IMPERIAL
:
501 units_symbol
= SYM_TEMP_F
;
502 temperature
= (temperature
* (9.0f
/5)) + 32;
506 case OSD_UNIT_METRIC
:
507 units_symbol
= SYM_TEMP_C
;
510 osdFormatCentiNumber(buff
, (int32_t) (temperature
* 100), 0, 0, 0, 3);
511 buff
[3] = units_symbol
;
515 static void osdFormatCoordinate(char *buff
, char sym
, int32_t val
)
517 // up to 4 for number + 1 for the symbol + null terminator + fill the rest with decimals
518 const int coordinateLength
= osdConfig()->coordinate_digits
+ 1;
521 int32_t integerPart
= val
/ GPS_DEGREES_DIVIDER
;
522 // Latitude maximum integer width is 3 (-90) while
523 // longitude maximum integer width is 4 (-180).
524 int integerDigits
= tfp_sprintf(buff
+ 1, (integerPart
== 0 && val
< 0) ? "-%d" : "%d", integerPart
);
525 // We can show up to 7 digits in decimalPart.
526 int32_t decimalPart
= abs(val
% GPS_DEGREES_DIVIDER
);
527 STATIC_ASSERT(GPS_DEGREES_DIVIDER
== 1e7
, adjust_max_decimal_digits
);
528 int decimalDigits
= tfp_sprintf(buff
+ 1 + integerDigits
, "%07d", decimalPart
);
529 // Embbed the decimal separator
530 buff
[1 + integerDigits
- 1] += SYM_ZERO_HALF_TRAILING_DOT
- '0';
531 buff
[1 + integerDigits
] += SYM_ZERO_HALF_LEADING_DOT
- '0';
532 // Fill up to coordinateLength with zeros
533 int total
= 1 + integerDigits
+ decimalDigits
;
534 while(total
< coordinateLength
) {
538 buff
[coordinateLength
] = '\0';
541 // Used twice, make sure it's exactly the same string
542 // to save some memory
543 #define RC_RX_LINK_LOST_MSG "!RC RX LINK LOST!"
545 static const char * osdArmingDisabledReasonMessage(void)
547 switch (isArmingDisabledReason()) {
548 case ARMING_DISABLED_FAILSAFE_SYSTEM
:
549 // See handling of FAILSAFE_RX_LOSS_MONITORING in failsafe.c
550 if (failsafePhase() == FAILSAFE_RX_LOSS_MONITORING
) {
551 if (failsafeIsReceivingRxData()) {
552 if (isUsingSticksForArming()) {
553 // Need to power-cycle
554 return OSD_MESSAGE_STR("POWER CYCLE TO ARM");
556 // If we're not using sticks, it means the ARM switch
557 // hasn't been off since entering FAILSAFE_RX_LOSS_MONITORING
559 return OSD_MESSAGE_STR("TURN ARM SWITCH OFF");
561 // Not receiving RX data
562 return OSD_MESSAGE_STR(RC_RX_LINK_LOST_MSG
);
564 return OSD_MESSAGE_STR("DISABLED BY FAILSAFE");
565 case ARMING_DISABLED_NOT_LEVEL
:
566 return OSD_MESSAGE_STR("AIRCRAFT IS NOT LEVEL");
567 case ARMING_DISABLED_SENSORS_CALIBRATING
:
568 return OSD_MESSAGE_STR("SENSORS CALIBRATING");
569 case ARMING_DISABLED_SYSTEM_OVERLOADED
:
570 return OSD_MESSAGE_STR("SYSTEM OVERLOADED");
571 case ARMING_DISABLED_NAVIGATION_UNSAFE
:
572 return OSD_MESSAGE_STR("NAVIGATION IS UNSAFE");
573 case ARMING_DISABLED_COMPASS_NOT_CALIBRATED
:
574 return OSD_MESSAGE_STR("COMPASS NOT CALIBRATED");
575 case ARMING_DISABLED_ACCELEROMETER_NOT_CALIBRATED
:
576 return OSD_MESSAGE_STR("ACCELEROMETER NOT CALIBRATED");
577 case ARMING_DISABLED_ARM_SWITCH
:
578 return OSD_MESSAGE_STR("DISABLE ARM SWITCH FIRST");
579 case ARMING_DISABLED_HARDWARE_FAILURE
:
581 if (!HW_SENSOR_IS_HEALTHY(getHwGyroStatus())) {
582 return OSD_MESSAGE_STR("GYRO FAILURE");
584 if (!HW_SENSOR_IS_HEALTHY(getHwAccelerometerStatus())) {
585 return OSD_MESSAGE_STR("ACCELEROMETER FAILURE");
587 if (!HW_SENSOR_IS_HEALTHY(getHwCompassStatus())) {
588 return OSD_MESSAGE_STR("COMPASS FAILURE");
590 if (!HW_SENSOR_IS_HEALTHY(getHwBarometerStatus())) {
591 return OSD_MESSAGE_STR("BAROMETER FAILURE");
593 if (!HW_SENSOR_IS_HEALTHY(getHwGPSStatus())) {
594 return OSD_MESSAGE_STR("GPS FAILURE");
596 if (!HW_SENSOR_IS_HEALTHY(getHwRangefinderStatus())) {
597 return OSD_MESSAGE_STR("RANGE FINDER FAILURE");
599 if (!HW_SENSOR_IS_HEALTHY(getHwPitotmeterStatus())) {
600 return OSD_MESSAGE_STR("PITOT METER FAILURE");
603 return OSD_MESSAGE_STR("HARDWARE FAILURE");
604 case ARMING_DISABLED_BOXFAILSAFE
:
605 return OSD_MESSAGE_STR("FAILSAFE MODE ENABLED");
606 case ARMING_DISABLED_BOXKILLSWITCH
:
607 return OSD_MESSAGE_STR("KILLSWITCH MODE ENABLED");
608 case ARMING_DISABLED_RC_LINK
:
609 return OSD_MESSAGE_STR("NO RC LINK");
610 case ARMING_DISABLED_THROTTLE
:
611 return OSD_MESSAGE_STR("THROTTLE IS NOT LOW");
612 case ARMING_DISABLED_ROLLPITCH_NOT_CENTERED
:
613 return OSD_MESSAGE_STR("ROLLPITCH NOT CENTERED");
614 case ARMING_DISABLED_SERVO_AUTOTRIM
:
615 return OSD_MESSAGE_STR("AUTOTRIM IS ACTIVE");
616 case ARMING_DISABLED_OOM
:
617 return OSD_MESSAGE_STR("NOT ENOUGH MEMORY");
618 case ARMING_DISABLED_INVALID_SETTING
:
619 return OSD_MESSAGE_STR("INVALID SETTING");
620 case ARMING_DISABLED_CLI
:
621 return OSD_MESSAGE_STR("CLI IS ACTIVE");
622 // Cases without message
623 case ARMING_DISABLED_CMS_MENU
:
625 case ARMING_DISABLED_OSD_MENU
:
627 case ARMING_DISABLED_ALL_FLAGS
:
637 static const char * osdFailsafePhaseMessage(void)
639 // See failsafe.h for each phase explanation
640 switch (failsafePhase()) {
642 case FAILSAFE_RETURN_TO_HOME
:
643 // XXX: Keep this in sync with OSD_FLYMODE.
644 return OSD_MESSAGE_STR("(RTH)");
646 case FAILSAFE_LANDING
:
647 // This should be considered an emergengy landing
648 return OSD_MESSAGE_STR("(EMERGENCY LANDING)");
649 case FAILSAFE_RX_LOSS_MONITORING
:
650 // Only reachable from FAILSAFE_LANDED, which performs
651 // a disarm. Since aircraft has been disarmed, we no
652 // longer show failsafe details.
654 case FAILSAFE_LANDED
:
655 // Very brief, disarms and transitions into
656 // FAILSAFE_RX_LOSS_MONITORING. Note that it prevents
657 // further rearming via ARMING_DISABLED_FAILSAFE_SYSTEM,
658 // so we'll show the user how to re-arm in when
659 // that flag is the reason to prevent arming.
661 case FAILSAFE_RX_LOSS_IDLE
:
662 // This only happens when user has chosen NONE as FS
663 // procedure. The recovery messages should be enough.
666 // Failsafe not active
668 case FAILSAFE_RX_LOSS_DETECTED
:
669 // Very brief, changes to FAILSAFE_RX_LOSS_RECOVERED
670 // or the FS procedure immediately.
672 case FAILSAFE_RX_LOSS_RECOVERED
:
679 static const char * osdFailsafeInfoMessage(void)
681 if (failsafeIsReceivingRxData()) {
682 // User must move sticks to exit FS mode
683 return OSD_MESSAGE_STR("!MOVE STICKS TO EXIT FS!");
685 return OSD_MESSAGE_STR(RC_RX_LINK_LOST_MSG
);
688 static const char * navigationStateMessage(void)
690 switch (NAV_Status
.state
) {
691 case MW_NAV_STATE_NONE
:
693 case MW_NAV_STATE_RTH_START
:
694 return OSD_MESSAGE_STR("STARTING RTH");
695 case MW_NAV_STATE_RTH_ENROUTE
:
696 // TODO: Break this up between climb and head home
697 return OSD_MESSAGE_STR("EN ROUTE TO HOME");
698 case MW_NAV_STATE_HOLD_INFINIT
:
699 // Used by HOLD flight modes. No information to add.
701 case MW_NAV_STATE_HOLD_TIMED
:
704 case MW_NAV_STATE_WP_ENROUTE
:
705 // TODO: Show WP number
706 return OSD_MESSAGE_STR("EN ROUTE TO WAYPOINT");
707 case MW_NAV_STATE_PROCESS_NEXT
:
708 return OSD_MESSAGE_STR("PREPARING FOR NEXT WAYPOINT");
709 case MW_NAV_STATE_DO_JUMP
:
712 case MW_NAV_STATE_LAND_START
:
715 case MW_NAV_STATE_EMERGENCY_LANDING
:
716 return OSD_MESSAGE_STR("EMERGENCY LANDING");
717 case MW_NAV_STATE_LAND_IN_PROGRESS
:
718 return OSD_MESSAGE_STR("LANDING");
719 case MW_NAV_STATE_HOVER_ABOVE_HOME
:
720 if (STATE(FIXED_WING
)) {
721 return OSD_MESSAGE_STR("LOITERING AROUND HOME");
723 return OSD_MESSAGE_STR("HOVERING");
724 case MW_NAV_STATE_LANDED
:
725 return OSD_MESSAGE_STR("LANDED");
726 case MW_NAV_STATE_LAND_SETTLE
:
727 return OSD_MESSAGE_STR("PREPARING TO LAND");
728 case MW_NAV_STATE_LAND_START_DESCENT
:
735 static void osdFormatMessage(char *buff
, size_t size
, const char *message
)
737 memset(buff
, SYM_BLANK
, size
);
739 int messageLength
= strlen(message
);
740 int rem
= MAX(0, OSD_MESSAGE_LENGTH
- (int)messageLength
);
741 // Don't finish the string at the end of the message,
742 // write the rest of the blanks.
743 strncpy(buff
+ rem
/ 2, message
, MIN(OSD_MESSAGE_LENGTH
- rem
/ 2, messageLength
));
745 // Ensure buff is zero terminated
746 buff
[size
- 1] = '\0';
750 * Draws the battery symbol filled in accordingly to the
751 * battery voltage to buff[0].
753 static void osdFormatBatteryChargeSymbol(char *buff
)
755 uint8_t p
= calculateBatteryPercentage();
756 p
= (100 - p
) / 16.6;
757 buff
[0] = SYM_BATT_FULL
+ p
;
760 static void osdUpdateBatteryCapacityOrVoltageTextAttributes(textAttributes_t
*attr
)
762 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && ((batteryUsesCapacityThresholds() && (getBatteryRemainingCapacity() <= currentBatteryProfile
->capacity
.warning
- currentBatteryProfile
->capacity
.critical
)) || ((!batteryUsesCapacityThresholds()) && (getBatteryVoltage() <= getBatteryWarningVoltage()))))
763 TEXT_ATTRIBUTES_ADD_BLINK(*attr
);
766 static void osdCrosshairsBounds(uint8_t *x
, uint8_t *y
, uint8_t *length
)
768 *x
= 14 - 1; // Offset for 1 char to the left
770 if (IS_DISPLAY_PAL
) {
774 if (osdConfig()->crosshairs_style
== OSD_CROSSHAIRS_STYLE_AIRCRAFT
) {
784 * Formats throttle position prefixed by its symbol. If autoThr
785 * is true and the navigation system is controlling THR, it
786 * uses the THR value applied by the system rather than the
787 * input value received by the sticks.
789 static void osdFormatThrottlePosition(char *buff
, bool autoThr
, textAttributes_t
*elemAttr
)
793 int16_t thr
= rcData
[THROTTLE
];
794 if (autoThr
&& navigationIsControllingThrottle()) {
795 buff
[0] = SYM_AUTO_THR0
;
796 buff
[1] = SYM_AUTO_THR1
;
797 thr
= rcCommand
[THROTTLE
];
798 if (isFixedWingAutoThrottleManuallyIncreased())
799 TEXT_ATTRIBUTES_ADD_BLINK(*elemAttr
);
801 tfp_sprintf(buff
+ 2, "%3d", (constrain(thr
, PWM_RANGE_MIN
, PWM_RANGE_MAX
) - PWM_RANGE_MIN
) * 100 / (PWM_RANGE_MAX
- PWM_RANGE_MIN
));
804 static inline int32_t osdGetAltitude(void)
807 return getEstimatedActualPosition(Z
);
808 #elif defined(USE_BARO)
815 static inline int32_t osdGetAltitudeMsl(void)
818 return getEstimatedActualPosition(Z
)+GPS_home
.alt
;
819 #elif defined(USE_BARO)
820 return baro
.alt
+GPS_home
.alt
;
826 static uint8_t osdUpdateSidebar(osd_sidebar_scroll_e scroll
, osd_sidebar_t
*sidebar
, timeMs_t currentTimeMs
)
828 // Scroll between SYM_AH_DECORATION_MIN and SYM_AH_DECORATION_MAX.
829 // Zero scrolling should draw SYM_AH_DECORATION.
830 uint8_t decoration
= SYM_AH_DECORATION
;
834 case OSD_SIDEBAR_SCROLL_NONE
:
835 sidebar
->arrow
= OSD_SIDEBAR_ARROW_NONE
;
838 case OSD_SIDEBAR_SCROLL_ALTITUDE
:
839 // Move 1 char for every 20cm
840 offset
= osdGetAltitude();
843 case OSD_SIDEBAR_SCROLL_GROUND_SPEED
:
845 offset
= gpsSol
.groundSpeed
;
849 // Move 1 char for every 20 cm/s
852 case OSD_SIDEBAR_SCROLL_HOME_DISTANCE
:
854 offset
= GPS_distanceToHome
;
858 // Move 1 char for every 5m
863 decoration
-= steps
% SYM_AH_DECORATION_COUNT
;
864 if (decoration
> SYM_AH_DECORATION_MAX
) {
865 decoration
-= SYM_AH_DECORATION_COUNT
;
866 } else if (decoration
< SYM_AH_DECORATION_MIN
) {
867 decoration
+= SYM_AH_DECORATION_COUNT
;
870 if (currentTimeMs
- sidebar
->updated
> 100) {
871 if (offset
> sidebar
->offset
) {
872 sidebar
->arrow
= OSD_SIDEBAR_ARROW_UP
;
874 } else if (offset
< sidebar
->offset
) {
875 sidebar
->arrow
= OSD_SIDEBAR_ARROW_DOWN
;
878 if (sidebar
->idle
> 3) {
879 sidebar
->arrow
= OSD_SIDEBAR_ARROW_NONE
;
884 sidebar
->offset
= offset
;
885 sidebar
->updated
= currentTimeMs
;
890 static bool osdIsHeadingValid(void)
892 return isImuHeadingValid();
895 static int16_t osdGetHeading(void)
897 return attitude
.values
.yaw
;
900 // Returns a heading angle in degrees normalized to [0, 360).
901 static int osdGetHeadingAngle(int angle
)
906 while (angle
>= 360) {
914 /* Draws a map with the given symbol in the center and given point of interest
915 * defined by its distance in meters and direction in degrees.
916 * referenceHeading indicates the up direction in the map, in degrees, while
917 * referenceSym (if non-zero) is drawn at the upper right corner below a small
918 * arrow to indicate the map reference to the user. The drawn argument is an
919 * in-out used to store the last position where the craft was drawn to avoid
920 * erasing all screen on each redraw.
922 static void osdDrawMap(int referenceHeading
, uint8_t referenceSym
, uint8_t centerSym
,
923 uint32_t poiDistance
, int16_t poiDirection
, uint8_t poiSymbol
,
924 uint16_t *drawn
, uint32_t *usedScale
)
926 // TODO: These need to be tested with several setups. We might
927 // need to make them configurable.
928 const int hMargin
= 1;
929 const int vMargin
= 1;
931 // TODO: Get this from the display driver?
932 const int charWidth
= 12;
933 const int charHeight
= 18;
937 uint8_t minX
= hMargin
;
938 uint8_t maxX
= osdDisplayPort
->cols
- 1 - hMargin
;
939 uint8_t minY
= vMargin
;
940 uint8_t maxY
= osdDisplayPort
->rows
- 1 - vMargin
;
941 uint8_t midX
= osdDisplayPort
->cols
/ 2;
942 uint8_t midY
= osdDisplayPort
->rows
/ 2;
946 displayWriteChar(osdDisplayPort
, maxX
, minY
, SYM_DIRECTION
);
947 displayWriteChar(osdDisplayPort
, maxX
, minY
+ 1, referenceSym
);
949 displayWriteChar(osdDisplayPort
, minX
, maxY
, SYM_SCALE
);
950 displayWriteChar(osdDisplayPort
, midX
, midY
, centerSym
);
952 // First, erase the previous drawing.
953 if (OSD_VISIBLE(*drawn
)) {
954 displayWriteChar(osdDisplayPort
, OSD_X(*drawn
), OSD_Y(*drawn
), SYM_BLANK
);
958 uint32_t initialScale
;
960 int scaleUnitDivisor
;
964 const unsigned scaleMultiplier
= 2;
965 // We try to reduce the scale when the POI will be around half the distance
966 // between the center and the closers map edge, to avoid too much jumping
967 const int scaleReductionMultiplier
= MIN(midX
- hMargin
, midY
- vMargin
) / 2;
969 switch (osdConfig()->units
) {
970 case OSD_UNIT_IMPERIAL
:
971 initialScale
= 16; // 16m ~= 0.01miles
972 scaleToUnit
= 100 / 1609.3440f
; // scale to 0.01mi for osdFormatCentiNumber()
973 scaleUnitDivisor
= 0;
974 symUnscaled
= SYM_MI
;
980 case OSD_UNIT_METRIC
:
981 initialScale
= 10; // 10m as initial scale
982 scaleToUnit
= 100; // scale to cm for osdFormatCentiNumber()
983 scaleUnitDivisor
= 1000; // Convert to km when scale gets bigger than 999m
990 // Try to keep the same scale when getting closer until we draw over the center point
991 uint32_t scale
= initialScale
;
994 if (scale
> initialScale
&& poiDistance
< *usedScale
* scaleReductionMultiplier
) {
995 scale
/= scaleMultiplier
;
999 if (STATE(GPS_FIX
)) {
1001 int directionToPoi
= osdGetHeadingAngle(poiDirection
- referenceHeading
);
1002 float poiAngle
= DEGREES_TO_RADIANS(directionToPoi
);
1003 float poiSin
= sin_approx(poiAngle
);
1004 float poiCos
= cos_approx(poiAngle
);
1006 // Now start looking for a valid scale that lets us draw everything
1008 for (ii
= 0; ii
< 50; ii
++) {
1009 // Calculate location of the aircraft in map
1010 int points
= poiDistance
/ ((float)scale
/ charHeight
);
1012 float pointsX
= points
* poiSin
;
1013 int poiX
= midX
- roundf(pointsX
/ charWidth
);
1014 if (poiX
< minX
|| poiX
> maxX
) {
1015 scale
*= scaleMultiplier
;
1019 float pointsY
= points
* poiCos
;
1020 int poiY
= midY
+ roundf(pointsY
/ charHeight
);
1021 if (poiY
< minY
|| poiY
> maxY
) {
1022 scale
*= scaleMultiplier
;
1026 if (poiX
== midX
&& poiY
== midY
) {
1027 // We're over the map center symbol, so we would be drawing
1028 // over it even if we increased the scale. Alternate between
1029 // drawing the center symbol or drawing the POI.
1030 if (centerSym
!= SYM_BLANK
&& OSD_ALTERNATING_CHOICES(1000, 2) == 0) {
1036 if (displayReadCharWithAttr(osdDisplayPort
, poiX
, poiY
, &c
, NULL
) && c
!= SYM_BLANK
) {
1037 // Something else written here, increase scale. If the display doesn't support reading
1038 // back characters, we assume there's nothing.
1040 // If we're close to the center, decrease scale. Otherwise increase it.
1041 uint8_t centerDeltaX
= (maxX
- minX
) / (scaleMultiplier
* 2);
1042 uint8_t centerDeltaY
= (maxY
- minY
) / (scaleMultiplier
* 2);
1043 if (poiX
>= midX
- centerDeltaX
&& poiX
<= midX
+ centerDeltaX
&&
1044 poiY
>= midY
- centerDeltaY
&& poiY
<= midY
+ centerDeltaY
&&
1045 scale
> scaleMultiplier
) {
1047 scale
/= scaleMultiplier
;
1049 scale
*= scaleMultiplier
;
1055 // Draw the point on the map
1056 if (poiSymbol
== SYM_ARROW_UP
) {
1057 // Drawing aircraft, rotate
1058 int mapHeading
= osdGetHeadingAngle(DECIDEGREES_TO_DEGREES(osdGetHeading()) - referenceHeading
);
1059 poiSymbol
+= mapHeading
* 2 / 45;
1061 displayWriteChar(osdDisplayPort
, poiX
, poiY
, poiSymbol
);
1063 // Update saved location
1064 *drawn
= OSD_POS(poiX
, poiY
) | OSD_VISIBLE_FLAG
;
1069 // Draw the used scale
1070 bool scaled
= osdFormatCentiNumber(buf
, scale
* scaleToUnit
, scaleUnitDivisor
, maxDecimals
, 2, 3);
1071 buf
[3] = scaled
? symScaled
: symUnscaled
;
1073 displayWrite(osdDisplayPort
, minX
+ 1, maxY
, buf
);
1077 /* Draws a map with the home in the center and the craft moving around.
1078 * See osdDrawMap() for reference.
1080 static void osdDrawHomeMap(int referenceHeading
, uint8_t referenceSym
, uint16_t *drawn
, uint32_t *usedScale
)
1082 osdDrawMap(referenceHeading
, referenceSym
, SYM_HOME
, GPS_distanceToHome
, GPS_directionToHome
, SYM_ARROW_UP
, drawn
, usedScale
);
1085 /* Draws a map with the aircraft in the center and the home moving around.
1086 * See osdDrawMap() for reference.
1088 static void osdDrawRadar(uint16_t *drawn
, uint32_t *usedScale
)
1090 int16_t reference
= DECIDEGREES_TO_DEGREES(osdGetHeading());
1091 int16_t poiDirection
= osdGetHeadingAngle(GPS_directionToHome
+ 180);
1092 osdDrawMap(reference
, 0, SYM_ARROW_UP
, GPS_distanceToHome
, poiDirection
, SYM_HOME
, drawn
, usedScale
);
1095 static int16_t osdGet3DSpeed(void)
1097 int16_t vert_speed
= getEstimatedActualVelocity(Z
);
1098 int16_t hor_speed
= gpsSol
.groundSpeed
;
1099 return (int16_t)sqrtf(sq(hor_speed
) + sq(vert_speed
));
1104 static void osdFormatPidControllerOutput(char *buff
, const char *label
, const pidController_t
*pidController
, uint8_t scale
, bool showDecimal
) {
1105 strcpy(buff
, label
);
1106 for (uint8_t i
= strlen(label
); i
< 5; ++i
) buff
[i
] = ' ';
1107 uint8_t decimals
= showDecimal
? 1 : 0;
1108 osdFormatCentiNumber(buff
+ 5, pidController
->proportional
* scale
, 0, decimals
, 0, 4);
1110 osdFormatCentiNumber(buff
+ 10, pidController
->integrator
* scale
, 0, decimals
, 0, 4);
1112 osdFormatCentiNumber(buff
+ 15, pidController
->derivative
* scale
, 0, decimals
, 0, 4);
1114 osdFormatCentiNumber(buff
+ 20, pidController
->output_constrained
* scale
, 0, decimals
, 0, 4);
1118 static void osdDisplayBatteryVoltage(uint8_t elemPosX
, uint8_t elemPosY
, uint16_t voltage
, uint8_t digits
, uint8_t decimals
)
1121 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
1123 osdFormatBatteryChargeSymbol(buff
);
1125 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1126 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
1128 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1129 osdFormatCentiNumber(buff
, voltage
, 0, decimals
, 0, MIN(digits
, 4));
1131 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && (getBatteryVoltage() <= getBatteryWarningVoltage()))
1132 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1133 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 1, elemPosY
, buff
, elemAttr
);
1136 static void osdDisplayPIDValues(uint8_t elemPosX
, uint8_t elemPosY
, const char *str
, const pid8_t
*pid
, adjustmentFunction_e adjFuncP
, adjustmentFunction_e adjFuncI
, adjustmentFunction_e adjFuncD
)
1138 textAttributes_t elemAttr
;
1141 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1143 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1144 tfp_sprintf(buff
, "%3d", pid
->P
);
1145 if ((isAdjustmentFunctionSelected(adjFuncP
)) || (((adjFuncP
== ADJUSTMENT_ROLL_P
) || (adjFuncP
== ADJUSTMENT_PITCH_P
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_P
))))
1146 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1147 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1149 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1150 tfp_sprintf(buff
, "%3d", pid
->I
);
1151 if ((isAdjustmentFunctionSelected(adjFuncI
)) || (((adjFuncI
== ADJUSTMENT_ROLL_I
) || (adjFuncI
== ADJUSTMENT_PITCH_I
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_I
))))
1152 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1153 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 8, elemPosY
, buff
, elemAttr
);
1155 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1156 tfp_sprintf(buff
, "%3d", pid
->D
);
1157 if ((isAdjustmentFunctionSelected(adjFuncD
)) || (((adjFuncD
== ADJUSTMENT_ROLL_D
) || (adjFuncD
== ADJUSTMENT_PITCH_D
)) && (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_D
))))
1158 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1159 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 12, elemPosY
, buff
, elemAttr
);
1162 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
) {
1164 textAttributes_t elemAttr
;
1165 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, str
);
1167 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1168 osdFormatCentiNumber(buff
, value
* 100, 0, maxDecimals
, 0, MIN(valueLength
, 8));
1169 if (isAdjustmentFunctionSelected(adjFunc
))
1170 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1171 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ strlen(str
) + 1 + valueOffset
, elemPosY
, buff
, elemAttr
);
1174 static bool osdDrawSingleElement(uint8_t item
)
1176 uint16_t pos
= osdConfig()->item_pos
[currentLayout
][item
];
1177 if (!OSD_VISIBLE(pos
)) {
1181 uint8_t elemPosX
= OSD_X(pos
);
1182 uint8_t elemPosY
= OSD_Y(pos
);
1183 textAttributes_t elemAttr
= TEXT_ATTRIBUTES_NONE
;
1187 case OSD_RSSI_VALUE
:
1189 uint16_t osdRssi
= osdConvertRSSI();
1191 tfp_sprintf(buff
+ 1, "%2d", osdRssi
);
1192 if (osdRssi
< osdConfig()->rssi_alarm
) {
1193 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1198 case OSD_MAIN_BATT_VOLTAGE
:
1199 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatteryRawVoltage(), 2 + osdConfig()->main_voltage_decimals
, osdConfig()->main_voltage_decimals
);
1202 case OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
:
1203 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatterySagCompensatedVoltage(), 2 + osdConfig()->main_voltage_decimals
, osdConfig()->main_voltage_decimals
);
1206 case OSD_CURRENT_DRAW
:
1208 osdFormatCentiNumber(buff
+ 1, getAmperage(), 0, 2, 0, 3);
1213 tfp_sprintf(buff
+ 1, "%-4d", getMAhDrawn());
1214 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1219 osdFormatCentiNumber(buff
+ 1, getMWhDrawn() / 10, 0, 2, 0, 3);
1220 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1223 case OSD_BATTERY_REMAINING_CAPACITY
:
1224 buff
[0] = (currentBatteryProfile
->capacity
.unit
== BAT_CAPACITY_UNIT_MAH
? SYM_MAH
: SYM_WH
);
1226 if (currentBatteryProfile
->capacity
.value
== 0)
1227 tfp_sprintf(buff
+ 1, "NA");
1228 else if (!batteryWasFullWhenPluggedIn())
1229 tfp_sprintf(buff
+ 1, "NF");
1230 else if (currentBatteryProfile
->capacity
.unit
== BAT_CAPACITY_UNIT_MAH
)
1231 tfp_sprintf(buff
+ 1, "%-4lu", getBatteryRemainingCapacity());
1232 else // currentBatteryProfile->capacity.unit == BAT_CAPACITY_UNIT_MWH
1233 osdFormatCentiNumber(buff
+ 1, getBatteryRemainingCapacity() / 10, 0, 2, 0, 3);
1235 if ((getBatteryState() != BATTERY_NOT_PRESENT
) && batteryUsesCapacityThresholds() && (getBatteryRemainingCapacity() <= currentBatteryProfile
->capacity
.warning
- currentBatteryProfile
->capacity
.critical
))
1236 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1240 case OSD_BATTERY_REMAINING_PERCENT
:
1241 tfp_sprintf(buff
, "%3d%%", calculateBatteryPercentage());
1242 osdUpdateBatteryCapacityOrVoltageTextAttributes(&elemAttr
);
1245 case OSD_POWER_SUPPLY_IMPEDANCE
:
1246 if (isPowerSupplyImpedanceValid())
1247 tfp_sprintf(buff
, "%3d", getPowerSupplyImpedance());
1249 strcpy(buff
, "---");
1250 buff
[3] = SYM_MILLIOHM
;
1256 buff
[0] = SYM_SAT_L
;
1257 buff
[1] = SYM_SAT_R
;
1258 tfp_sprintf(buff
+ 2, "%2d", gpsSol
.numSat
);
1259 if (!STATE(GPS_FIX
)) {
1260 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1265 osdFormatVelocityStr(buff
, gpsSol
.groundSpeed
, false);
1270 osdFormatVelocityStr(buff
, osdGet3DSpeed(), true);
1275 osdFormatCoordinate(buff
, SYM_LAT
, gpsSol
.llh
.lat
);
1279 osdFormatCoordinate(buff
, SYM_LON
, gpsSol
.llh
.lon
);
1284 if (STATE(GPS_FIX
) && STATE(GPS_FIX_HOME
) && isImuHeadingValid()) {
1285 // There are 16 orientations for the home direction arrow.
1286 // so we use 22.5deg per image, where the 1st image is used
1287 // for [349, 11], the 2nd for [12, 33], etc...
1288 int homeDirection
= GPS_directionToHome
- DECIDEGREES_TO_DEGREES(osdGetHeading());
1289 // Add 11 to the angle, so first character maps to [349, 11]
1290 int homeArrowDir
= osdGetHeadingAngle(homeDirection
+ 11);
1291 unsigned arrowOffset
= homeArrowDir
* 2 / 45;
1292 buff
[0] = SYM_ARROW_UP
+ arrowOffset
;
1294 // No home or no fix or unknown heading, blink.
1295 // If we're unarmed, show the arrow pointing up so users can see the arrow
1296 // while configuring the OSD. If we're armed, show a '-' indicating that
1297 // we don't know the direction to home.
1298 buff
[0] = ARMING_FLAG(ARMED
) ? '-' : SYM_ARROW_UP
;
1299 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1305 case OSD_HOME_HEADING_ERROR
:
1308 buff
[1] = SYM_HEADING
;
1310 if (isImuHeadingValid() && navigationPositionEstimateIsHealthy()) {
1311 int16_t h
= lrintf(CENTIDEGREES_TO_DEGREES((float)wrap_18000(DEGREES_TO_CENTIDEGREES((int32_t)GPS_directionToHome
) - DECIDEGREES_TO_CENTIDEGREES((int32_t)osdGetHeading()))));
1312 tfp_sprintf(buff
+ 2, "%4d", h
);
1314 strcpy(buff
+ 2, "----");
1317 buff
[6] = SYM_DEGREES
;
1325 osdFormatDistanceSymbol(&buff
[1], GPS_distanceToHome
* 100);
1326 uint16_t dist_alarm
= osdConfig()->dist_alarm
;
1327 if (dist_alarm
> 0 && GPS_distanceToHome
> dist_alarm
) {
1328 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1334 buff
[0] = SYM_TRIP_DIST
;
1335 osdFormatDistanceSymbol(buff
+ 1, getTotalTravelDistance());
1340 buff
[0] = SYM_HEADING
;
1341 if (osdIsHeadingValid()) {
1342 int16_t h
= DECIDEGREES_TO_DEGREES(osdGetHeading());
1346 tfp_sprintf(&buff
[1], "%3d", h
);
1348 buff
[1] = buff
[2] = buff
[3] = '-';
1350 buff
[4] = SYM_DEGREES
;
1355 case OSD_CRUISE_HEADING_ERROR
:
1357 if (ARMING_FLAG(ARMED
) && !FLIGHT_MODE(NAV_CRUISE_MODE
)) {
1358 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, " ");
1362 buff
[0] = SYM_HEADING
;
1364 if ((!ARMING_FLAG(ARMED
)) || (FLIGHT_MODE(NAV_CRUISE_MODE
) && isAdjustingPosition())) {
1365 buff
[1] = buff
[2] = buff
[3] = '-';
1366 } else if (FLIGHT_MODE(NAV_CRUISE_MODE
)) {
1367 int16_t herr
= lrintf(CENTIDEGREES_TO_DEGREES((float)navigationGetHeadingError()));
1369 strcpy(buff
+ 1, ">99");
1371 tfp_sprintf(buff
+ 1, "%3d", herr
);
1374 buff
[4] = SYM_DEGREES
;
1379 case OSD_CRUISE_HEADING_ADJUSTMENT
:
1381 int16_t heading_adjust
= lrintf(CENTIDEGREES_TO_DEGREES((float)getCruiseHeadingAdjustment()));
1383 if (ARMING_FLAG(ARMED
) && ((!FLIGHT_MODE(NAV_CRUISE_MODE
)) || !(isAdjustingPosition() || isAdjustingHeading() || (heading_adjust
!= 0)))) {
1384 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, " ");
1388 buff
[0] = SYM_HEADING
;
1390 if (!ARMING_FLAG(ARMED
)) {
1391 buff
[1] = buff
[2] = buff
[3] = buff
[4] = '-';
1392 } else if (FLIGHT_MODE(NAV_CRUISE_MODE
)) {
1393 tfp_sprintf(buff
+ 1, "%4d", heading_adjust
);
1396 buff
[5] = SYM_DEGREES
;
1403 buff
[0] = SYM_HDP_L
;
1404 buff
[1] = SYM_HDP_R
;
1405 int32_t centiHDOP
= 100 * gpsSol
.hdop
/ HDOP_SCALE
;
1406 osdFormatCentiNumber(&buff
[2], centiHDOP
, 0, 1, 0, 2);
1412 static uint16_t drawn
= 0;
1413 static uint32_t scale
= 0;
1414 osdDrawHomeMap(0, 'N', &drawn
, &scale
);
1417 case OSD_MAP_TAKEOFF
:
1419 static uint16_t drawn
= 0;
1420 static uint32_t scale
= 0;
1421 osdDrawHomeMap(CENTIDEGREES_TO_DEGREES(navigationGetHomeHeading()), 'T', &drawn
, &scale
);
1426 static uint16_t drawn
= 0;
1427 static uint32_t scale
= 0;
1428 osdDrawRadar(&drawn
, &scale
);
1435 int32_t alt
= osdGetAltitude();
1436 osdFormatAltitudeSymbol(buff
, alt
);
1437 uint16_t alt_alarm
= osdConfig()->alt_alarm
;
1438 uint16_t neg_alt_alarm
= osdConfig()->neg_alt_alarm
;
1439 if ((alt_alarm
> 0 && CENTIMETERS_TO_METERS(alt
) > alt_alarm
) ||
1440 (neg_alt_alarm
> 0 && alt
< 0 && -CENTIMETERS_TO_METERS(alt
) > neg_alt_alarm
)) {
1442 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1447 case OSD_ALTITUDE_MSL
:
1449 int32_t alt
= osdGetAltitudeMsl();
1450 osdFormatAltitudeSymbol(buff
, alt
);
1456 osdFormatOnTime(buff
);
1462 osdFormatFlyTime(buff
, &elemAttr
);
1466 case OSD_ONTIME_FLYTIME
:
1468 if (ARMING_FLAG(ARMED
)) {
1469 osdFormatFlyTime(buff
, &elemAttr
);
1471 osdFormatOnTime(buff
);
1476 case OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
:
1478 static timeUs_t updatedTimestamp
= 0;
1479 /*static int32_t updatedTimeSeconds = 0;*/
1480 timeUs_t currentTimeUs
= micros();
1481 static int32_t timeSeconds
= -1;
1482 if (cmpTimeUs(currentTimeUs
, updatedTimestamp
) >= 1000000) {
1483 timeSeconds
= calculateRemainingFlightTimeBeforeRTH(osdConfig()->estimations_wind_compensation
);
1484 updatedTimestamp
= currentTimeUs
;
1486 if ((!ARMING_FLAG(ARMED
)) || (timeSeconds
== -1)) {
1487 buff
[0] = SYM_FLY_M
;
1488 strcpy(buff
+ 1, "--:--");
1489 updatedTimestamp
= 0;
1490 } else if (timeSeconds
== -2) {
1491 // Wind is too strong to come back with cruise throttle
1492 buff
[0] = SYM_FLY_M
;
1493 buff
[1] = buff
[2] = buff
[4] = buff
[5] = SYM_WIND_HORIZONTAL
;
1496 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1498 osdFormatTime(buff
, timeSeconds
, SYM_FLY_M
, SYM_FLY_H
);
1499 if (timeSeconds
== 0)
1500 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1505 case OSD_REMAINING_DISTANCE_BEFORE_RTH
:;
1506 static timeUs_t updatedTimestamp
= 0;
1507 timeUs_t currentTimeUs
= micros();
1508 static int32_t distanceMeters
= -1;
1509 if (cmpTimeUs(currentTimeUs
, updatedTimestamp
) >= 1000000) {
1510 distanceMeters
= calculateRemainingDistanceBeforeRTH(osdConfig()->estimations_wind_compensation
);
1511 updatedTimestamp
= currentTimeUs
;
1513 buff
[0] = SYM_TRIP_DIST
;
1514 if ((!ARMING_FLAG(ARMED
)) || (distanceMeters
== -1)) {
1515 buff
[1] = SYM_DIST_M
;
1516 strcpy(buff
+ 2, "---");
1517 } else if (distanceMeters
== -2) {
1518 // Wind is too strong to come back with cruise throttle
1519 buff
[1] = SYM_DIST_M
;
1520 buff
[2] = buff
[3] = buff
[4] = SYM_WIND_HORIZONTAL
;
1522 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1524 osdFormatDistanceSymbol(buff
+ 1, distanceMeters
* 100);
1525 if (distanceMeters
== 0)
1526 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1534 if (FLIGHT_MODE(FAILSAFE_MODE
))
1536 else if (FLIGHT_MODE(MANUAL_MODE
))
1538 else if (FLIGHT_MODE(NAV_RTH_MODE
))
1540 else if (FLIGHT_MODE(NAV_POSHOLD_MODE
))
1542 else if (FLIGHT_MODE(NAV_CRUISE_MODE
) && FLIGHT_MODE(NAV_ALTHOLD_MODE
))
1544 else if (FLIGHT_MODE(NAV_CRUISE_MODE
))
1546 else if (FLIGHT_MODE(NAV_ALTHOLD_MODE
) && navigationRequiresAngleMode()) {
1547 // If navigationRequiresAngleMode() returns false when ALTHOLD is active,
1548 // it means it can be combined with ANGLE, HORIZON, ACRO, etc...
1549 // and its display is handled by OSD_MESSAGES rather than OSD_FLYMODE.
1551 } else if (FLIGHT_MODE(NAV_WP_MODE
))
1553 else if (FLIGHT_MODE(ANGLE_MODE
))
1555 else if (FLIGHT_MODE(HORIZON_MODE
))
1557 else if (isAirmodeActive())
1560 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, p
);
1564 case OSD_CRAFT_NAME
:
1565 if (strlen(systemConfig()->name
) == 0)
1566 strcpy(buff
, "CRAFT_NAME");
1568 for (int i
= 0; i
< MAX_NAME_LENGTH
; i
++) {
1569 buff
[i
] = sl_toupper((unsigned char)systemConfig()->name
[i
]);
1570 if (systemConfig()->name
[i
] == 0)
1576 case OSD_THROTTLE_POS
:
1578 osdFormatThrottlePosition(buff
, false, NULL
);
1582 #if defined(VTX) || defined(USE_VTX_COMMON)
1583 case OSD_VTX_CHANNEL
:
1585 // FIXME: This doesn't actually work. It's for boards with
1587 tfp_sprintf(buff
, "CH:%2d", current_vtx_channel
% CHANNELS_PER_BAND
+ 1);
1591 uint8_t channel
= 0;
1592 uint8_t powerIndex
= 0;
1594 const char *channelStr
= "-";
1595 char powerChr
= '-';
1596 vtxDevice_t
*vtxDevice
= vtxCommonDevice();
1598 if (vtxCommonGetBandAndChannel(vtxDevice
, &band
, &channel
)) {
1599 bandChr
= vtx58BandLetter
[band
];
1600 channelStr
= vtx58ChannelNames
[channel
];
1602 if (vtxCommonGetPowerIndex(vtxDevice
, &powerIndex
)) {
1603 powerChr
= '0' + powerIndex
;
1606 tfp_sprintf(buff
, "CH:%c%s:%c", bandChr
, channelStr
, powerChr
);
1610 #endif // VTX || VTX_COMMON
1612 case OSD_CROSSHAIRS
:
1613 osdCrosshairsBounds(&elemPosX
, &elemPosY
, NULL
);
1614 switch ((osd_crosshairs_style_e
)osdConfig()->crosshairs_style
) {
1615 case OSD_CROSSHAIRS_STYLE_DEFAULT
:
1616 buff
[0] = SYM_AH_CENTER_LINE
;
1617 buff
[1] = SYM_AH_CENTER
;
1618 buff
[2] = SYM_AH_CENTER_LINE_RIGHT
;
1621 case OSD_CROSSHAIRS_STYLE_AIRCRAFT
:
1622 buff
[0] = SYM_AH_CROSSHAIRS_AIRCRAFT0
;
1623 buff
[1] = SYM_AH_CROSSHAIRS_AIRCRAFT1
;
1624 buff
[2] = SYM_AH_CROSSHAIRS_AIRCRAFT2
;
1625 buff
[3] = SYM_AH_CROSSHAIRS_AIRCRAFT3
;
1626 buff
[4] = SYM_AH_CROSSHAIRS_AIRCRAFT4
;
1632 case OSD_ATTITUDE_ROLL
:
1633 buff
[0] = SYM_ROLL_LEVEL
;
1634 if (ABS(attitude
.values
.roll
) >= 1)
1635 buff
[0] += (attitude
.values
.roll
< 0 ? -1 : 1);
1636 osdFormatCentiNumber(buff
+ 1, DECIDEGREES_TO_CENTIDEGREES(ABS(attitude
.values
.roll
)), 0, 1, 0, 3);
1639 case OSD_ATTITUDE_PITCH
:
1640 if (ABS(attitude
.values
.pitch
) < 1)
1642 else if (attitude
.values
.pitch
> 0)
1643 buff
[0] = SYM_PITCH_DOWN
;
1644 else if (attitude
.values
.pitch
< 0)
1645 buff
[0] = SYM_PITCH_UP
;
1646 osdFormatCentiNumber(buff
+ 1, DECIDEGREES_TO_CENTIDEGREES(ABS(attitude
.values
.pitch
)), 0, 1, 0, 3);
1649 case OSD_ARTIFICIAL_HORIZON
:
1653 elemPosY
= 6; // Center of the AH area
1655 // Store the positions we draw over to erase only these at the next iteration
1656 static int8_t previous_written
[AH_PREV_SIZE
];
1657 static int8_t previous_orient
= -1;
1659 float pitch_rad_to_char
= (float)(AH_HEIGHT
/ 2 + 0.5) / DEGREES_TO_RADIANS(osdConfig()->ahi_max_pitch
);
1661 float rollAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.roll
);
1662 float pitchAngle
= DECIDEGREES_TO_RADIANS(attitude
.values
.pitch
);
1664 if (osdConfig()->ahi_reverse_roll
) {
1665 rollAngle
= -rollAngle
;
1668 if (IS_DISPLAY_PAL
) {
1672 float ky
= sin_approx(rollAngle
);
1673 float kx
= cos_approx(rollAngle
);
1675 if (previous_orient
!= -1) {
1676 for (int i
= 0; i
< AH_PREV_SIZE
; ++i
) {
1677 if (previous_written
[i
] > -1) {
1678 int8_t dx
= (previous_orient
? previous_written
[i
] : i
) - AH_PREV_SIZE
/ 2;
1679 int8_t dy
= (previous_orient
? i
: previous_written
[i
]) - AH_PREV_SIZE
/ 2;
1680 displayWriteChar(osdDisplayPort
, elemPosX
+ dx
, elemPosY
- dy
, SYM_BLANK
);
1681 previous_written
[i
] = -1;
1686 if (ABS(ky
) < ABS(kx
)) {
1688 previous_orient
= 0;
1690 for (int8_t dx
= -AH_WIDTH
/ 2; dx
<= AH_WIDTH
/ 2; dx
++) {
1691 float fy
= dx
* (ky
/ kx
) + pitchAngle
* pitch_rad_to_char
+ 0.49f
;
1692 int8_t dy
= floorf(fy
);
1693 const uint8_t chX
= elemPosX
+ dx
, chY
= elemPosY
- dy
;
1696 if ((dy
>= -AH_HEIGHT
/ 2) && (dy
<= AH_HEIGHT
/ 2) && displayReadCharWithAttr(osdDisplayPort
, chX
, chY
, &c
, NULL
) && (c
== SYM_BLANK
)) {
1697 c
= SYM_AH_H_START
+ ((AH_H_SYM_COUNT
- 1) - (uint8_t)((fy
- dy
) * AH_H_SYM_COUNT
));
1698 displayWriteChar(osdDisplayPort
, elemPosX
+ dx
, elemPosY
- dy
, c
);
1699 previous_written
[dx
+ AH_PREV_SIZE
/ 2] = dy
+ AH_PREV_SIZE
/ 2;
1705 previous_orient
= 1;
1707 for (int8_t dy
= -AH_HEIGHT
/ 2; dy
<= AH_HEIGHT
/ 2; dy
++) {
1708 const float fx
= (dy
- pitchAngle
* pitch_rad_to_char
) * (kx
/ ky
) + 0.5f
;
1709 const int8_t dx
= floorf(fx
);
1710 const uint8_t chX
= elemPosX
+ dx
, chY
= elemPosY
- dy
;
1713 if ((dx
>= -AH_WIDTH
/ 2) && (dx
<= AH_WIDTH
/ 2) && displayReadCharWithAttr(osdDisplayPort
, chX
, chY
, &c
, NULL
) && (c
== SYM_BLANK
)) {
1714 c
= SYM_AH_V_START
+ (fx
- dx
) * AH_V_SYM_COUNT
;
1715 displayWriteChar(osdDisplayPort
, chX
, chY
, c
);
1716 previous_written
[dy
+ AH_PREV_SIZE
/ 2] = dx
+ AH_PREV_SIZE
/ 2;
1722 osdDrawSingleElement(OSD_HORIZON_SIDEBARS
);
1723 osdDrawSingleElement(OSD_CROSSHAIRS
);
1728 case OSD_HORIZON_SIDEBARS
:
1733 if (IS_DISPLAY_PAL
) {
1737 static osd_sidebar_t left
;
1738 static osd_sidebar_t right
;
1740 timeMs_t currentTimeMs
= millis();
1741 uint8_t leftDecoration
= osdUpdateSidebar(osdConfig()->left_sidebar_scroll
, &left
, currentTimeMs
);
1742 uint8_t rightDecoration
= osdUpdateSidebar(osdConfig()->right_sidebar_scroll
, &right
, currentTimeMs
);
1744 const int8_t hudwidth
= AH_SIDEBAR_WIDTH_POS
;
1745 const int8_t hudheight
= AH_SIDEBAR_HEIGHT_POS
;
1748 if (osdConfig()->sidebar_scroll_arrows
) {
1749 displayWriteChar(osdDisplayPort
, elemPosX
- hudwidth
, elemPosY
- hudheight
- 1,
1750 left
.arrow
== OSD_SIDEBAR_ARROW_UP
? SYM_AH_DECORATION_UP
: SYM_BLANK
);
1752 displayWriteChar(osdDisplayPort
, elemPosX
+ hudwidth
, elemPosY
- hudheight
- 1,
1753 right
.arrow
== OSD_SIDEBAR_ARROW_UP
? SYM_AH_DECORATION_UP
: SYM_BLANK
);
1755 displayWriteChar(osdDisplayPort
, elemPosX
- hudwidth
, elemPosY
+ hudheight
+ 1,
1756 left
.arrow
== OSD_SIDEBAR_ARROW_DOWN
? SYM_AH_DECORATION_DOWN
: SYM_BLANK
);
1758 displayWriteChar(osdDisplayPort
, elemPosX
+ hudwidth
, elemPosY
+ hudheight
+ 1,
1759 right
.arrow
== OSD_SIDEBAR_ARROW_DOWN
? SYM_AH_DECORATION_DOWN
: SYM_BLANK
);
1763 for (int y
= -hudheight
; y
<= hudheight
; y
++) {
1764 displayWriteChar(osdDisplayPort
, elemPosX
- hudwidth
, elemPosY
+ y
, leftDecoration
);
1765 displayWriteChar(osdDisplayPort
, elemPosX
+ hudwidth
, elemPosY
+ y
, rightDecoration
);
1768 // AH level indicators
1769 displayWriteChar(osdDisplayPort
, elemPosX
- hudwidth
+ 1, elemPosY
, SYM_AH_LEFT
);
1770 displayWriteChar(osdDisplayPort
, elemPosX
+ hudwidth
- 1, elemPosY
, SYM_AH_RIGHT
);
1775 #if defined(USE_BARO) || defined(USE_GPS)
1778 int16_t v
= getEstimatedActualVelocity(Z
) / 50; //50cm = 1 arrow
1779 uint8_t vchars
[] = {SYM_BLANK
, SYM_BLANK
, SYM_BLANK
, SYM_BLANK
, SYM_BLANK
};
1782 vchars
[0] = SYM_VARIO_UP_2A
;
1784 vchars
[0] = SYM_VARIO_UP_1A
;
1786 vchars
[1] = SYM_VARIO_UP_2A
;
1788 vchars
[1] = SYM_VARIO_UP_1A
;
1790 vchars
[2] = SYM_VARIO_UP_2A
;
1792 vchars
[2] = SYM_VARIO_UP_1A
;
1794 vchars
[2] = SYM_VARIO_DOWN_2A
;
1796 vchars
[2] = SYM_VARIO_DOWN_1A
;
1798 vchars
[3] = SYM_VARIO_DOWN_2A
;
1800 vchars
[3] = SYM_VARIO_DOWN_1A
;
1802 vchars
[4] = SYM_VARIO_DOWN_2A
;
1804 vchars
[4] = SYM_VARIO_DOWN_1A
;
1806 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
, vchars
[0]);
1807 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
+1, vchars
[1]);
1808 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
+2, vchars
[2]);
1809 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
+3, vchars
[3]);
1810 displayWriteChar(osdDisplayPort
, elemPosX
, elemPosY
+4, vchars
[4]);
1816 int16_t value
= getEstimatedActualVelocity(Z
);
1818 switch ((osd_unit_e
)osdConfig()->units
) {
1819 case OSD_UNIT_IMPERIAL
:
1820 // Convert to centifeet/s
1821 value
= CENTIMETERS_TO_CENTIFEET(value
);
1826 case OSD_UNIT_METRIC
:
1832 osdFormatCentiNumber(buff
, value
, 0, 1, 0, 3);
1840 osdDisplayPIDValues(elemPosX
, elemPosY
, "ROL", &pidBank()->pid
[PID_ROLL
], ADJUSTMENT_ROLL_P
, ADJUSTMENT_ROLL_I
, ADJUSTMENT_ROLL_D
);
1843 case OSD_PITCH_PIDS
:
1844 osdDisplayPIDValues(elemPosX
, elemPosY
, "PIT", &pidBank()->pid
[PID_PITCH
], ADJUSTMENT_PITCH_P
, ADJUSTMENT_PITCH_I
, ADJUSTMENT_PITCH_D
);
1848 osdDisplayPIDValues(elemPosX
, elemPosY
, "YAW", &pidBank()->pid
[PID_YAW
], ADJUSTMENT_YAW_P
, ADJUSTMENT_YAW_I
, ADJUSTMENT_YAW_D
);
1851 case OSD_LEVEL_PIDS
:
1852 osdDisplayPIDValues(elemPosX
, elemPosY
, "LEV", &pidBank()->pid
[PID_LEVEL
], ADJUSTMENT_LEVEL_P
, ADJUSTMENT_LEVEL_I
, ADJUSTMENT_LEVEL_D
);
1855 case OSD_POS_XY_PIDS
:
1856 osdDisplayPIDValues(elemPosX
, elemPosY
, "PXY", &pidBank()->pid
[PID_POS_XY
], ADJUSTMENT_POS_XY_P
, ADJUSTMENT_POS_XY_I
, ADJUSTMENT_POS_XY_D
);
1859 case OSD_POS_Z_PIDS
:
1860 osdDisplayPIDValues(elemPosX
, elemPosY
, "PZ", &pidBank()->pid
[PID_POS_Z
], ADJUSTMENT_POS_Z_P
, ADJUSTMENT_POS_Z_I
, ADJUSTMENT_POS_Z_D
);
1863 case OSD_VEL_XY_PIDS
:
1864 if (STATE(FIXED_WING
))
1865 osdDisplayPIDValues(elemPosX
, elemPosY
, "VXY", &pidBank()->pid
[PID_VEL_XY
], ADJUSTMENT_VEL_XY_P
, ADJUSTMENT_VEL_XY_I
, ADJUSTMENT_VEL_XY_D
);
1868 case OSD_VEL_Z_PIDS
:
1869 if (STATE(FIXED_WING
))
1870 osdDisplayPIDValues(elemPosX
, elemPosY
, "VZ", &pidBank()->pid
[PID_VEL_Z
], ADJUSTMENT_VEL_Z_P
, ADJUSTMENT_VEL_Z_I
, ADJUSTMENT_VEL_Z_D
);
1874 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "HP", 0, pidBank()->pid
[PID_HEADING
].P
, 3, 0, ADJUSTMENT_HEADING_P
);
1877 case OSD_BOARD_ALIGN_ROLL
:
1878 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "AR", 0, DECIDEGREES_TO_DEGREES((float)boardAlignment()->rollDeciDegrees
), 4, 1, ADJUSTMENT_ROLL_BOARD_ALIGNMENT
);
1881 case OSD_BOARD_ALIGN_PITCH
:
1882 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "AP", 0, DECIDEGREES_TO_DEGREES((float)boardAlignment()->pitchDeciDegrees
), 4, 1, ADJUSTMENT_PITCH_BOARD_ALIGNMENT
);
1886 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "EXP", 0, currentControlRateProfile
->stabilized
.rcExpo8
, 3, 0, ADJUSTMENT_RC_EXPO
);
1889 case OSD_RC_YAW_EXPO
:
1890 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "YEX", 0, currentControlRateProfile
->stabilized
.rcYawExpo8
, 3, 0, ADJUSTMENT_RC_YAW_EXPO
);
1893 case OSD_THROTTLE_EXPO
:
1894 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "TEX", 0, currentControlRateProfile
->throttle
.rcExpo8
, 3, 0, ADJUSTMENT_THROTTLE_EXPO
);
1897 case OSD_PITCH_RATE
:
1898 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "SPR");
1900 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1901 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->stabilized
.rates
[FD_PITCH
]);
1902 if (isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_RATE
))
1903 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1904 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1908 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "SRR");
1910 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1911 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->stabilized
.rates
[FD_ROLL
]);
1912 if (isAdjustmentFunctionSelected(ADJUSTMENT_ROLL_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_PITCH_ROLL_RATE
))
1913 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1914 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1918 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "SYR", 0, currentControlRateProfile
->stabilized
.rates
[FD_YAW
], 3, 0, ADJUSTMENT_YAW_RATE
);
1921 case OSD_MANUAL_RC_EXPO
:
1922 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MEX", 0, currentControlRateProfile
->manual
.rcExpo8
, 3, 0, ADJUSTMENT_MANUAL_RC_EXPO
);
1925 case OSD_MANUAL_RC_YAW_EXPO
:
1926 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MYX", 0, currentControlRateProfile
->manual
.rcYawExpo8
, 3, 0, ADJUSTMENT_MANUAL_RC_YAW_EXPO
);
1929 case OSD_MANUAL_PITCH_RATE
:
1930 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "MPR");
1932 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1933 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->manual
.rates
[FD_PITCH
]);
1934 if (isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_ROLL_RATE
))
1935 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1936 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1939 case OSD_MANUAL_ROLL_RATE
:
1940 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, "MRR");
1942 elemAttr
= TEXT_ATTRIBUTES_NONE
;
1943 tfp_sprintf(buff
, "%3d", currentControlRateProfile
->manual
.rates
[FD_ROLL
]);
1944 if (isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_ROLL_RATE
) || isAdjustmentFunctionSelected(ADJUSTMENT_MANUAL_PITCH_ROLL_RATE
))
1945 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
1946 displayWriteWithAttr(osdDisplayPort
, elemPosX
+ 4, elemPosY
, buff
, elemAttr
);
1949 case OSD_MANUAL_YAW_RATE
:
1950 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "MYR", 0, currentControlRateProfile
->stabilized
.rates
[FD_YAW
], 3, 0, ADJUSTMENT_YAW_RATE
);
1953 case OSD_NAV_FW_CRUISE_THR
:
1954 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "CRS", 0, navConfig()->fw
.cruise_throttle
, 4, 0, ADJUSTMENT_NAV_FW_CRUISE_THR
);
1957 case OSD_NAV_FW_PITCH2THR
:
1958 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "P2T", 0, navConfig()->fw
.pitch_to_throttle
, 3, 0, ADJUSTMENT_NAV_FW_PITCH2THR
);
1961 case OSD_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
:
1962 osdDisplayAdjustableDecimalValue(elemPosX
, elemPosY
, "0TP", 0, (float)mixerConfig()->fwMinThrottleDownPitchAngle
/ 10, 3, 1, ADJUSTMENT_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
);
1965 case OSD_FW_ALT_PID_OUTPUTS
:
1967 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
1968 osdFormatPidControllerOutput(buff
, "PZO", &nav_pids
->fw_alt
, 10, true); // display requested pitch degrees
1972 case OSD_FW_POS_PID_OUTPUTS
:
1974 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers(); // display requested roll degrees
1975 osdFormatPidControllerOutput(buff
, "PXYO", &nav_pids
->fw_nav
, 1, true);
1979 case OSD_MC_VEL_Z_PID_OUTPUTS
:
1981 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
1982 osdFormatPidControllerOutput(buff
, "VZO", &nav_pids
->vel
[Z
], 100, false); // display throttle adjustment µs
1986 case OSD_MC_VEL_X_PID_OUTPUTS
:
1988 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
1989 osdFormatPidControllerOutput(buff
, "VXO", &nav_pids
->vel
[X
], 100, false); // display requested acceleration cm/s^2
1993 case OSD_MC_VEL_Y_PID_OUTPUTS
:
1995 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
1996 osdFormatPidControllerOutput(buff
, "VYO", &nav_pids
->vel
[Y
], 100, false); // display requested acceleration cm/s^2
2000 case OSD_MC_POS_XYZ_P_OUTPUTS
:
2002 const navigationPIDControllers_t
*nav_pids
= getNavigationPIDControllers();
2003 strcpy(buff
, "POSO ");
2004 // display requested velocity cm/s
2005 tfp_sprintf(buff
+ 5, "%4d", lrintf(nav_pids
->pos
[X
].output_constrained
* 100));
2007 tfp_sprintf(buff
+ 10, "%4d", lrintf(nav_pids
->pos
[Y
].output_constrained
* 100));
2009 tfp_sprintf(buff
+ 15, "%4d", lrintf(nav_pids
->pos
[Z
].output_constrained
* 100));
2017 osdFormatCentiNumber(buff
+ 1, getPower(), 0, 2, 0, 3);
2025 osdFormatVelocityStr(buff
+ 1, pitot
.airSpeed
, false);
2034 // RTC not configured will show 00:00
2035 dateTime_t dateTime
;
2036 rtcGetDateTimeLocal(&dateTime
);
2037 buff
[0] = SYM_CLOCK
;
2038 tfp_sprintf(buff
+ 1, "%02u:%02u", dateTime
.hours
, dateTime
.minutes
);
2044 const char *message
= NULL
;
2045 char messageBuf
[MAX(SETTING_MAX_NAME_LENGTH
, OSD_MESSAGE_LENGTH
+1)];
2046 if (ARMING_FLAG(ARMED
)) {
2047 // Aircraft is armed. We might have up to 4
2048 // messages to show.
2049 const char *messages
[4];
2050 unsigned messageCount
= 0;
2051 if (FLIGHT_MODE(FAILSAFE_MODE
)) {
2052 // In FS mode while being armed too
2053 const char *failsafePhaseMessage
= osdFailsafePhaseMessage();
2054 const char *failsafeInfoMessage
= osdFailsafeInfoMessage();
2055 const char *navStateFSMessage
= navigationStateMessage();
2056 if (failsafePhaseMessage
) {
2057 messages
[messageCount
++] = failsafePhaseMessage
;
2059 if (failsafeInfoMessage
) {
2060 messages
[messageCount
++] = failsafeInfoMessage
;
2062 if (navStateFSMessage
) {
2063 messages
[messageCount
++] = navStateFSMessage
;
2065 if (messageCount
> 0) {
2066 message
= messages
[OSD_ALTERNATING_CHOICES(1000, messageCount
)];
2067 if (message
== failsafeInfoMessage
) {
2068 // failsafeInfoMessage is not useful for recovering
2069 // a lost model, but might help avoiding a crash.
2070 // Blink to grab user attention.
2071 TEXT_ATTRIBUTES_ADD_BLINK(elemAttr
);
2073 // We're shoing either failsafePhaseMessage or
2074 // navStateFSMessage. Don't BLINK here since
2075 // having this text available might be crucial
2076 // during a lost aircraft recovery and blinking
2077 // will cause it to be missing from some frames.
2080 if (FLIGHT_MODE(NAV_RTH_MODE
) || FLIGHT_MODE(NAV_WP_MODE
) || navigationIsExecutingAnEmergencyLanding()) {
2081 const char *navStateMessage
= navigationStateMessage();
2082 if (navStateMessage
) {
2083 messages
[messageCount
++] = navStateMessage
;
2086 if (FLIGHT_MODE(NAV_ALTHOLD_MODE
) && !navigationRequiresAngleMode()) {
2087 // ALTHOLD might be enabled alongside ANGLE/HORIZON/ACRO
2088 // when it doesn't require ANGLE mode (required only in FW
2089 // right now). If if requires ANGLE, its display is handled
2091 messages
[messageCount
++] = "(ALTITUDE HOLD)";
2093 if (IS_RC_MODE_ACTIVE(BOXAUTOTRIM
)) {
2094 messages
[messageCount
++] = "(AUTOTRIM)";
2096 if (IS_RC_MODE_ACTIVE(BOXAUTOTUNE
)) {
2097 messages
[messageCount
++] = "(AUTOTUNE)";
2099 if (FLIGHT_MODE(HEADFREE_MODE
)) {
2100 messages
[messageCount
++] = "(HEADFREE)";
2103 // Pick one of the available messages. Each message lasts
2105 if (messageCount
> 0) {
2106 message
= messages
[OSD_ALTERNATING_CHOICES(1000, messageCount
)];
2109 } else if (ARMING_FLAG(ARMING_DISABLED_ALL_FLAGS
)) {
2110 unsigned invalidIndex
;
2111 // Check if we're unable to arm for some reason
2112 if (ARMING_FLAG(ARMING_DISABLED_INVALID_SETTING
) && !settingsValidate(&invalidIndex
)) {
2113 if (OSD_ALTERNATING_CHOICES(1000, 2) == 0) {
2114 const setting_t
*setting
= settingGet(invalidIndex
);
2115 settingGetName(setting
, messageBuf
);
2116 for (int ii
= 0; messageBuf
[ii
]; ii
++) {
2117 messageBuf
[ii
] = sl_toupper(messageBuf
[ii
]);
2119 message
= messageBuf
;
2121 message
= "INVALID SETTING";
2122 TEXT_ATTRIBUTES_ADD_INVERTED(elemAttr
);
2125 if (OSD_ALTERNATING_CHOICES(1000, 2) == 0) {
2126 message
= "UNABLE TO ARM";
2127 TEXT_ATTRIBUTES_ADD_INVERTED(elemAttr
);
2129 // Show the reason for not arming
2130 message
= osdArmingDisabledReasonMessage();
2134 osdFormatMessage(buff
, sizeof(buff
), message
);
2138 case OSD_MAIN_BATT_CELL_VOLTAGE
:
2140 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatteryRawAverageCellVoltage(), 3, 2);
2144 case OSD_MAIN_BATT_SAG_COMPENSATED_CELL_VOLTAGE
:
2146 osdDisplayBatteryVoltage(elemPosX
, elemPosY
, getBatterySagCompensatedAverageCellVoltage(), 3, 2);
2150 case OSD_THROTTLE_POS_AUTO_THR
:
2152 osdFormatThrottlePosition(buff
, true, &elemAttr
);
2156 case OSD_HEADING_GRAPH
:
2158 static const uint8_t graph
[] = {
2162 SYM_HEADING_DIVIDED_LINE
,
2166 SYM_HEADING_DIVIDED_LINE
,
2170 SYM_HEADING_DIVIDED_LINE
,
2174 SYM_HEADING_DIVIDED_LINE
,
2178 SYM_HEADING_DIVIDED_LINE
,
2182 SYM_HEADING_DIVIDED_LINE
,
2187 if (osdIsHeadingValid()) {
2188 int16_t h
= DECIDEGREES_TO_DEGREES(osdGetHeading());
2195 memcpy_fn(buff
, graph
+ hh
+ 1, 9);
2197 buff
[0] = buff
[2] = buff
[4] = buff
[6] = buff
[8] = SYM_HEADING_LINE
;
2198 buff
[1] = buff
[3] = buff
[5] = buff
[7] = SYM_HEADING_DIVIDED_LINE
;
2204 case OSD_EFFICIENCY_MAH_PER_KM
:
2206 // amperage is in centi amps, speed is in cms/s. We want
2207 // mah/km. Values over 999 are considered useless and
2208 // displayed as "---""
2209 static pt1Filter_t eFilterState
;
2210 static timeUs_t efficiencyUpdated
= 0;
2212 timeUs_t currentTimeUs
= micros();
2213 timeDelta_t efficiencyTimeDelta
= cmpTimeUs(currentTimeUs
, efficiencyUpdated
);
2214 if (STATE(GPS_FIX
) && gpsSol
.groundSpeed
> 0) {
2215 if (efficiencyTimeDelta
>= EFFICIENCY_UPDATE_INTERVAL
) {
2216 value
= pt1FilterApply4(&eFilterState
, ((float)getAmperage() / gpsSol
.groundSpeed
) / 0.0036f
,
2217 1, efficiencyTimeDelta
* 1e-6f
);
2219 efficiencyUpdated
= currentTimeUs
;
2221 value
= eFilterState
.state
;
2224 if (value
> 0 && value
<= 999) {
2225 tfp_sprintf(buff
, "%3d", value
);
2227 buff
[0] = buff
[1] = buff
[2] = '-';
2229 buff
[3] = SYM_MAH_KM_0
;
2230 buff
[4] = SYM_MAH_KM_1
;
2235 case OSD_EFFICIENCY_WH_PER_KM
:
2237 // amperage is in centi amps, speed is in cms/s. We want
2238 // mWh/km. Values over 999Wh/km are considered useless and
2239 // displayed as "---""
2240 static pt1Filter_t eFilterState
;
2241 static timeUs_t efficiencyUpdated
= 0;
2243 timeUs_t currentTimeUs
= micros();
2244 timeDelta_t efficiencyTimeDelta
= cmpTimeUs(currentTimeUs
, efficiencyUpdated
);
2245 if (STATE(GPS_FIX
) && gpsSol
.groundSpeed
> 0) {
2246 if (efficiencyTimeDelta
>= EFFICIENCY_UPDATE_INTERVAL
) {
2247 value
= pt1FilterApply4(&eFilterState
, ((float)getPower() / gpsSol
.groundSpeed
) / 0.0036f
,
2248 1, efficiencyTimeDelta
* 1e-6f
);
2250 efficiencyUpdated
= currentTimeUs
;
2252 value
= eFilterState
.state
;
2255 if (value
> 0 && value
<= 999999) {
2256 osdFormatCentiNumber(buff
, value
/ 10, 0, 2, 0, 3);
2258 buff
[0] = buff
[1] = buff
[2] = '-';
2260 buff
[3] = SYM_WH_KM_0
;
2261 buff
[4] = SYM_WH_KM_1
;
2268 // Longest representable string is -32768, hence 6 characters
2269 tfp_sprintf(buff
, "[0]=%6d [1]=%6d", debug
[0], debug
[1]);
2270 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, buff
);
2272 tfp_sprintf(buff
, "[2]=%6d [3]=%6d", debug
[2], debug
[3]);
2276 case OSD_TEMPERATURE
:
2278 int16_t temperature
= getCurrentTemperature();
2279 osdFormatTemperatureSymbol(buff
, temperature
);
2283 case OSD_WIND_SPEED_HORIZONTAL
:
2284 #ifdef USE_WIND_ESTIMATOR
2286 bool valid
= isEstimatedWindSpeedValid();
2287 float horizontalWindSpeed
;
2290 horizontalWindSpeed
= getEstimatedHorizontalWindSpeed(&angle
);
2291 int16_t windDirection
= osdGetHeadingAngle((int)angle
- DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
2292 buff
[1] = SYM_DIRECTION
+ (windDirection
* 2 / 90);
2294 horizontalWindSpeed
= 0;
2295 buff
[1] = SYM_BLANK
;
2297 buff
[0] = SYM_WIND_HORIZONTAL
;
2298 osdFormatWindSpeedStr(buff
+ 2, horizontalWindSpeed
, valid
);
2305 case OSD_WIND_SPEED_VERTICAL
:
2306 #ifdef USE_WIND_ESTIMATOR
2308 buff
[0] = SYM_WIND_VERTICAL
;
2309 buff
[1] = SYM_BLANK
;
2310 bool valid
= isEstimatedWindSpeedValid();
2311 float verticalWindSpeed
;
2313 verticalWindSpeed
= getEstimatedWindSpeed(Z
);
2314 if (verticalWindSpeed
< 0) {
2315 buff
[1] = SYM_AH_DECORATION_DOWN
;
2316 verticalWindSpeed
= -verticalWindSpeed
;
2317 } else if (verticalWindSpeed
> 0) {
2318 buff
[1] = SYM_AH_DECORATION_UP
;
2321 verticalWindSpeed
= 0;
2323 osdFormatWindSpeedStr(buff
+ 2, verticalWindSpeed
, valid
);
2334 displayWriteWithAttr(osdDisplayPort
, elemPosX
, elemPosY
, buff
, elemAttr
);
2338 static uint8_t osdIncElementIndex(uint8_t elementIndex
)
2342 if (elementIndex
== OSD_ARTIFICIAL_HORIZON
)
2345 if (!sensors(SENSOR_ACC
)) {
2346 if (elementIndex
== OSD_CROSSHAIRS
) {
2347 elementIndex
= OSD_ONTIME
;
2351 if (!feature(FEATURE_VBAT
)) {
2352 if (elementIndex
== OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
) {
2353 elementIndex
= OSD_LEVEL_PIDS
;
2357 if (!feature(FEATURE_CURRENT_METER
)) {
2358 if (elementIndex
== OSD_CURRENT_DRAW
) {
2359 elementIndex
= OSD_GPS_SPEED
;
2361 if (elementIndex
== OSD_EFFICIENCY_MAH_PER_KM
) {
2362 elementIndex
= OSD_TRIP_DIST
;
2364 if (elementIndex
== OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
) {
2365 elementIndex
= OSD_HOME_HEADING_ERROR
;
2367 if (elementIndex
== OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
) {
2368 elementIndex
= OSD_LEVEL_PIDS
;
2372 if (!feature(FEATURE_GPS
)) {
2373 if (elementIndex
== OSD_GPS_SPEED
) {
2374 elementIndex
= OSD_ALTITUDE
;
2376 if (elementIndex
== OSD_GPS_LON
) {
2377 elementIndex
= OSD_VARIO
;
2379 if (elementIndex
== OSD_GPS_HDOP
) {
2380 elementIndex
= OSD_MAIN_BATT_CELL_VOLTAGE
;
2382 if (elementIndex
== OSD_TRIP_DIST
) {
2383 elementIndex
= OSD_ATTITUDE_PITCH
;
2385 if (elementIndex
== OSD_MAP_NORTH
) {
2386 elementIndex
= OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
;
2388 if (elementIndex
== OSD_3D_SPEED
) {
2394 if (elementIndex
== OSD_ITEM_COUNT
) {
2397 return elementIndex
;
2400 void osdDrawNextElement(void)
2402 static uint8_t elementIndex
= 0;
2403 // Prevent infinite loop when no elements are enabled
2404 uint8_t index
= elementIndex
;
2406 elementIndex
= osdIncElementIndex(elementIndex
);
2407 } while(!osdDrawSingleElement(elementIndex
) && index
!= elementIndex
);
2409 // Draw artificial horizon last
2410 osdDrawSingleElement(OSD_ARTIFICIAL_HORIZON
);
2413 void pgResetFn_osdConfig(osdConfig_t
*osdConfig
)
2415 osdConfig
->item_pos
[0][OSD_ALTITUDE
] = OSD_POS(1, 0) | OSD_VISIBLE_FLAG
;
2416 osdConfig
->item_pos
[0][OSD_MAIN_BATT_VOLTAGE
] = OSD_POS(12, 0) | OSD_VISIBLE_FLAG
;
2417 osdConfig
->item_pos
[0][OSD_SAG_COMPENSATED_MAIN_BATT_VOLTAGE
] = OSD_POS(12, 1);
2419 osdConfig
->item_pos
[0][OSD_RSSI_VALUE
] = OSD_POS(23, 0) | OSD_VISIBLE_FLAG
;
2421 osdConfig
->item_pos
[0][OSD_HOME_DIST
] = OSD_POS(1, 1);
2422 osdConfig
->item_pos
[0][OSD_TRIP_DIST
] = OSD_POS(1, 2);
2423 osdConfig
->item_pos
[0][OSD_MAIN_BATT_CELL_VOLTAGE
] = OSD_POS(12, 1);
2424 osdConfig
->item_pos
[0][OSD_MAIN_BATT_SAG_COMPENSATED_CELL_VOLTAGE
] = OSD_POS(12, 1);
2425 osdConfig
->item_pos
[0][OSD_GPS_SPEED
] = OSD_POS(23, 1);
2426 osdConfig
->item_pos
[0][OSD_3D_SPEED
] = OSD_POS(23, 1);
2428 osdConfig
->item_pos
[0][OSD_THROTTLE_POS
] = OSD_POS(1, 2) | OSD_VISIBLE_FLAG
;
2429 osdConfig
->item_pos
[0][OSD_THROTTLE_POS_AUTO_THR
] = OSD_POS(6, 2);
2430 osdConfig
->item_pos
[0][OSD_HEADING
] = OSD_POS(12, 2);
2431 osdConfig
->item_pos
[0][OSD_CRUISE_HEADING_ERROR
] = OSD_POS(12, 2);
2432 osdConfig
->item_pos
[0][OSD_CRUISE_HEADING_ADJUSTMENT
] = OSD_POS(12, 2);
2433 osdConfig
->item_pos
[0][OSD_HEADING_GRAPH
] = OSD_POS(18, 2);
2434 osdConfig
->item_pos
[0][OSD_CURRENT_DRAW
] = OSD_POS(1, 3) | OSD_VISIBLE_FLAG
;
2435 osdConfig
->item_pos
[0][OSD_MAH_DRAWN
] = OSD_POS(1, 4) | OSD_VISIBLE_FLAG
;
2436 osdConfig
->item_pos
[0][OSD_WH_DRAWN
] = OSD_POS(1, 5);
2437 osdConfig
->item_pos
[0][OSD_BATTERY_REMAINING_CAPACITY
] = OSD_POS(1, 6);
2438 osdConfig
->item_pos
[0][OSD_BATTERY_REMAINING_PERCENT
] = OSD_POS(1, 7);
2439 osdConfig
->item_pos
[0][OSD_POWER_SUPPLY_IMPEDANCE
] = OSD_POS(1, 8);
2441 osdConfig
->item_pos
[0][OSD_EFFICIENCY_MAH_PER_KM
] = OSD_POS(1, 5);
2442 osdConfig
->item_pos
[0][OSD_EFFICIENCY_WH_PER_KM
] = OSD_POS(1, 5);
2444 osdConfig
->item_pos
[0][OSD_ATTITUDE_ROLL
] = OSD_POS(1, 7);
2445 osdConfig
->item_pos
[0][OSD_ATTITUDE_PITCH
] = OSD_POS(1, 8);
2447 // avoid OSD_VARIO under OSD_CROSSHAIRS
2448 osdConfig
->item_pos
[0][OSD_VARIO
] = OSD_POS(23, 5);
2449 // OSD_VARIO_NUM at the right of OSD_VARIO
2450 osdConfig
->item_pos
[0][OSD_VARIO_NUM
] = OSD_POS(24, 7);
2451 osdConfig
->item_pos
[0][OSD_HOME_DIR
] = OSD_POS(14, 11);
2452 osdConfig
->item_pos
[0][OSD_ARTIFICIAL_HORIZON
] = OSD_POS(8, 6) | OSD_VISIBLE_FLAG
;
2453 osdConfig
->item_pos
[0][OSD_HORIZON_SIDEBARS
] = OSD_POS(8, 6) | OSD_VISIBLE_FLAG
;
2455 osdConfig
->item_pos
[0][OSD_CRAFT_NAME
] = OSD_POS(20, 2);
2456 osdConfig
->item_pos
[0][OSD_VTX_CHANNEL
] = OSD_POS(8, 6);
2458 osdConfig
->item_pos
[0][OSD_ONTIME
] = OSD_POS(23, 8);
2459 osdConfig
->item_pos
[0][OSD_FLYTIME
] = OSD_POS(23, 9);
2460 osdConfig
->item_pos
[0][OSD_ONTIME_FLYTIME
] = OSD_POS(23, 11) | OSD_VISIBLE_FLAG
;
2461 osdConfig
->item_pos
[0][OSD_RTC_TIME
] = OSD_POS(23, 12);
2462 osdConfig
->item_pos
[0][OSD_REMAINING_FLIGHT_TIME_BEFORE_RTH
] = OSD_POS(23, 7);
2463 osdConfig
->item_pos
[0][OSD_REMAINING_DISTANCE_BEFORE_RTH
] = OSD_POS(23, 6);
2465 osdConfig
->item_pos
[0][OSD_GPS_SATS
] = OSD_POS(0, 11) | OSD_VISIBLE_FLAG
;
2466 osdConfig
->item_pos
[0][OSD_GPS_HDOP
] = OSD_POS(0, 10);
2468 osdConfig
->item_pos
[0][OSD_GPS_LAT
] = OSD_POS(0, 12);
2469 osdConfig
->item_pos
[0][OSD_FLYMODE
] = OSD_POS(12, 12) | OSD_VISIBLE_FLAG
;
2470 osdConfig
->item_pos
[0][OSD_GPS_LON
] = OSD_POS(18, 12);
2472 osdConfig
->item_pos
[0][OSD_ROLL_PIDS
] = OSD_POS(2, 10);
2473 osdConfig
->item_pos
[0][OSD_PITCH_PIDS
] = OSD_POS(2, 11);
2474 osdConfig
->item_pos
[0][OSD_YAW_PIDS
] = OSD_POS(2, 12);
2475 osdConfig
->item_pos
[0][OSD_LEVEL_PIDS
] = OSD_POS(2, 12);
2476 osdConfig
->item_pos
[0][OSD_POS_XY_PIDS
] = OSD_POS(2, 12);
2477 osdConfig
->item_pos
[0][OSD_POS_Z_PIDS
] = OSD_POS(2, 12);
2478 osdConfig
->item_pos
[0][OSD_VEL_XY_PIDS
] = OSD_POS(2, 12);
2479 osdConfig
->item_pos
[0][OSD_VEL_Z_PIDS
] = OSD_POS(2, 12);
2480 osdConfig
->item_pos
[0][OSD_HEADING_P
] = OSD_POS(2, 12);
2481 osdConfig
->item_pos
[0][OSD_BOARD_ALIGN_ROLL
] = OSD_POS(2, 10);
2482 osdConfig
->item_pos
[0][OSD_BOARD_ALIGN_PITCH
] = OSD_POS(2, 11);
2483 osdConfig
->item_pos
[0][OSD_RC_EXPO
] = OSD_POS(2, 12);
2484 osdConfig
->item_pos
[0][OSD_RC_YAW_EXPO
] = OSD_POS(2, 12);
2485 osdConfig
->item_pos
[0][OSD_THROTTLE_EXPO
] = OSD_POS(2, 12);
2486 osdConfig
->item_pos
[0][OSD_PITCH_RATE
] = OSD_POS(2, 12);
2487 osdConfig
->item_pos
[0][OSD_ROLL_RATE
] = OSD_POS(2, 12);
2488 osdConfig
->item_pos
[0][OSD_YAW_RATE
] = OSD_POS(2, 12);
2489 osdConfig
->item_pos
[0][OSD_MANUAL_RC_EXPO
] = OSD_POS(2, 12);
2490 osdConfig
->item_pos
[0][OSD_MANUAL_RC_YAW_EXPO
] = OSD_POS(2, 12);
2491 osdConfig
->item_pos
[0][OSD_MANUAL_PITCH_RATE
] = OSD_POS(2, 12);
2492 osdConfig
->item_pos
[0][OSD_MANUAL_ROLL_RATE
] = OSD_POS(2, 12);
2493 osdConfig
->item_pos
[0][OSD_MANUAL_YAW_RATE
] = OSD_POS(2, 12);
2494 osdConfig
->item_pos
[0][OSD_NAV_FW_CRUISE_THR
] = OSD_POS(2, 12);
2495 osdConfig
->item_pos
[0][OSD_NAV_FW_PITCH2THR
] = OSD_POS(2, 12);
2496 osdConfig
->item_pos
[0][OSD_FW_MIN_THROTTLE_DOWN_PITCH_ANGLE
] = OSD_POS(2, 12);
2497 osdConfig
->item_pos
[0][OSD_FW_ALT_PID_OUTPUTS
] = OSD_POS(2, 12);
2498 osdConfig
->item_pos
[0][OSD_FW_POS_PID_OUTPUTS
] = OSD_POS(2, 12);
2499 osdConfig
->item_pos
[0][OSD_MC_VEL_X_PID_OUTPUTS
] = OSD_POS(2, 12);
2500 osdConfig
->item_pos
[0][OSD_MC_VEL_Y_PID_OUTPUTS
] = OSD_POS(2, 12);
2501 osdConfig
->item_pos
[0][OSD_MC_VEL_Z_PID_OUTPUTS
] = OSD_POS(2, 12);
2502 osdConfig
->item_pos
[0][OSD_MC_POS_XYZ_P_OUTPUTS
] = OSD_POS(2, 12);
2504 osdConfig
->item_pos
[0][OSD_POWER
] = OSD_POS(15, 1);
2505 osdConfig
->item_pos
[0][OSD_TEMPERATURE
] = OSD_POS(23, 2);
2507 osdConfig
->item_pos
[0][OSD_AIR_SPEED
] = OSD_POS(3, 5);
2508 osdConfig
->item_pos
[0][OSD_WIND_SPEED_HORIZONTAL
] = OSD_POS(3, 6);
2509 osdConfig
->item_pos
[0][OSD_WIND_SPEED_VERTICAL
] = OSD_POS(3, 7);
2511 // Under OSD_FLYMODE. TODO: Might not be visible on NTSC?
2512 osdConfig
->item_pos
[0][OSD_MESSAGES
] = OSD_POS(1, 13) | OSD_VISIBLE_FLAG
;
2514 for (unsigned ii
= 1; ii
< OSD_LAYOUT_COUNT
; ii
++) {
2515 for (unsigned jj
= 0; jj
< ARRAYLEN(osdConfig
->item_pos
[0]); jj
++) {
2516 osdConfig
->item_pos
[ii
][jj
] = osdConfig
->item_pos
[0][jj
] & ~OSD_VISIBLE_FLAG
;
2520 osdConfig
->rssi_alarm
= 20;
2521 osdConfig
->time_alarm
= 10;
2522 osdConfig
->alt_alarm
= 100;
2523 osdConfig
->dist_alarm
= 1000;
2524 osdConfig
->neg_alt_alarm
= 5;
2526 osdConfig
->video_system
= VIDEO_SYSTEM_AUTO
;
2528 osdConfig
->ahi_reverse_roll
= 0;
2529 osdConfig
->ahi_max_pitch
= AH_MAX_PITCH_DEFAULT
;
2530 osdConfig
->crosshairs_style
= OSD_CROSSHAIRS_STYLE_DEFAULT
;
2531 osdConfig
->left_sidebar_scroll
= OSD_SIDEBAR_SCROLL_NONE
;
2532 osdConfig
->right_sidebar_scroll
= OSD_SIDEBAR_SCROLL_NONE
;
2533 osdConfig
->sidebar_scroll_arrows
= 0;
2535 osdConfig
->units
= OSD_UNIT_METRIC
;
2536 osdConfig
->main_voltage_decimals
= 1;
2538 osdConfig
->estimations_wind_compensation
= true;
2539 osdConfig
->coordinate_digits
= 9;
2541 osdConfig
->osd_failsafe_switch_layout
= false;
2544 static void osdSetNextRefreshIn(uint32_t timeMs
) {
2545 resumeRefreshAt
= micros() + timeMs
* 1000;
2546 refreshWaitForResumeCmdRelease
= true;
2549 void osdInit(displayPort_t
*osdDisplayPortToUse
)
2551 if (!osdDisplayPortToUse
)
2554 BUILD_BUG_ON(OSD_POS_MAX
!= OSD_POS(31,31));
2556 osdDisplayPort
= osdDisplayPortToUse
;
2559 cmsDisplayPortRegister(osdDisplayPort
);
2562 armState
= ARMING_FLAG(ARMED
);
2564 displayClearScreen(osdDisplayPort
);
2567 char string_buffer
[30];
2568 tfp_sprintf(string_buffer
, "INAV VERSION: %s", FC_VERSION_STRING
);
2569 displayWrite(osdDisplayPort
, 5, y
++, string_buffer
);
2571 displayWrite(osdDisplayPort
, 7, y
++, CMS_STARTUP_HELP_TEXT1
);
2572 displayWrite(osdDisplayPort
, 11, y
++, CMS_STARTUP_HELP_TEXT2
);
2573 displayWrite(osdDisplayPort
, 11, y
++, CMS_STARTUP_HELP_TEXT3
);
2577 #define STATS_LABEL_X_POS 4
2578 #define STATS_VALUE_X_POS 24
2579 if (statsConfig()->stats_enabled
) {
2580 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "ODOMETER:");
2581 if (osdConfig()->units
== OSD_UNIT_IMPERIAL
) {
2582 tfp_sprintf(string_buffer
, "%5d", statsConfig()->stats_total_dist
/ METERS_PER_MILE
);
2583 string_buffer
[5] = SYM_MI
;
2585 tfp_sprintf(string_buffer
, "%5d", statsConfig()->stats_total_dist
/ METERS_PER_KILOMETER
);
2586 string_buffer
[5] = SYM_KM
;
2588 string_buffer
[6] = '\0';
2589 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-5, y
, string_buffer
);
2591 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "TOTAL TIME:");
2592 uint32_t tot_mins
= statsConfig()->stats_total_time
/ 60;
2593 tfp_sprintf(string_buffer
, "%2d:%02dHM", tot_mins
/ 60, tot_mins
% 60);
2594 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-5, y
, string_buffer
);
2597 if (feature(FEATURE_VBAT
) && feature(FEATURE_CURRENT_METER
)) {
2598 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "TOTAL ENERGY:");
2599 osdFormatCentiNumber(string_buffer
, statsConfig()->stats_total_energy
/ 10, 0, 2, 0, 4);
2600 strcat(string_buffer
, "\xAB"); // SYM_WH
2601 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-4, y
, string_buffer
);
2603 displayWrite(osdDisplayPort
, STATS_LABEL_X_POS
, ++y
, "AVG EFFICIENCY:");
2604 if (statsConfig()->stats_total_dist
) {
2605 uint32_t avg_efficiency
= statsConfig()->stats_total_energy
/ (statsConfig()->stats_total_dist
/ METERS_PER_KILOMETER
); // mWh/km
2606 osdFormatCentiNumber(string_buffer
, avg_efficiency
/ 10, 0, 2, 0, 3);
2608 strcpy(string_buffer
, "---");
2609 string_buffer
[3] = SYM_WH_KM_0
;
2610 string_buffer
[4] = SYM_WH_KM_1
;
2611 string_buffer
[5] = '\0';
2612 displayWrite(osdDisplayPort
, STATS_VALUE_X_POS
-3, y
, string_buffer
);
2618 displayResync(osdDisplayPort
);
2619 osdSetNextRefreshIn(SPLASH_SCREEN_DISPLAY_TIME
);
2622 static void osdResetStats(void)
2624 stats
.max_current
= 0;
2625 stats
.max_power
= 0;
2626 stats
.max_speed
= 0;
2627 stats
.min_voltage
= 5000;
2628 stats
.min_rssi
= 99;
2629 stats
.max_altitude
= 0;
2632 static void osdUpdateStats(void)
2636 if (feature(FEATURE_GPS
)) {
2637 value
= osdGet3DSpeed();
2638 if (stats
.max_speed
< value
)
2639 stats
.max_speed
= value
;
2641 if (stats
.max_distance
< GPS_distanceToHome
)
2642 stats
.max_distance
= GPS_distanceToHome
;
2645 value
= getBatteryVoltage();
2646 if (stats
.min_voltage
> value
)
2647 stats
.min_voltage
= value
;
2649 value
= abs(getAmperage() / 100);
2650 if (stats
.max_current
< value
)
2651 stats
.max_current
= value
;
2653 value
= abs(getPower() / 100);
2654 if (stats
.max_power
< value
)
2655 stats
.max_power
= value
;
2657 value
= osdConvertRSSI();
2658 if (stats
.min_rssi
> value
)
2659 stats
.min_rssi
= value
;
2661 stats
.max_altitude
= MAX(stats
.max_altitude
, osdGetAltitude());
2664 /* Attention: NTSC screen only has 12 fully visible lines - it is FULL now! */
2665 static void osdShowStats(void)
2667 const char * disarmReasonStr
[DISARM_REASON_COUNT
] = { "UNKNOWN", "TIMEOUT", "STICKS", "SWITCH", "SWITCH", "KILLSW", "FAILSAFE", "NAV SYS" };
2668 uint8_t top
= 1; /* first fully visible line */
2669 const uint8_t statNameX
= 1;
2670 const uint8_t statValuesX
= 20;
2673 displayClearScreen(osdDisplayPort
);
2675 displayWrite(osdDisplayPort
, statNameX
, top
++, " --- STATS ---");
2677 if (STATE(GPS_FIX
)) {
2678 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX SPEED :");
2679 osdFormatVelocityStr(buff
, stats
.max_speed
, true);
2680 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2682 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX DISTANCE :");
2683 osdFormatDistanceStr(buff
, stats
.max_distance
*100);
2684 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2686 displayWrite(osdDisplayPort
, statNameX
, top
, "TRAVELED DISTANCE:");
2687 osdFormatDistanceStr(buff
, getTotalTravelDistance());
2688 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2691 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX ALTITUDE :");
2692 osdFormatAltitudeStr(buff
, stats
.max_altitude
);
2693 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2695 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN BATTERY VOLT :");
2696 osdFormatCentiNumber(buff
, stats
.min_voltage
, 0, osdConfig()->main_voltage_decimals
, 0, osdConfig()->main_voltage_decimals
+ 2);
2698 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2700 displayWrite(osdDisplayPort
, statNameX
, top
, "MIN RSSI :");
2701 itoa(stats
.min_rssi
, buff
, 10);
2703 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2705 if (feature(FEATURE_CURRENT_METER
)) {
2706 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX CURRENT :");
2707 itoa(stats
.max_current
, buff
, 10);
2709 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2711 displayWrite(osdDisplayPort
, statNameX
, top
, "MAX POWER :");
2712 itoa(stats
.max_power
, buff
, 10);
2714 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2716 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
) {
2717 displayWrite(osdDisplayPort
, statNameX
, top
, "USED MAH :");
2718 tfp_sprintf(buff
, "%d%c", getMAhDrawn(), SYM_MAH
);
2720 displayWrite(osdDisplayPort
, statNameX
, top
, "USED WH :");
2721 osdFormatCentiNumber(buff
, getMWhDrawn() / 10, 0, 2, 0, 3);
2722 strcat(buff
, "\xAB"); // SYM_WH
2724 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2726 int32_t totalDistance
= getTotalTravelDistance();
2727 if (totalDistance
> 0) {
2728 displayWrite(osdDisplayPort
, statNameX
, top
, "AVG EFFICIENCY :");
2729 if (osdConfig()->stats_energy_unit
== OSD_STATS_ENERGY_UNIT_MAH
)
2730 tfp_sprintf(buff
, "%d%c%c", getMAhDrawn() * 100000 / totalDistance
,
2731 SYM_MAH_KM_0
, SYM_MAH_KM_1
);
2733 osdFormatCentiNumber(buff
, getMWhDrawn() * 10000 / totalDistance
, 0, 2, 0, 3);
2734 buff
[3] = SYM_WH_KM_0
;
2735 buff
[4] = SYM_WH_KM_1
;
2738 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2742 displayWrite(osdDisplayPort
, statNameX
, top
, "FLY TIME :");
2743 uint16_t flySeconds
= getFlightTime();
2744 uint16_t flyMinutes
= flySeconds
/ 60;
2746 uint16_t flyHours
= flyMinutes
/ 60;
2748 tfp_sprintf(buff
, "%02u:%02u:%02u", flyHours
, flyMinutes
, flySeconds
);
2749 displayWrite(osdDisplayPort
, statValuesX
, top
++, buff
);
2751 displayWrite(osdDisplayPort
, statNameX
, top
, "DISARMED BY :");
2752 displayWrite(osdDisplayPort
, statValuesX
, top
++, disarmReasonStr
[getDisarmReason()]);
2755 // called when motors armed
2756 static void osdShowArmed(void)
2759 char buf
[MAX(32, FORMATTED_DATE_TIME_BUFSIZE
)];
2762 // We need 6 visible rows
2763 uint8_t y
= MIN((osdDisplayPort
->rows
/ 2) - 1, osdDisplayPort
->rows
- 6 - 1);
2765 displayClearScreen(osdDisplayPort
);
2766 displayWrite(osdDisplayPort
, 12, y
, "ARMED");
2769 #if defined(USE_GPS)
2770 if (feature(FEATURE_GPS
)) {
2771 if (STATE(GPS_FIX_HOME
)) {
2772 osdFormatCoordinate(buf
, SYM_LAT
, GPS_home
.lat
);
2773 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
2774 osdFormatCoordinate(buf
, SYM_LON
, GPS_home
.lon
);
2775 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
+ 1, buf
);
2778 strcpy(buf
, "!NO HOME POSITION!");
2779 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(buf
)) / 2, y
, buf
);
2785 if (rtcGetDateTime(&dt
)) {
2786 dateTimeFormatLocal(buf
, &dt
);
2787 dateTimeSplitFormatted(buf
, &date
, &time
);
2789 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(date
)) / 2, y
, date
);
2790 displayWrite(osdDisplayPort
, (osdDisplayPort
->cols
- strlen(time
)) / 2, y
+ 1, time
);
2794 static void osdRefresh(timeUs_t currentTimeUs
)
2797 if (IS_RC_MODE_ACTIVE(BOXOSD
) && (!cmsInMenu
) && !(osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
))) {
2799 if (IS_RC_MODE_ACTIVE(BOXOSD
) && !(osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
))) {
2801 displayClearScreen(osdDisplayPort
);
2802 armState
= ARMING_FLAG(ARMED
);
2806 // detect arm/disarm
2807 if (armState
!= ARMING_FLAG(ARMED
)) {
2808 if (ARMING_FLAG(ARMED
)) {
2810 osdShowArmed(); // reset statistic etc
2811 osdSetNextRefreshIn(ARMED_SCREEN_DISPLAY_TIME
);
2813 osdShowStats(); // show statistic
2814 osdSetNextRefreshIn(STATS_SCREEN_DISPLAY_TIME
);
2817 armState
= ARMING_FLAG(ARMED
);
2820 if (resumeRefreshAt
) {
2821 // If we already reached he time for the next refresh,
2822 // or THR is high or PITCH is high, resume refreshing.
2823 // Clear the screen first to erase other elements which
2824 // might have been drawn while the OSD wasn't refreshing.
2826 if (!DELAYED_REFRESH_RESUME_COMMAND
)
2827 refreshWaitForResumeCmdRelease
= false;
2829 if ((currentTimeUs
> resumeRefreshAt
) || ((!refreshWaitForResumeCmdRelease
) && DELAYED_REFRESH_RESUME_COMMAND
)) {
2830 displayClearScreen(osdDisplayPort
);
2831 resumeRefreshAt
= 0;
2833 displayHeartbeat(osdDisplayPort
);
2839 if (!displayIsGrabbed(osdDisplayPort
)) {
2841 displayClearScreen(osdDisplayPort
);
2844 osdDrawNextElement();
2845 displayHeartbeat(osdDisplayPort
);
2846 #ifdef OSD_CALLS_CMS
2848 cmsUpdate(currentTimeUs
);
2855 * Called periodically by the scheduler
2857 void osdUpdate(timeUs_t currentTimeUs
)
2859 static uint32_t counter
= 0;
2861 // don't touch buffers if DMA transaction is in progress
2862 if (displayIsTransferInProgress(osdDisplayPort
)) {
2866 #if defined(OSD_ALTERNATE_LAYOUT_COUNT) && OSD_ALTERNATE_LAYOUT_COUNT > 0
2867 // Check if the layout has changed. Higher numbered
2868 // boxes take priority.
2869 unsigned activeLayout
;
2870 if (layoutOverride
>= 0) {
2871 activeLayout
= layoutOverride
;
2872 } else if (osdConfig()->osd_failsafe_switch_layout
&& FLIGHT_MODE(FAILSAFE_MODE
)) {
2875 #if OSD_ALTERNATE_LAYOUT_COUNT > 2
2876 if (IS_RC_MODE_ACTIVE(BOXOSDALT3
))
2880 #if OSD_ALTERNATE_LAYOUT_COUNT > 1
2881 if (IS_RC_MODE_ACTIVE(BOXOSDALT2
))
2885 if (IS_RC_MODE_ACTIVE(BOXOSDALT1
))
2890 if (currentLayout
!= activeLayout
) {
2891 currentLayout
= activeLayout
;
2892 osdStartFullRedraw();
2896 #define DRAW_FREQ_DENOM 4
2897 #define STATS_FREQ_DENOM 50
2900 if ((counter
% STATS_FREQ_DENOM
) == 0) {
2904 if ((counter
& DRAW_FREQ_DENOM
) == 0) {
2905 // redraw values in buffer
2906 osdRefresh(currentTimeUs
);
2908 // rest of time redraw screen
2909 displayDrawScreen(osdDisplayPort
);
2913 // do not allow ARM if we are in menu
2914 if (displayIsGrabbed(osdDisplayPort
)) {
2915 ENABLE_ARMING_FLAG(ARMING_DISABLED_OSD_MENU
);
2917 DISABLE_ARMING_FLAG(ARMING_DISABLED_OSD_MENU
);
2922 void osdStartFullRedraw(void)
2927 void osdOverrideLayout(int layout
)
2929 layoutOverride
= constrain(layout
, -1, ARRAYLEN(osdConfig()->item_pos
) - 1);
2932 bool osdItemIsFixed(osd_items_e item
)
2934 return item
== OSD_CROSSHAIRS
||
2935 item
== OSD_ARTIFICIAL_HORIZON
||
2936 item
== OSD_HORIZON_SIDEBARS
;