2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
22 Created by Marcin Baliniak
23 some functions based on MinimOSD
25 OSD-CMS separation by jflyper
39 #include "blackbox/blackbox.h"
40 #include "blackbox/blackbox_io.h"
42 #include "build/build_config.h"
43 #include "build/version.h"
47 #include "common/axis.h"
48 #include "common/maths.h"
49 #include "common/printf.h"
50 #include "common/typeconversion.h"
51 #include "common/utils.h"
52 #include "common/unit.h"
54 #include "config/feature.h"
56 #include "drivers/display.h"
57 #include "drivers/dshot.h"
58 #include "drivers/flash.h"
59 #include "drivers/osd_symbols.h"
60 #include "drivers/sdcard.h"
61 #include "drivers/time.h"
63 #include "fc/rc_controls.h"
64 #include "fc/rc_modes.h"
65 #include "fc/runtime_config.h"
67 #if defined(USE_DYN_NOTCH_FILTER)
68 #include "flight/dyn_notch_filter.h"
70 #include "flight/imu.h"
71 #include "flight/mixer.h"
72 #include "flight/position.h"
74 #include "io/asyncfatfs/asyncfatfs.h"
75 #include "io/beeper.h"
76 #include "io/flashfs.h"
80 #include "osd/osd_elements.h"
84 #include "pg/pg_ids.h"
90 #include "scheduler/scheduler.h"
92 #include "sensors/acceleration.h"
93 #include "sensors/battery.h"
94 #include "sensors/esc_sensor.h"
95 #include "sensors/sensors.h"
97 #ifdef USE_HARDWARE_REVISION_DETECTION
98 #include "hardware_revision.h"
104 OSD_LOGO_ARMING_FIRST
105 } osd_logo_on_arming_e
;
107 const char * const osdTimerSourceNames
[] = {
114 // Things in both OSD and CMS
116 #define IS_HI(X) (rcData[X] > 1750)
117 #define IS_LO(X) (rcData[X] < 1250)
118 #define IS_MID(X) (rcData[X] > 1250 && rcData[X] < 1750)
120 timeUs_t osdFlyTime
= 0;
125 static bool showVisualBeeper
= false;
127 static statistic_t stats
;
128 timeUs_t resumeRefreshAt
= 0;
129 #define REFRESH_1S 1000 * 1000
131 static uint8_t armState
;
132 #ifdef USE_OSD_PROFILES
133 static uint8_t osdProfile
= 1;
135 static displayPort_t
*osdDisplayPort
;
136 static osdDisplayPortDevice_e osdDisplayPortDeviceType
;
137 static bool osdIsReady
;
139 static bool suppressStatsDisplay
= false;
141 static bool backgroundLayerSupported
= false;
143 #ifdef USE_ESC_SENSOR
144 escSensorData_t
*osdEscDataCombined
;
147 STATIC_ASSERT(OSD_POS_MAX
== OSD_POS(31,31), OSD_POS_MAX_incorrect
);
149 PG_REGISTER_WITH_RESET_FN(osdConfig_t
, osdConfig
, PG_OSD_CONFIG
, 9);
151 PG_REGISTER_WITH_RESET_FN(osdElementConfig_t
, osdElementConfig
, PG_OSD_ELEMENT_CONFIG
, 0);
153 // Controls the display order of the OSD post-flight statistics.
154 // Adjust the ordering here to control how the post-flight stats are presented.
155 // Every entry in osd_stats_e should be represented. Any that are missing will not
156 // be shown on the the post-flight statistics page.
157 // If you reorder the stats it's likely that you'll need to make likewise updates
158 // to the unit tests.
160 // If adding new stats, please add to the osdStatsNeedAccelerometer() function
161 // if the statistic utilizes the accelerometer.
163 const osd_stats_e osdStatsDisplayOrder
[OSD_STAT_COUNT
] = {
164 OSD_STAT_RTC_DATE_TIME
,
167 OSD_STAT_MAX_ALTITUDE
,
169 OSD_STAT_MAX_DISTANCE
,
170 OSD_STAT_FLIGHT_DISTANCE
,
171 OSD_STAT_MIN_BATTERY
,
172 OSD_STAT_END_BATTERY
,
175 OSD_STAT_MAX_CURRENT
,
178 OSD_STAT_BLACKBOX_NUMBER
,
179 OSD_STAT_MAX_G_FORCE
,
180 OSD_STAT_MAX_ESC_TEMP
,
181 OSD_STAT_MAX_ESC_RPM
,
182 OSD_STAT_MIN_LINK_QUALITY
,
184 OSD_STAT_MIN_RSSI_DBM
,
185 OSD_STAT_TOTAL_FLIGHTS
,
190 // Group elements in a number of groups to reduce task scheduling overhead
191 #define OSD_GROUP_COUNT 20
192 // Aim to render a group of elements within a target time
193 #define OSD_ELEMENT_RENDER_TARGET 40
194 // Allow a margin by which a group render can exceed that of the sum of the elements before declaring insane
195 // This will most likely be violated by a USB interrupt whilst using the CLI
196 #define OSD_ELEMENT_RENDER_GROUP_MARGIN 5
197 // Safe margin when rendering elements
198 #define OSD_ELEMENT_RENDER_MARGIN 5
199 // Safe margin in other states
202 // Format a float to the specified number of decimal places with optional rounding.
203 // OSD symbols can optionally be placed before and after the formatted number (use SYM_NONE for no symbol).
204 // The formatString can be used for customized formatting of the integer part. Follow the printf style.
205 // Pass an empty formatString for default.
206 int osdPrintFloat(char *buffer
, char leadingSymbol
, float value
, char *formatString
, unsigned decimalPlaces
, bool round
, char trailingSymbol
)
211 for (unsigned i
= 0; i
< decimalPlaces
; i
++) {
216 const int scaledValueAbs
= ABS(round
? lrintf(value
) : value
);
217 const int integerPart
= scaledValueAbs
/ multiplier
;
218 const int fractionalPart
= scaledValueAbs
% multiplier
;
220 if (leadingSymbol
!= SYM_NONE
) {
221 buffer
[pos
++] = leadingSymbol
;
223 if (value
< 0 && (integerPart
|| fractionalPart
)) {
227 pos
+= tfp_sprintf(buffer
+ pos
, (strlen(formatString
) ? formatString
: "%01u"), integerPart
);
229 tfp_sprintf((char *)&mask
, ".%%0%uu", decimalPlaces
); // builds up the format string to be like ".%03u" for decimalPlaces == 3 as an example
230 pos
+= tfp_sprintf(buffer
+ pos
, mask
, fractionalPart
);
233 if (trailingSymbol
!= SYM_NONE
) {
234 buffer
[pos
++] = trailingSymbol
;
241 void osdStatSetState(uint8_t statIndex
, bool enabled
)
244 osdConfigMutable()->enabled_stats
|= (1 << statIndex
);
246 osdConfigMutable()->enabled_stats
&= ~(1 << statIndex
);
250 bool osdStatGetState(uint8_t statIndex
)
252 return osdConfig()->enabled_stats
& (1 << statIndex
);
255 void osdWarnSetState(uint8_t warningIndex
, bool enabled
)
258 osdConfigMutable()->enabledWarnings
|= (1 << warningIndex
);
260 osdConfigMutable()->enabledWarnings
&= ~(1 << warningIndex
);
264 bool osdWarnGetState(uint8_t warningIndex
)
266 return osdConfig()->enabledWarnings
& (1 << warningIndex
);
269 #ifdef USE_OSD_PROFILES
270 void setOsdProfile(uint8_t value
)
275 if (value
<= OSD_PROFILE_COUNT
) {
279 osdProfile
= 1 << (value
- 1);
284 uint8_t getCurrentOsdProfileIndex(void)
286 return osdConfig()->osdProfileIndex
;
289 void changeOsdProfileIndex(uint8_t profileIndex
)
291 if (profileIndex
<= OSD_PROFILE_COUNT
) {
292 osdConfigMutable()->osdProfileIndex
= profileIndex
;
293 setOsdProfile(profileIndex
);
294 osdAnalyzeActiveElements();
299 void osdAnalyzeActiveElements(void)
301 /* This code results in a total RX task RX_STATE_MODES state time of ~68us on an F411 overclocked to 108MHz
302 * This upsets the scheduler task duration estimation and will break SPI RX communication. This can
303 * occur in flight, but only when the OSD profile is changed by switch so can be ignored, only causing
304 * one late task instance.
306 schedulerIgnoreTaskExecTime();
308 osdAddActiveElements();
309 osdDrawActiveElementsBackground(osdDisplayPort
);
312 const uint16_t osdTimerDefault
[OSD_TIMER_COUNT
] = {
313 OSD_TIMER(OSD_TIMER_SRC_ON
, OSD_TIMER_PREC_SECOND
, 10),
314 OSD_TIMER(OSD_TIMER_SRC_TOTAL_ARMED
, OSD_TIMER_PREC_SECOND
, 10)
317 void pgResetFn_osdConfig(osdConfig_t
*osdConfig
)
319 // Enable the default stats
320 osdConfig
->enabled_stats
= 0; // reset all to off and enable only a few initially
321 osdStatSetState(OSD_STAT_MAX_SPEED
, true);
322 osdStatSetState(OSD_STAT_MIN_BATTERY
, true);
323 osdStatSetState(OSD_STAT_MIN_RSSI
, true);
324 osdStatSetState(OSD_STAT_MAX_CURRENT
, true);
325 osdStatSetState(OSD_STAT_USED_MAH
, true);
326 osdStatSetState(OSD_STAT_BLACKBOX
, true);
327 osdStatSetState(OSD_STAT_BLACKBOX_NUMBER
, true);
328 osdStatSetState(OSD_STAT_TIMER_2
, true);
330 osdConfig
->units
= UNIT_METRIC
;
332 // Enable all warnings by default
333 for (int i
=0; i
< OSD_WARNING_COUNT
; i
++) {
334 osdWarnSetState(i
, true);
336 // turn off RSSI & Link Quality warnings by default
337 osdWarnSetState(OSD_WARNING_RSSI
, false);
338 osdWarnSetState(OSD_WARNING_LINK_QUALITY
, false);
339 osdWarnSetState(OSD_WARNING_RSSI_DBM
, false);
340 // turn off the over mah capacity warning
341 osdWarnSetState(OSD_WARNING_OVER_CAP
, false);
343 osdConfig
->timers
[OSD_TIMER_1
] = osdTimerDefault
[OSD_TIMER_1
];
344 osdConfig
->timers
[OSD_TIMER_2
] = osdTimerDefault
[OSD_TIMER_2
];
346 osdConfig
->overlay_radio_mode
= 2;
348 osdConfig
->rssi_alarm
= 20;
349 osdConfig
->link_quality_alarm
= 80;
350 osdConfig
->cap_alarm
= 2200;
351 osdConfig
->alt_alarm
= 100; // meters or feet depend on configuration
352 osdConfig
->esc_temp_alarm
= ESC_TEMP_ALARM_OFF
; // off by default
353 osdConfig
->esc_rpm_alarm
= ESC_RPM_ALARM_OFF
; // off by default
354 osdConfig
->esc_current_alarm
= ESC_CURRENT_ALARM_OFF
; // off by default
355 osdConfig
->core_temp_alarm
= 70; // a temperature above 70C should produce a warning, lockups have been reported above 80C
357 osdConfig
->ahMaxPitch
= 20; // 20 degrees
358 osdConfig
->ahMaxRoll
= 40; // 40 degrees
360 osdConfig
->osdProfileIndex
= 1;
361 osdConfig
->ahInvert
= false;
362 for (int i
=0; i
< OSD_PROFILE_COUNT
; i
++) {
363 osdConfig
->profile
[i
][0] = '\0';
365 osdConfig
->rssi_dbm_alarm
= -60;
366 osdConfig
->gps_sats_show_hdop
= false;
368 for (int i
= 0; i
< OSD_RCCHANNELS_COUNT
; i
++) {
369 osdConfig
->rcChannels
[i
] = -1;
372 osdConfig
->displayPortDevice
= OSD_DISPLAYPORT_DEVICE_AUTO
;
374 osdConfig
->distance_alarm
= 0;
375 osdConfig
->logo_on_arming
= OSD_LOGO_ARMING_OFF
;
376 osdConfig
->logo_on_arming_duration
= 5; // 0.5 seconds
378 osdConfig
->camera_frame_width
= 24;
379 osdConfig
->camera_frame_height
= 11;
381 osdConfig
->stat_show_cell_value
= false;
382 osdConfig
->framerate_hz
= OSD_FRAMERATE_DEFAULT_HZ
;
383 osdConfig
->cms_background_type
= DISPLAY_BACKGROUND_TRANSPARENT
;
386 void pgResetFn_osdElementConfig(osdElementConfig_t
*osdElementConfig
)
388 // Position elements near centre of screen and disabled by default
389 for (int i
= 0; i
< OSD_ITEM_COUNT
; i
++) {
390 osdElementConfig
->item_pos
[i
] = OSD_POS(10, 7);
393 // Always enable warnings elements by default
394 uint16_t profileFlags
= 0;
395 for (unsigned i
= 1; i
<= OSD_PROFILE_COUNT
; i
++) {
396 profileFlags
|= OSD_PROFILE_FLAG(i
);
398 osdElementConfig
->item_pos
[OSD_WARNINGS
] = OSD_POS(9, 10) | profileFlags
;
400 // Default to old fixed positions for these elements
401 osdElementConfig
->item_pos
[OSD_CROSSHAIRS
] = OSD_POS(13, 6);
402 osdElementConfig
->item_pos
[OSD_ARTIFICIAL_HORIZON
] = OSD_POS(14, 2);
403 osdElementConfig
->item_pos
[OSD_HORIZON_SIDEBARS
] = OSD_POS(14, 6);
404 osdElementConfig
->item_pos
[OSD_CAMERA_FRAME
] = OSD_POS(3, 1);
405 osdElementConfig
->item_pos
[OSD_UP_DOWN_REFERENCE
] = OSD_POS(13, 6);
408 static void osdDrawLogo(int x
, int y
)
410 // display logo and help
411 int fontOffset
= 160;
412 for (int row
= 0; row
< 4; row
++) {
413 for (int column
= 0; column
< 24; column
++) {
414 if (fontOffset
<= SYM_END_OF_FONT
)
415 displayWriteChar(osdDisplayPort
, x
+ column
, y
+ row
, DISPLAYPORT_ATTR_NONE
, fontOffset
++);
420 static void osdCompleteInitialization(void)
422 armState
= ARMING_FLAG(ARMED
);
426 backgroundLayerSupported
= displayLayerSupported(osdDisplayPort
, DISPLAYPORT_LAYER_BACKGROUND
);
427 displayLayerSelect(osdDisplayPort
, DISPLAYPORT_LAYER_FOREGROUND
);
429 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
430 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_WAIT
);
434 char string_buffer
[30];
435 tfp_sprintf(string_buffer
, "V%s", FC_VERSION_STRING
);
436 displayWrite(osdDisplayPort
, 20, 6, DISPLAYPORT_ATTR_NONE
, string_buffer
);
438 displayWrite(osdDisplayPort
, 7, 8, DISPLAYPORT_ATTR_NONE
, CMS_STARTUP_HELP_TEXT1
);
439 displayWrite(osdDisplayPort
, 11, 9, DISPLAYPORT_ATTR_NONE
, CMS_STARTUP_HELP_TEXT2
);
440 displayWrite(osdDisplayPort
, 11, 10, DISPLAYPORT_ATTR_NONE
, CMS_STARTUP_HELP_TEXT3
);
444 char dateTimeBuffer
[FORMATTED_DATE_TIME_BUFSIZE
];
445 if (osdFormatRtcDateTime(&dateTimeBuffer
[0])) {
446 displayWrite(osdDisplayPort
, 5, 12, DISPLAYPORT_ATTR_NONE
, dateTimeBuffer
);
450 resumeRefreshAt
= micros() + (4 * REFRESH_1S
);
451 #ifdef USE_OSD_PROFILES
452 setOsdProfile(osdConfig()->osdProfileIndex
);
455 osdElementsInit(backgroundLayerSupported
);
456 osdAnalyzeActiveElements();
461 void osdInit(displayPort_t
*osdDisplayPortToUse
, osdDisplayPortDevice_e displayPortDeviceType
)
463 osdDisplayPortDeviceType
= displayPortDeviceType
;
465 if (!osdDisplayPortToUse
) {
469 osdDisplayPort
= osdDisplayPortToUse
;
471 cmsDisplayPortRegister(osdDisplayPort
);
475 static void osdResetStats(void)
477 stats
.max_current
= 0;
479 stats
.min_voltage
= 5000;
480 stats
.end_voltage
= 0;
481 stats
.min_rssi
= 99; // percent
482 stats
.max_altitude
= 0;
483 stats
.max_distance
= 0;
484 stats
.armed_time
= 0;
485 stats
.max_g_force
= 0;
486 stats
.max_esc_temp
= 0;
487 stats
.max_esc_rpm
= 0;
488 stats
.min_link_quality
= (linkQualitySource
== LQ_SOURCE_NONE
) ? 99 : 100; // percent
489 stats
.min_rssi_dbm
= CRSF_SNR_MAX
;
492 #if defined(USE_ESC_SENSOR) || defined(USE_DSHOT_TELEMETRY)
493 static int32_t getAverageEscRpm(void)
495 #ifdef USE_DSHOT_TELEMETRY
496 if (motorConfig()->dev
.useDshotTelemetry
) {
498 for (int i
= 0; i
< getMotorCount(); i
++) {
499 rpm
+= getDshotTelemetry(i
);
501 rpm
= rpm
/ getMotorCount();
502 return rpm
* 100 * 2 / motorConfig()->motorPoleCount
;
505 #ifdef USE_ESC_SENSOR
506 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
507 return calcEscRpm(osdEscDataCombined
->rpm
);
514 static uint16_t getStatsVoltage(void)
516 return osdConfig()->stat_show_cell_value
? getBatteryAverageCellVoltage() : getBatteryVoltage();
519 static void osdUpdateStats(void)
524 if (gpsConfig()->gps_use_3d_speed
) {
525 value
= gpsSol
.speed3d
;
527 value
= gpsSol
.groundSpeed
;
529 if (stats
.max_speed
< value
) {
530 stats
.max_speed
= value
;
534 value
= getStatsVoltage();
535 if (stats
.min_voltage
> value
) {
536 stats
.min_voltage
= value
;
539 value
= getAmperage() / 100;
540 if (stats
.max_current
< value
) {
541 stats
.max_current
= value
;
544 value
= getRssiPercent();
545 if (stats
.min_rssi
> value
) {
546 stats
.min_rssi
= value
;
549 int32_t altitudeCm
= getEstimatedAltitudeCm();
550 if (stats
.max_altitude
< altitudeCm
) {
551 stats
.max_altitude
= altitudeCm
;
555 if (stats
.max_g_force
< osdGForce
) {
556 stats
.max_g_force
= osdGForce
;
560 #ifdef USE_RX_LINK_QUALITY_INFO
561 value
= rxGetLinkQualityPercent();
562 if (stats
.min_link_quality
> value
) {
563 stats
.min_link_quality
= value
;
567 #ifdef USE_RX_RSSI_DBM
568 value
= getRssiDbm();
569 if (stats
.min_rssi_dbm
> value
) {
570 stats
.min_rssi_dbm
= value
;
575 if (STATE(GPS_FIX
) && STATE(GPS_FIX_HOME
)) {
576 if (stats
.max_distance
< GPS_distanceToHome
) {
577 stats
.max_distance
= GPS_distanceToHome
;
582 #ifdef USE_ESC_SENSOR
583 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
584 value
= osdEscDataCombined
->temperature
;
585 if (stats
.max_esc_temp
< value
) {
586 stats
.max_esc_temp
= value
;
591 #if defined(USE_ESC_SENSOR) || defined(USE_DSHOT_TELEMETRY)
592 int32_t rpm
= getAverageEscRpm();
593 if (stats
.max_esc_rpm
< rpm
) {
594 stats
.max_esc_rpm
= rpm
;
601 static void osdGetBlackboxStatusString(char * buff
)
603 bool storageDeviceIsWorking
= isBlackboxDeviceWorking();
604 uint32_t storageUsed
= 0;
605 uint32_t storageTotal
= 0;
607 switch (blackboxConfig()->device
) {
609 case BLACKBOX_DEVICE_SDCARD
:
610 if (storageDeviceIsWorking
) {
611 storageTotal
= sdcard_getMetadata()->numBlocks
/ 2000;
612 storageUsed
= storageTotal
- (afatfs_getContiguousFreeSpace() / 1024000);
618 case BLACKBOX_DEVICE_FLASH
:
619 if (storageDeviceIsWorking
) {
621 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
622 const flashGeometry_t
*flashGeometry
= flashGetGeometry();
624 storageTotal
= ((FLASH_PARTITION_SECTOR_COUNT(flashPartition
) * flashGeometry
->sectorSize
) / 1024);
625 storageUsed
= flashfsGetOffset() / 1024;
634 if (storageDeviceIsWorking
) {
635 const uint16_t storageUsedPercent
= (storageUsed
* 100) / storageTotal
;
636 tfp_sprintf(buff
, "%d%%", storageUsedPercent
);
638 tfp_sprintf(buff
, "FAULT");
643 static void osdDisplayStatisticLabel(uint8_t y
, const char * text
, const char * value
)
645 displayWrite(osdDisplayPort
, 2, y
, DISPLAYPORT_ATTR_NONE
, text
);
646 displayWrite(osdDisplayPort
, 20, y
, DISPLAYPORT_ATTR_NONE
, ":");
647 displayWrite(osdDisplayPort
, 22, y
, DISPLAYPORT_ATTR_NONE
, value
);
651 * Test if there's some stat enabled
653 static bool isSomeStatEnabled(void)
655 return (osdConfig()->enabled_stats
!= 0);
659 // The stats display order was previously required to match the enumeration definition so it matched
660 // the order shown in the configurator. However, to allow reordering this screen without breaking the
661 // compatibility, this requirement has been relaxed to a best effort approach. Reordering the elements
662 // on the stats screen will have to be more beneficial than the hassle of not matching exactly to the
663 // configurator list.
665 static bool osdDisplayStat(int statistic
, uint8_t displayRow
)
667 char buff
[OSD_ELEMENT_BUFFER_LENGTH
];
670 case OSD_STAT_RTC_DATE_TIME
: {
671 bool success
= false;
673 success
= osdFormatRtcDateTime(&buff
[0]);
676 tfp_sprintf(buff
, "NO RTC");
679 displayWrite(osdDisplayPort
, 2, displayRow
, DISPLAYPORT_ATTR_NONE
, buff
);
683 case OSD_STAT_TIMER_1
:
684 osdFormatTimer(buff
, false, (OSD_TIMER_SRC(osdConfig()->timers
[OSD_TIMER_1
]) == OSD_TIMER_SRC_ON
? false : true), OSD_TIMER_1
);
685 osdDisplayStatisticLabel(displayRow
, osdTimerSourceNames
[OSD_TIMER_SRC(osdConfig()->timers
[OSD_TIMER_1
])], buff
);
688 case OSD_STAT_TIMER_2
:
689 osdFormatTimer(buff
, false, (OSD_TIMER_SRC(osdConfig()->timers
[OSD_TIMER_2
]) == OSD_TIMER_SRC_ON
? false : true), OSD_TIMER_2
);
690 osdDisplayStatisticLabel(displayRow
, osdTimerSourceNames
[OSD_TIMER_SRC(osdConfig()->timers
[OSD_TIMER_2
])], buff
);
693 case OSD_STAT_MAX_ALTITUDE
: {
694 osdPrintFloat(buff
, SYM_NONE
, osdGetMetersToSelectedUnit(stats
.max_altitude
) / 100.0f
, "", 1, true, osdGetMetersToSelectedUnitSymbol());
695 osdDisplayStatisticLabel(displayRow
, "MAX ALTITUDE", buff
);
700 case OSD_STAT_MAX_SPEED
:
701 if (featureIsEnabled(FEATURE_GPS
)) {
702 tfp_sprintf(buff
, "%d%c", osdGetSpeedToSelectedUnit(stats
.max_speed
), osdGetSpeedToSelectedUnitSymbol());
703 osdDisplayStatisticLabel(displayRow
, "MAX SPEED", buff
);
708 case OSD_STAT_MAX_DISTANCE
:
709 if (featureIsEnabled(FEATURE_GPS
)) {
710 osdFormatDistanceString(buff
, stats
.max_distance
, SYM_NONE
);
711 osdDisplayStatisticLabel(displayRow
, "MAX DISTANCE", buff
);
716 case OSD_STAT_FLIGHT_DISTANCE
:
717 if (featureIsEnabled(FEATURE_GPS
)) {
718 const int distanceFlown
= GPS_distanceFlownInCm
/ 100;
719 osdFormatDistanceString(buff
, distanceFlown
, SYM_NONE
);
720 osdDisplayStatisticLabel(displayRow
, "FLIGHT DISTANCE", buff
);
726 case OSD_STAT_MIN_BATTERY
:
727 osdPrintFloat(buff
, SYM_NONE
, stats
.min_voltage
/ 100.0f
, "", 2, true, SYM_VOLT
);
728 osdDisplayStatisticLabel(displayRow
, osdConfig()->stat_show_cell_value
? "MIN AVG CELL" : "MIN BATTERY", buff
);
731 case OSD_STAT_END_BATTERY
:
732 osdPrintFloat(buff
, SYM_NONE
, stats
.end_voltage
/ 100.0f
, "", 2, true, SYM_VOLT
);
733 osdDisplayStatisticLabel(displayRow
, osdConfig()->stat_show_cell_value
? "END AVG CELL" : "END BATTERY", buff
);
736 case OSD_STAT_BATTERY
:
738 const uint16_t statsVoltage
= getStatsVoltage();
739 osdPrintFloat(buff
, SYM_NONE
, statsVoltage
/ 100.0f
, "", 2, true, SYM_VOLT
);
740 osdDisplayStatisticLabel(displayRow
, osdConfig()->stat_show_cell_value
? "AVG BATT CELL" : "BATTERY", buff
);
745 case OSD_STAT_MIN_RSSI
:
746 itoa(stats
.min_rssi
, buff
, 10);
748 osdDisplayStatisticLabel(displayRow
, "MIN RSSI", buff
);
751 case OSD_STAT_MAX_CURRENT
:
752 if (batteryConfig()->currentMeterSource
!= CURRENT_METER_NONE
) {
753 tfp_sprintf(buff
, "%d%c", stats
.max_current
, SYM_AMP
);
754 osdDisplayStatisticLabel(displayRow
, "MAX CURRENT", buff
);
759 case OSD_STAT_USED_MAH
:
760 if (batteryConfig()->currentMeterSource
!= CURRENT_METER_NONE
) {
761 tfp_sprintf(buff
, "%d%c", getMAhDrawn(), SYM_MAH
);
762 osdDisplayStatisticLabel(displayRow
, "USED MAH", buff
);
768 case OSD_STAT_BLACKBOX
:
769 if (blackboxConfig()->device
&& blackboxConfig()->device
!= BLACKBOX_DEVICE_SERIAL
) {
770 osdGetBlackboxStatusString(buff
);
771 osdDisplayStatisticLabel(displayRow
, "BLACKBOX", buff
);
776 case OSD_STAT_BLACKBOX_NUMBER
:
778 int32_t logNumber
= blackboxGetLogNumber();
779 if (logNumber
>= 0) {
780 itoa(logNumber
, buff
, 10);
781 osdDisplayStatisticLabel(displayRow
, "BB LOG NUM", buff
);
789 case OSD_STAT_MAX_G_FORCE
:
790 if (sensors(SENSOR_ACC
)) {
791 osdPrintFloat(buff
, SYM_NONE
, stats
.max_g_force
, "", 1, true, 'G');
792 osdDisplayStatisticLabel(displayRow
, "MAX G-FORCE", buff
);
798 #ifdef USE_ESC_SENSOR
799 case OSD_STAT_MAX_ESC_TEMP
:
800 tfp_sprintf(buff
, "%d%c", osdConvertTemperatureToSelectedUnit(stats
.max_esc_temp
), osdGetTemperatureSymbolForSelectedUnit());
801 osdDisplayStatisticLabel(displayRow
, "MAX ESC TEMP", buff
);
805 #if defined(USE_ESC_SENSOR) || defined(USE_DSHOT_TELEMETRY)
806 case OSD_STAT_MAX_ESC_RPM
:
807 itoa(stats
.max_esc_rpm
, buff
, 10);
808 osdDisplayStatisticLabel(displayRow
, "MAX ESC RPM", buff
);
812 #ifdef USE_RX_LINK_QUALITY_INFO
813 case OSD_STAT_MIN_LINK_QUALITY
:
814 tfp_sprintf(buff
, "%d", stats
.min_link_quality
);
816 osdDisplayStatisticLabel(displayRow
, "MIN LINK", buff
);
820 #if defined(USE_DYN_NOTCH_FILTER)
821 case OSD_STAT_MAX_FFT
:
822 if (isDynNotchActive()) {
823 int value
= getMaxFFT();
825 tfp_sprintf(buff
, "%dHZ", value
);
826 osdDisplayStatisticLabel(displayRow
, "PEAK FFT", buff
);
828 osdDisplayStatisticLabel(displayRow
, "PEAK FFT", "THRT<20%");
835 #ifdef USE_RX_RSSI_DBM
836 case OSD_STAT_MIN_RSSI_DBM
:
837 tfp_sprintf(buff
, "%3d", stats
.min_rssi_dbm
);
838 osdDisplayStatisticLabel(displayRow
, "MIN RSSI DBM", buff
);
842 #ifdef USE_PERSISTENT_STATS
843 case OSD_STAT_TOTAL_FLIGHTS
:
844 itoa(statsConfig()->stats_total_flights
, buff
, 10);
845 osdDisplayStatisticLabel(displayRow
, "TOTAL FLIGHTS", buff
);
848 case OSD_STAT_TOTAL_TIME
: {
849 int minutes
= statsConfig()->stats_total_time_s
/ 60;
850 tfp_sprintf(buff
, "%d:%02dH", minutes
/ 60, minutes
% 60);
851 osdDisplayStatisticLabel(displayRow
, "TOTAL FLIGHT TIME", buff
);
855 case OSD_STAT_TOTAL_DIST
:
856 #define METERS_PER_KILOMETER 1000
857 #define METERS_PER_MILE 1609
858 if (osdConfig()->units
== UNIT_IMPERIAL
) {
859 tfp_sprintf(buff
, "%d%c", statsConfig()->stats_total_dist_m
/ METERS_PER_MILE
, SYM_MILES
);
861 tfp_sprintf(buff
, "%d%c", statsConfig()->stats_total_dist_m
/ METERS_PER_KILOMETER
, SYM_KM
);
863 osdDisplayStatisticLabel(displayRow
, "TOTAL DISTANCE", buff
);
870 typedef struct osdStatsRenderingState_s
{
874 } osdStatsRenderingState_t
;
876 static osdStatsRenderingState_t osdStatsRenderingState
;
878 static void osdRenderStatsReset(void)
880 // reset to 0 so it will be recalculated on the next stats refresh
881 osdStatsRenderingState
.rowCount
= 0;
884 static void osdRenderStatsBegin(void)
886 osdStatsRenderingState
.row
= 0;
887 osdStatsRenderingState
.index
= 0;
891 // call repeatedly until it returns true which indicates that all stats have been rendered.
892 static bool osdRenderStatsContinue(void)
894 if (osdStatsRenderingState
.row
== 0) {
896 bool displayLabel
= false;
898 // if rowCount is 0 then we're running an initial analysis of the active stats items
899 if (osdStatsRenderingState
.rowCount
> 0) {
900 const int availableRows
= osdDisplayPort
->rows
;
901 int displayRows
= MIN(osdStatsRenderingState
.rowCount
, availableRows
);
902 if (osdStatsRenderingState
.rowCount
< availableRows
) {
906 osdStatsRenderingState
.row
= (availableRows
- displayRows
) / 2; // center the stats vertically
910 displayWrite(osdDisplayPort
, 2, osdStatsRenderingState
.row
++, DISPLAYPORT_ATTR_NONE
, " --- STATS ---");
916 bool renderedStat
= false;
918 while (osdStatsRenderingState
.index
< OSD_STAT_COUNT
) {
919 int index
= osdStatsRenderingState
.index
;
921 // prepare for the next call to the method
922 osdStatsRenderingState
.index
++;
924 // look for something to render
925 if (osdStatGetState(osdStatsDisplayOrder
[index
])) {
926 if (osdDisplayStat(osdStatsDisplayOrder
[index
], osdStatsRenderingState
.row
)) {
927 osdStatsRenderingState
.row
++;
934 bool moreSpaceAvailable
= osdStatsRenderingState
.row
< osdDisplayPort
->rows
;
936 if (renderedStat
&& moreSpaceAvailable
) {
940 if (osdStatsRenderingState
.rowCount
== 0) {
941 osdStatsRenderingState
.rowCount
= osdStatsRenderingState
.row
;
947 // returns true when all phases are complete
948 static bool osdRefreshStats(void)
950 bool completed
= false;
953 INITIAL_CLEAR_SCREEN
= 0,
957 } osd_refresh_stats_phase_e
;
959 static osd_refresh_stats_phase_e phase
= INITIAL_CLEAR_SCREEN
;
963 case INITIAL_CLEAR_SCREEN
:
964 osdRenderStatsBegin();
965 if (osdStatsRenderingState
.rowCount
> 0) {
966 phase
= RENDER_STATS
;
970 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_NONE
);
974 // No stats row count has been set yet.
975 // Go through the logic one time to determine how many stats are actually displayed.
976 bool count_phase_complete
= osdRenderStatsContinue();
977 if (count_phase_complete
) {
983 osdRenderStatsBegin();
984 // Then clear the screen and commence with normal stats display which will
985 // determine if the heading should be displayed and also center the content vertically.
986 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_NONE
);
990 completed
= osdRenderStatsContinue();
995 phase
= INITIAL_CLEAR_SCREEN
;
1001 static timeDelta_t
osdShowArmed(void)
1005 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_WAIT
);
1007 if ((osdConfig()->logo_on_arming
== OSD_LOGO_ARMING_ON
) || ((osdConfig()->logo_on_arming
== OSD_LOGO_ARMING_FIRST
) && !ARMING_FLAG(WAS_EVER_ARMED
))) {
1009 ret
= osdConfig()->logo_on_arming_duration
* 1e5
;
1011 ret
= (REFRESH_1S
/ 2);
1013 displayWrite(osdDisplayPort
, 12, 7, DISPLAYPORT_ATTR_NONE
, "ARMED");
1018 static bool osdStatsVisible
= false;
1019 static bool osdStatsEnabled
= false;
1021 STATIC_UNIT_TESTED
bool osdProcessStats1(timeUs_t currentTimeUs
)
1023 static timeUs_t lastTimeUs
= 0;
1024 static timeUs_t osdStatsRefreshTimeUs
;
1026 bool refreshStatsRequired
= false;
1028 // detect arm/disarm
1029 if (armState
!= ARMING_FLAG(ARMED
)) {
1030 if (ARMING_FLAG(ARMED
)) {
1031 osdStatsEnabled
= false;
1032 osdStatsVisible
= false;
1034 resumeRefreshAt
= osdShowArmed() + currentTimeUs
;
1035 } else if (isSomeStatEnabled()
1036 && !suppressStatsDisplay
1037 && (!(getArmingDisableFlags() & (ARMING_DISABLED_RUNAWAY_TAKEOFF
| ARMING_DISABLED_CRASH_DETECTED
))
1038 || !VISIBLE(osdElementConfig()->item_pos
[OSD_WARNINGS
]))) { // suppress stats if runaway takeoff triggered disarm and WARNINGS element is visible
1039 osdStatsEnabled
= true;
1040 resumeRefreshAt
= currentTimeUs
+ (60 * REFRESH_1S
);
1041 stats
.end_voltage
= getStatsVoltage();
1042 osdRenderStatsReset();
1045 armState
= ARMING_FLAG(ARMED
);
1048 if (ARMING_FLAG(ARMED
)) {
1050 timeUs_t deltaT
= currentTimeUs
- lastTimeUs
;
1051 osdFlyTime
+= deltaT
;
1052 stats
.armed_time
+= deltaT
;
1053 } else if (osdStatsEnabled
) { // handle showing/hiding stats based on OSD disable switch position
1054 if (displayIsGrabbed(osdDisplayPort
)) {
1055 osdStatsEnabled
= false;
1056 resumeRefreshAt
= 0;
1057 stats
.armed_time
= 0;
1059 if (IS_RC_MODE_ACTIVE(BOXOSD
) && osdStatsVisible
) {
1060 osdStatsVisible
= false;
1061 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_NONE
);
1062 } else if (!IS_RC_MODE_ACTIVE(BOXOSD
)) {
1063 if (!osdStatsVisible
) {
1064 osdStatsVisible
= true;
1065 osdStatsRefreshTimeUs
= 0;
1067 if (currentTimeUs
>= osdStatsRefreshTimeUs
) {
1068 osdStatsRefreshTimeUs
= currentTimeUs
+ REFRESH_1S
;
1069 refreshStatsRequired
= true;
1074 lastTimeUs
= currentTimeUs
;
1076 return refreshStatsRequired
;
1079 void osdProcessStats2(timeUs_t currentTimeUs
)
1081 displayBeginTransaction(osdDisplayPort
, DISPLAY_TRANSACTION_OPT_RESET_DRAWING
);
1083 if (resumeRefreshAt
) {
1084 if (cmp32(currentTimeUs
, resumeRefreshAt
) < 0) {
1085 // in timeout period, check sticks for activity to resume display.
1086 if (IS_HI(THROTTLE
) || IS_HI(PITCH
)) {
1087 resumeRefreshAt
= currentTimeUs
;
1091 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_NONE
);
1092 resumeRefreshAt
= 0;
1093 osdStatsEnabled
= false;
1094 stats
.armed_time
= 0;
1097 schedulerIgnoreTaskExecTime();
1099 #ifdef USE_ESC_SENSOR
1100 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
1101 osdEscDataCombined
= getEscSensorData(ESC_SENSOR_COMBINED
);
1106 void osdProcessStats3()
1108 #if defined(USE_ACC)
1109 if (sensors(SENSOR_ACC
)
1110 && (VISIBLE(osdElementConfig()->item_pos
[OSD_G_FORCE
]) || osdStatGetState(OSD_STAT_MAX_G_FORCE
))) {
1111 // only calculate the G force if the element is visible or the stat is enabled
1112 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
1113 const float a
= accAverage
[axis
];
1116 osdGForce
= sqrtf(osdGForce
) * acc
.dev
.acc_1G_rec
;
1125 OSD_STATE_PROCESS_STATS1
,
1126 OSD_STATE_REFRESH_STATS
,
1127 OSD_STATE_PROCESS_STATS2
,
1128 OSD_STATE_PROCESS_STATS3
,
1129 OSD_STATE_UPDATE_ALARMS
,
1130 OSD_STATE_UPDATE_CANVAS
,
1131 OSD_STATE_UPDATE_ELEMENTS
,
1132 OSD_STATE_UPDATE_HEARTBEAT
,
1138 osdState_e osdState
= OSD_STATE_INIT
;
1140 #define OSD_UPDATE_INTERVAL_US (1000000 / osdConfig()->framerate_hz)
1142 // Called periodically by the scheduler
1143 bool osdUpdateCheck(timeUs_t currentTimeUs
, timeDelta_t currentDeltaTimeUs
)
1145 UNUSED(currentDeltaTimeUs
);
1146 static timeUs_t osdUpdateDueUs
= 0;
1148 if (osdState
== OSD_STATE_IDLE
) {
1149 // If the OSD is due a refresh, mark that as being the case
1150 if (cmpTimeUs(currentTimeUs
, osdUpdateDueUs
) > 0) {
1151 osdState
= OSD_STATE_CHECK
;
1153 // Determine time of next update
1154 if (osdUpdateDueUs
) {
1155 osdUpdateDueUs
+= OSD_UPDATE_INTERVAL_US
;
1157 osdUpdateDueUs
= currentTimeUs
+ OSD_UPDATE_INTERVAL_US
;
1162 return (osdState
!= OSD_STATE_IDLE
);
1165 // Called when there is OSD update work to be done
1166 void osdUpdate(timeUs_t currentTimeUs
)
1168 static timeUs_t osdStateDurationUs
[OSD_STATE_COUNT
] = { 0 };
1169 static timeUs_t osdElementDurationUs
[OSD_ITEM_COUNT
] = { 0 };
1170 static timeUs_t osdElementGroupMembership
[OSD_ITEM_COUNT
];
1171 static timeUs_t osdElementGroupTargetUs
[OSD_GROUP_COUNT
] = { 0 };
1172 static timeUs_t osdElementGroupDurationUs
[OSD_GROUP_COUNT
] = { 0 };
1173 static uint8_t osdElementGroup
;
1174 static bool firstPass
= true;
1175 uint8_t osdCurElementGroup
= 0;
1176 timeUs_t executeTimeUs
;
1177 osdState_e osdCurState
= osdState
;
1179 if (osdState
!= OSD_STATE_UPDATE_CANVAS
) {
1180 schedulerIgnoreTaskExecRate();
1184 case OSD_STATE_INIT
:
1185 if (!displayCheckReady(osdDisplayPort
, false)) {
1186 // Frsky osd need a display redraw after search for MAX7456 devices
1187 if (osdDisplayPortDeviceType
== OSD_DISPLAYPORT_DEVICE_FRSKYOSD
) {
1188 displayRedraw(osdDisplayPort
);
1190 schedulerIgnoreTaskExecTime();
1195 osdCompleteInitialization();
1196 displayRedraw(osdDisplayPort
);
1197 osdState
= OSD_STATE_COMMIT
;
1201 case OSD_STATE_CHECK
:
1203 showVisualBeeper
= true;
1206 // don't touch buffers if DMA transaction is in progress
1207 if (displayIsTransferInProgress(osdDisplayPort
)) {
1211 osdState
= OSD_STATE_UPDATE_HEARTBEAT
;
1214 case OSD_STATE_UPDATE_HEARTBEAT
:
1215 if (displayHeartbeat(osdDisplayPort
)) {
1216 // Extraordinary action was taken, so return without allowing osdStateDurationUs table to be updated
1220 osdState
= OSD_STATE_PROCESS_STATS1
;
1223 case OSD_STATE_PROCESS_STATS1
:
1225 bool refreshStatsRequired
= osdProcessStats1(currentTimeUs
);
1226 showVisualBeeper
= false;
1228 if (refreshStatsRequired
) {
1229 osdState
= OSD_STATE_REFRESH_STATS
;
1231 osdState
= OSD_STATE_PROCESS_STATS2
;
1235 case OSD_STATE_REFRESH_STATS
:
1237 bool completed
= osdRefreshStats();
1239 osdState
= OSD_STATE_PROCESS_STATS2
;
1243 case OSD_STATE_PROCESS_STATS2
:
1244 osdProcessStats2(currentTimeUs
);
1246 osdState
= OSD_STATE_PROCESS_STATS3
;
1248 case OSD_STATE_PROCESS_STATS3
:
1252 if (!displayIsGrabbed(osdDisplayPort
))
1255 osdState
= OSD_STATE_UPDATE_ALARMS
;
1259 osdState
= OSD_STATE_COMMIT
;
1262 case OSD_STATE_UPDATE_ALARMS
:
1265 if (resumeRefreshAt
) {
1266 osdState
= OSD_STATE_TRANSFER
;
1268 osdState
= OSD_STATE_UPDATE_CANVAS
;
1272 case OSD_STATE_UPDATE_CANVAS
:
1273 // Hide OSD when OSDSW mode is active
1274 if (IS_RC_MODE_ACTIVE(BOXOSD
)) {
1275 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_WAIT
);
1276 osdState
= OSD_STATE_COMMIT
;
1280 if (backgroundLayerSupported
) {
1281 // Background layer is supported, overlay it onto the foreground
1282 // so that we only need to draw the active parts of the elements.
1283 displayLayerCopy(osdDisplayPort
, DISPLAYPORT_LAYER_FOREGROUND
, DISPLAYPORT_LAYER_BACKGROUND
);
1285 // Background layer not supported, just clear the foreground in preparation
1286 // for drawing the elements including their backgrounds.
1287 displayClearScreen(osdDisplayPort
, DISPLAY_CLEAR_WAIT
);
1291 static bool lastGpsSensorState
;
1292 // Handle the case that the GPS_SENSOR may be delayed in activation
1293 // or deactivate if communication is lost with the module.
1294 const bool currentGpsSensorState
= sensors(SENSOR_GPS
);
1295 if (lastGpsSensorState
!= currentGpsSensorState
) {
1296 lastGpsSensorState
= currentGpsSensorState
;
1297 osdAnalyzeActiveElements();
1303 uint8_t elementGroup
;
1304 uint8_t activeElements
= osdGetActiveElementCount();
1307 for (elementGroup
= 0; elementGroup
< OSD_GROUP_COUNT
; elementGroup
++) {
1308 if (osdElementGroupDurationUs
[elementGroup
] > (osdElementGroupTargetUs
[elementGroup
] + OSD_ELEMENT_RENDER_GROUP_MARGIN
)) {
1309 osdElementGroupDurationUs
[elementGroup
] = 0;
1311 osdElementGroupTargetUs
[elementGroup
] = 0;
1316 // Based on the current element rendering, group to execute in approx 40us
1317 for (uint8_t curElement
= 0; curElement
< activeElements
; curElement
++) {
1318 if ((osdElementGroupTargetUs
[elementGroup
] == 0) ||
1319 ((osdElementGroupTargetUs
[elementGroup
] + osdElementDurationUs
[curElement
]) <= OSD_ELEMENT_RENDER_TARGET
) ||
1320 (elementGroup
== (OSD_GROUP_COUNT
- 1))) {
1321 osdElementGroupTargetUs
[elementGroup
] += osdElementDurationUs
[curElement
];
1322 // If group membership changes, reset the stats for the group
1323 if (osdElementGroupMembership
[curElement
] != elementGroup
) {
1324 osdElementGroupDurationUs
[elementGroup
] = 0;
1326 osdElementGroupMembership
[curElement
] = elementGroup
;
1329 // Try again for this element
1334 // Start with group 0
1335 osdElementGroup
= 0;
1337 if (activeElements
> 0) {
1338 osdState
= OSD_STATE_UPDATE_ELEMENTS
;
1340 osdState
= OSD_STATE_COMMIT
;
1344 case OSD_STATE_UPDATE_ELEMENTS
:
1346 osdCurElementGroup
= osdElementGroup
;
1347 bool moreElements
= true;
1350 timeUs_t startElementTime
= micros();
1351 uint8_t osdCurElement
= osdGetActiveElement();
1353 // This element should be rendered in the next group
1354 if (osdElementGroupMembership
[osdCurElement
] != osdElementGroup
) {
1359 moreElements
= osdDrawNextActiveElement(osdDisplayPort
, currentTimeUs
);
1361 executeTimeUs
= micros() - startElementTime
;
1363 if (executeTimeUs
> osdElementDurationUs
[osdCurElement
]) {
1364 osdElementDurationUs
[osdCurElement
] = executeTimeUs
;
1366 } while (moreElements
);
1369 // There are more elements to draw
1373 osdElementGroup
= 0;
1375 osdState
= OSD_STATE_COMMIT
;
1379 case OSD_STATE_COMMIT
:
1380 displayCommitTransaction(osdDisplayPort
);
1382 if (resumeRefreshAt
) {
1383 osdState
= OSD_STATE_IDLE
;
1385 osdState
= OSD_STATE_TRANSFER
;
1389 case OSD_STATE_TRANSFER
:
1390 // Wait for any current transfer to complete
1391 if (displayIsTransferInProgress(osdDisplayPort
)) {
1395 // Transfer may be broken into many parts
1396 if (displayDrawScreen(osdDisplayPort
)) {
1401 osdState
= OSD_STATE_IDLE
;
1404 case OSD_STATE_IDLE
:
1406 osdState
= OSD_STATE_IDLE
;
1410 if (!schedulerGetIgnoreTaskExecTime()) {
1411 executeTimeUs
= micros() - currentTimeUs
;
1414 // On the first pass no element groups will have been formed, so all elements will have been
1415 // rendered which is unrepresentative, so ignore
1417 if (osdCurState
== OSD_STATE_UPDATE_ELEMENTS
) {
1418 if (executeTimeUs
> osdElementGroupDurationUs
[osdCurElementGroup
]) {
1419 osdElementGroupDurationUs
[osdCurElementGroup
] = executeTimeUs
;
1423 if (executeTimeUs
> osdStateDurationUs
[osdCurState
]) {
1424 osdStateDurationUs
[osdCurState
] = executeTimeUs
;
1429 if (osdState
== OSD_STATE_UPDATE_ELEMENTS
) {
1430 schedulerSetNextStateTime(osdElementGroupDurationUs
[osdElementGroup
] + OSD_ELEMENT_RENDER_MARGIN
);
1432 if (osdState
== OSD_STATE_IDLE
) {
1433 schedulerSetNextStateTime(osdStateDurationUs
[OSD_STATE_CHECK
] + OSD_MARGIN
);
1435 schedulerSetNextStateTime(osdStateDurationUs
[osdState
] + OSD_MARGIN
);
1437 schedulerIgnoreTaskExecTime();
1441 void osdSuppressStats(bool flag
)
1443 suppressStatsDisplay
= flag
;
1446 #ifdef USE_OSD_PROFILES
1447 bool osdElementVisible(uint16_t value
)
1449 return (bool)((((value
& OSD_PROFILE_MASK
) >> OSD_PROFILE_BITS_POS
) & osdProfile
) != 0);
1453 bool osdGetVisualBeeperState(void)
1455 return showVisualBeeper
;
1458 statistic_t
*osdGetStats(void)
1464 // Determine if there are any enabled stats that need
1465 // the ACC (currently only MAX_G_FORCE).
1466 static bool osdStatsNeedAccelerometer(void)
1468 return osdStatGetState(OSD_STAT_MAX_G_FORCE
);
1471 // Check if any enabled elements or stats need the ACC
1472 bool osdNeedsAccelerometer(void)
1474 return osdStatsNeedAccelerometer() || osdElementsNeedAccelerometer();
1478 displayPort_t
*osdGetDisplayPort(osdDisplayPortDevice_e
*displayPortDeviceType
)
1480 if (displayPortDeviceType
) {
1481 *displayPortDeviceType
= osdDisplayPortDeviceType
;
1483 return osdDisplayPort
;