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 "blackbox/blackbox.h"
37 #include "blackbox/blackbox_io.h"
39 #include "build/debug.h"
40 #include "build/version.h"
43 #include "cms/cms_types.h"
44 #include "cms/cms_menu_osd.h"
46 #include "common/maths.h"
47 #include "common/printf.h"
48 #include "common/typeconversion.h"
49 #include "common/utils.h"
51 #include "config/config_profile.h"
52 #include "config/feature.h"
53 #include "config/parameter_group.h"
54 #include "config/parameter_group_ids.h"
56 #include "drivers/max7456_symbols.h"
57 #include "drivers/display.h"
58 #include "drivers/system.h"
60 #include "drivers/vtx_soft_spi_rtc6705.h"
61 #include "drivers/vtx_soft_spi_rtc6705.h"
63 #include "drivers/vtx_rtc6705.h"
66 #include "io/asyncfatfs/asyncfatfs.h"
67 #include "io/flashfs.h"
71 #include "io/vtx_string.h"
73 #include "fc/config.h"
74 #include "fc/rc_controls.h"
75 #include "fc/runtime_config.h"
77 #include "flight/imu.h"
81 #include "sensors/barometer.h"
82 #include "sensors/battery.h"
83 #include "sensors/sensors.h"
85 #ifdef USE_HARDWARE_REVISION_DETECTION
86 #include "hardware_revision.h"
89 #define VIDEO_BUFFER_CHARS_PAL 480
91 // Character coordinate
93 #define OSD_POSITION_BITS 5 // 5 bits gives a range 0-31
94 #define OSD_POS(x,y) ((x & 0x001F) | ((y & 0x001F) << OSD_POSITION_BITS))
95 #define OSD_X(x) (x & 0x001F)
96 #define OSD_Y(x) ((x >> OSD_POSITION_BITS) & 0x001F)
100 bool blinkState
= true;
102 static uint32_t blinkBits
[(OSD_ITEM_COUNT
+ 31)/32];
103 #define SET_BLINK(item) (blinkBits[(item) / 32] |= (1 << ((item) % 32)))
104 #define CLR_BLINK(item) (blinkBits[(item) / 32] &= ~(1 << ((item) % 32)))
105 #define IS_BLINK(item) (blinkBits[(item) / 32] & (1 << ((item) % 32)))
106 #define BLINK(item) (IS_BLINK(item) && blinkState)
108 // Things in both OSD and CMS
110 #define IS_HI(X) (rcData[X] > 1750)
111 #define IS_LO(X) (rcData[X] < 1250)
112 #define IS_MID(X) (rcData[X] > 1250 && rcData[X] < 1750)
114 //extern uint8_t RSSI; // TODO: not used?
116 static uint16_t flyTime
= 0;
117 static uint8_t statRssi
;
119 typedef struct statistic_s
{
121 int16_t min_voltage
; // /10
122 int16_t max_current
; // /10
124 int16_t max_altitude
;
127 static statistic_t stats
;
129 uint16_t refreshTimeout
= 0;
130 #define REFRESH_1S 12
132 static uint8_t armState
;
134 static displayPort_t
*osdDisplayPort
;
137 #define AH_MAX_PITCH 200 // Specify maximum AHI pitch value displayed. Default 200 = 20.0 degrees
138 #define AH_MAX_ROLL 400 // Specify maximum AHI roll value displayed. Default 400 = 40.0 degrees
139 #define AH_SIDEBAR_WIDTH_POS 7
140 #define AH_SIDEBAR_HEIGHT_POS 3
142 PG_REGISTER_WITH_RESET_FN(osdConfig_t
, osdConfig
, PG_OSD_CONFIG
, 0);
145 * Gets the correct altitude symbol for the current unit system
147 static char osdGetAltitudeSymbol()
149 switch (osdConfig()->units
) {
150 case OSD_UNIT_IMPERIAL
:
158 * Converts altitude based on the current unit system.
159 * @param alt Raw altitude (i.e. as taken from BaroAlt)
161 static int32_t osdGetAltitude(int32_t alt
)
163 switch (osdConfig()->units
) {
164 case OSD_UNIT_IMPERIAL
:
165 return (alt
* 328) / 100; // Convert to feet / 100
167 return alt
; // Already in metre / 100
171 static void osdDrawSingleElement(uint8_t item
)
173 if (!VISIBLE(osdConfig()->item_pos
[item
]) || BLINK(item
))
176 uint8_t elemPosX
= OSD_X(osdConfig()->item_pos
[item
]);
177 uint8_t elemPosY
= OSD_Y(osdConfig()->item_pos
[item
]);
179 uint8_t elemOffsetX
= 0;
186 uint16_t osdRssi
= rssi
* 100 / 1024; // change range
191 sprintf(buff
+ 1, "%d", osdRssi
);
195 case OSD_MAIN_BATT_VOLTAGE
:
197 buff
[0] = SYM_BATT_5
;
198 sprintf(buff
+ 1, "%d.%1dV", getBatteryVoltage() / 10, getBatteryVoltage() % 10);
202 case OSD_CURRENT_DRAW
:
204 int32_t amperage
= getAmperage();
206 sprintf(buff
+ 1, "%d.%02d", abs(amperage
) / 100, abs(amperage
) % 100);
213 sprintf(buff
+ 1, "%d", getMAhDrawn());
221 sprintf(buff
+ 1, "%d", GPS_numSat
);
227 // FIXME ideally we want to use SYM_KMH symbol but it's not in the font any more, so we use K.
228 sprintf(buff
, "%dK", CM_S_TO_KM_H(GPS_speed
) * 10);
236 if (item
== OSD_GPS_LAT
) {
237 buff
[0] = 0x64; // right arrow
238 val
= GPS_coord
[LAT
];
240 buff
[0] = 0x60; // down arrow
241 val
= GPS_coord
[LON
];
248 itoa(val
, &buff
[1], 10);
259 int32_t alt
= osdGetAltitude(baro
.BaroAlt
);
260 sprintf(buff
, "%c%d.%01d%c", alt
< 0 ? '-' : ' ', abs(alt
/ 100), abs((alt
% 100) / 10), osdGetAltitudeSymbol());
266 uint32_t seconds
= micros() / 1000000;
268 sprintf(buff
+ 1, "%02d:%02d", seconds
/ 60, seconds
% 60);
275 sprintf(buff
+ 1, "%02d:%02d", flyTime
/ 60, flyTime
% 60);
283 if (isAirmodeActive())
286 if (FLIGHT_MODE(FAILSAFE_MODE
))
288 else if (FLIGHT_MODE(ANGLE_MODE
))
290 else if (FLIGHT_MODE(HORIZON_MODE
))
293 displayWrite(osdDisplayPort
, elemPosX
, elemPosY
, p
);
299 if (strlen(systemConfig()->name
) == 0)
300 strcpy(buff
, "CRAFT_NAME");
302 for (uint8_t i
= 0; i
< MAX_NAME_LENGTH
; i
++) {
303 buff
[i
] = toupper((unsigned char)systemConfig()->name
[i
]);
304 if (systemConfig()->name
[i
] == 0)
312 case OSD_THROTTLE_POS
:
316 sprintf(buff
+ 2, "%d", (constrain(rcData
[THROTTLE
], PWM_RANGE_MIN
, PWM_RANGE_MAX
) - PWM_RANGE_MIN
) * 100 / (PWM_RANGE_MAX
- PWM_RANGE_MIN
));
320 #if defined(VTX) || defined(USE_RTC6705)
321 case OSD_VTX_CHANNEL
:
323 // FIXME cleanup this when the VTX API is aligned for software vs hardware support of the RTC6705 - See SPRACINGF3NEO/SINGULARITY/SIRINFPV targets.
325 const char vtxBandLetter
= vtx58BandLetter
[vtxConfig()->vtx_band
+ 1];
326 const char *vtxChannelName
= vtx58ChannelNames
[vtxConfig()->vtx_channel
+ 1];
327 sprintf(buff
, "%c:%s", vtxBandLetter
, vtxChannelName
);
328 #elif defined(USE_RTC6705)
329 sprintf(buff
, "CH:%d", current_vtx_channel
% CHANNELS_PER_BAND
+ 1);
336 elemPosX
= 14 - 1; // Offset for 1 char to the left
338 if (displayScreenSize(osdDisplayPort
) == VIDEO_BUFFER_CHARS_PAL
) {
341 buff
[0] = SYM_AH_CENTER_LINE
;
342 buff
[1] = SYM_AH_CENTER
;
343 buff
[2] = SYM_AH_CENTER_LINE_RIGHT
;
347 case OSD_ARTIFICIAL_HORIZON
:
350 elemPosY
= 6 - 4; // Top center of the AH area
352 int rollAngle
= attitude
.values
.roll
;
353 int pitchAngle
= attitude
.values
.pitch
;
355 if (displayScreenSize(osdDisplayPort
) == VIDEO_BUFFER_CHARS_PAL
) {
359 if (pitchAngle
> AH_MAX_PITCH
)
360 pitchAngle
= AH_MAX_PITCH
;
361 if (pitchAngle
< -AH_MAX_PITCH
)
362 pitchAngle
= -AH_MAX_PITCH
;
363 if (rollAngle
> AH_MAX_ROLL
)
364 rollAngle
= AH_MAX_ROLL
;
365 if (rollAngle
< -AH_MAX_ROLL
)
366 rollAngle
= -AH_MAX_ROLL
;
368 // Convert pitchAngle to y compensation value
369 pitchAngle
= (pitchAngle
/ 8) - 41; // 41 = 4 * 9 + 5
371 for (int8_t x
= -4; x
<= 4; x
++) {
372 int y
= (-rollAngle
* x
) / 64;
374 // y += 41; // == 4 * 9 + 5
375 if (y
>= 0 && y
<= 81) {
376 displayWriteChar(osdDisplayPort
, elemPosX
+ x
, elemPosY
+ (y
/ 9), (SYM_AH_BAR9_0
+ (y
% 9)));
380 osdDrawSingleElement(OSD_HORIZON_SIDEBARS
);
385 case OSD_HORIZON_SIDEBARS
:
390 if (displayScreenSize(osdDisplayPort
) == VIDEO_BUFFER_CHARS_PAL
) {
395 int8_t hudwidth
= AH_SIDEBAR_WIDTH_POS
;
396 int8_t hudheight
= AH_SIDEBAR_HEIGHT_POS
;
397 for (int8_t y
= -hudheight
; y
<= hudheight
; y
++) {
398 displayWriteChar(osdDisplayPort
, elemPosX
- hudwidth
, elemPosY
+ y
, SYM_AH_DECORATION
);
399 displayWriteChar(osdDisplayPort
, elemPosX
+ hudwidth
, elemPosY
+ y
, SYM_AH_DECORATION
);
402 // AH level indicators
403 displayWriteChar(osdDisplayPort
, elemPosX
- hudwidth
+ 1, elemPosY
, SYM_AH_LEFT
);
404 displayWriteChar(osdDisplayPort
, elemPosX
+ hudwidth
- 1, elemPosY
, SYM_AH_RIGHT
);
411 const pidProfile_t
*pidProfile
= currentPidProfile
;
412 sprintf(buff
, "ROL %3d %3d %3d", pidProfile
->P8
[PIDROLL
], pidProfile
->I8
[PIDROLL
], pidProfile
->D8
[PIDROLL
]);
418 const pidProfile_t
*pidProfile
= currentPidProfile
;
419 sprintf(buff
, "PIT %3d %3d %3d", pidProfile
->P8
[PIDPITCH
], pidProfile
->I8
[PIDPITCH
], pidProfile
->D8
[PIDPITCH
]);
425 const pidProfile_t
*pidProfile
= currentPidProfile
;
426 sprintf(buff
, "YAW %3d %3d %3d", pidProfile
->P8
[PIDYAW
], pidProfile
->I8
[PIDYAW
], pidProfile
->D8
[PIDYAW
]);
432 sprintf(buff
, "%dW", getAmperage() * getBatteryVoltage() / 1000);
436 case OSD_PIDRATE_PROFILE
:
438 const uint8_t pidProfileIndex
= getCurrentPidProfileIndex();
439 const uint8_t rateProfileIndex
= getCurrentControlRateProfileIndex();
440 sprintf(buff
, "%d-%d", pidProfileIndex
+ 1, rateProfileIndex
+ 1);
444 case OSD_MAIN_BATT_WARNING
:
446 switch(getBatteryState()) {
447 case BATTERY_WARNING
:
448 sprintf(buff
, "LOW BATTERY");
451 case BATTERY_CRITICAL
:
452 sprintf(buff
, "LAND NOW");
462 case OSD_AVG_CELL_VOLTAGE
:
464 uint16_t cellV
= getBatteryVoltage() * 10 / getBatteryCellCount();
465 buff
[0] = SYM_BATT_5
;
466 sprintf(buff
+ 1, "%d.%dV", cellV
/ 100, cellV
% 100);
474 displayWrite(osdDisplayPort
, elemPosX
+ elemOffsetX
, elemPosY
, buff
);
477 void osdDrawElements(void)
479 displayClearScreen(osdDisplayPort
);
481 /* Hide OSD when OSDSW mode is active */
482 if (IS_RC_MODE_ACTIVE(BOXOSD
))
487 osdDrawElementPositioningHelp();
493 else if (sensors(SENSOR_ACC
) || displayIsGrabbed(osdDisplayPort
))
495 else if (sensors(SENSOR_ACC
))
498 osdDrawSingleElement(OSD_ARTIFICIAL_HORIZON
);
499 osdDrawSingleElement(OSD_CROSSHAIRS
);
502 osdDrawSingleElement(OSD_MAIN_BATT_VOLTAGE
);
503 osdDrawSingleElement(OSD_RSSI_VALUE
);
504 osdDrawSingleElement(OSD_FLYTIME
);
505 osdDrawSingleElement(OSD_ONTIME
);
506 osdDrawSingleElement(OSD_FLYMODE
);
507 osdDrawSingleElement(OSD_THROTTLE_POS
);
508 osdDrawSingleElement(OSD_VTX_CHANNEL
);
509 osdDrawSingleElement(OSD_CURRENT_DRAW
);
510 osdDrawSingleElement(OSD_MAH_DRAWN
);
511 osdDrawSingleElement(OSD_CRAFT_NAME
);
512 osdDrawSingleElement(OSD_ALTITUDE
);
513 osdDrawSingleElement(OSD_ROLL_PIDS
);
514 osdDrawSingleElement(OSD_PITCH_PIDS
);
515 osdDrawSingleElement(OSD_YAW_PIDS
);
516 osdDrawSingleElement(OSD_POWER
);
517 osdDrawSingleElement(OSD_PIDRATE_PROFILE
);
518 osdDrawSingleElement(OSD_MAIN_BATT_WARNING
);
519 osdDrawSingleElement(OSD_AVG_CELL_VOLTAGE
);
523 if (sensors(SENSOR_GPS
) || displayIsGrabbed(osdDisplayPort
))
525 if (sensors(SENSOR_GPS
))
528 osdDrawSingleElement(OSD_GPS_SATS
);
529 osdDrawSingleElement(OSD_GPS_SPEED
);
530 osdDrawSingleElement(OSD_GPS_LAT
);
531 osdDrawSingleElement(OSD_GPS_LON
);
536 void pgResetFn_osdConfig(osdConfig_t
*osdProfile
)
538 osdProfile
->item_pos
[OSD_RSSI_VALUE
] = OSD_POS(8, 1) | VISIBLE_FLAG
;
539 osdProfile
->item_pos
[OSD_MAIN_BATT_VOLTAGE
] = OSD_POS(12, 1) | VISIBLE_FLAG
;
540 osdProfile
->item_pos
[OSD_ARTIFICIAL_HORIZON
] = OSD_POS(8, 6) | VISIBLE_FLAG
;
541 osdProfile
->item_pos
[OSD_HORIZON_SIDEBARS
] = OSD_POS(8, 6) | VISIBLE_FLAG
;
542 osdProfile
->item_pos
[OSD_ONTIME
] = OSD_POS(22, 1) | VISIBLE_FLAG
;
543 osdProfile
->item_pos
[OSD_FLYTIME
] = OSD_POS(1, 1) | VISIBLE_FLAG
;
544 osdProfile
->item_pos
[OSD_FLYMODE
] = OSD_POS(13, 11) | VISIBLE_FLAG
;
545 osdProfile
->item_pos
[OSD_CRAFT_NAME
] = OSD_POS(10, 12) | VISIBLE_FLAG
;
546 osdProfile
->item_pos
[OSD_THROTTLE_POS
] = OSD_POS(1, 7) | VISIBLE_FLAG
;
547 osdProfile
->item_pos
[OSD_VTX_CHANNEL
] = OSD_POS(25, 11) | VISIBLE_FLAG
;
548 osdProfile
->item_pos
[OSD_CURRENT_DRAW
] = OSD_POS(1, 12) | VISIBLE_FLAG
;
549 osdProfile
->item_pos
[OSD_MAH_DRAWN
] = OSD_POS(1, 11) | VISIBLE_FLAG
;
550 osdProfile
->item_pos
[OSD_GPS_SPEED
] = OSD_POS(26, 6) | VISIBLE_FLAG
;
551 osdProfile
->item_pos
[OSD_GPS_SATS
] = OSD_POS(19, 1) | VISIBLE_FLAG
;
552 osdProfile
->item_pos
[OSD_ALTITUDE
] = OSD_POS(23, 7) | VISIBLE_FLAG
;
553 osdProfile
->item_pos
[OSD_ROLL_PIDS
] = OSD_POS(7, 13) | VISIBLE_FLAG
;
554 osdProfile
->item_pos
[OSD_PITCH_PIDS
] = OSD_POS(7, 14) | VISIBLE_FLAG
;
555 osdProfile
->item_pos
[OSD_YAW_PIDS
] = OSD_POS(7, 15) | VISIBLE_FLAG
;
556 osdProfile
->item_pos
[OSD_POWER
] = OSD_POS(1, 10) | VISIBLE_FLAG
;
557 osdProfile
->item_pos
[OSD_PIDRATE_PROFILE
] = OSD_POS(25, 10) | VISIBLE_FLAG
;
558 osdProfile
->item_pos
[OSD_MAIN_BATT_WARNING
] = OSD_POS(9, 10) | VISIBLE_FLAG
;
559 osdProfile
->item_pos
[OSD_AVG_CELL_VOLTAGE
] = OSD_POS(12, 2) | VISIBLE_FLAG
;
561 osdProfile
->item_pos
[OSD_GPS_LAT
] = OSD_POS(18, 14) | VISIBLE_FLAG
;
562 osdProfile
->item_pos
[OSD_GPS_LON
] = OSD_POS(18, 15) | VISIBLE_FLAG
;
564 osdProfile
->units
= OSD_UNIT_METRIC
;
565 osdProfile
->rssi_alarm
= 20;
566 osdProfile
->cap_alarm
= 2200;
567 osdProfile
->time_alarm
= 10; // in minutes
568 osdProfile
->alt_alarm
= 100; // meters or feet depend on configuration
571 static void osdDrawLogo(int x
, int y
)
573 // display logo and help
574 char fontOffset
= 160;
575 for (int row
= 0; row
< 4; row
++) {
576 for (int column
= 0; column
< 24; column
++) {
577 if (fontOffset
!= 255) // FIXME magic number
578 displayWriteChar(osdDisplayPort
, x
+ column
, y
+ row
, fontOffset
++);
583 void osdInit(displayPort_t
*osdDisplayPortToUse
)
585 if (!osdDisplayPortToUse
)
588 BUILD_BUG_ON(OSD_POS_MAX
!= OSD_POS(31,31));
590 osdDisplayPort
= osdDisplayPortToUse
;
592 cmsDisplayPortRegister(osdDisplayPort
);
595 armState
= ARMING_FLAG(ARMED
);
597 memset(blinkBits
, 0, sizeof(blinkBits
));
599 displayClearScreen(osdDisplayPort
);
603 char string_buffer
[30];
604 sprintf(string_buffer
, "V%s", FC_VERSION_STRING
);
605 displayWrite(osdDisplayPort
, 20, 6, string_buffer
);
607 displayWrite(osdDisplayPort
, 7, 8, CMS_STARTUP_HELP_TEXT1
);
608 displayWrite(osdDisplayPort
, 11, 9, CMS_STARTUP_HELP_TEXT2
);
609 displayWrite(osdDisplayPort
, 11, 10, CMS_STARTUP_HELP_TEXT3
);
612 displayResync(osdDisplayPort
);
614 refreshTimeout
= 4 * REFRESH_1S
;
617 void osdUpdateAlarms(void)
620 // uint16_t *itemPos = osdConfig()->item_pos;
622 int32_t alt
= osdGetAltitude(baro
.BaroAlt
) / 100;
623 statRssi
= rssi
* 100 / 1024;
625 if (statRssi
< osdConfig()->rssi_alarm
)
626 SET_BLINK(OSD_RSSI_VALUE
);
628 CLR_BLINK(OSD_RSSI_VALUE
);
630 if (getBatteryState() == BATTERY_OK
) {
631 CLR_BLINK(OSD_MAIN_BATT_VOLTAGE
);
632 CLR_BLINK(OSD_MAIN_BATT_WARNING
);
633 CLR_BLINK(OSD_AVG_CELL_VOLTAGE
);
635 SET_BLINK(OSD_MAIN_BATT_VOLTAGE
);
636 SET_BLINK(OSD_MAIN_BATT_WARNING
);
637 SET_BLINK(OSD_AVG_CELL_VOLTAGE
);
640 if (STATE(GPS_FIX
) == 0)
641 SET_BLINK(OSD_GPS_SATS
);
643 CLR_BLINK(OSD_GPS_SATS
);
645 if (flyTime
/ 60 >= osdConfig()->time_alarm
&& ARMING_FLAG(ARMED
))
646 SET_BLINK(OSD_FLYTIME
);
648 CLR_BLINK(OSD_FLYTIME
);
650 if (getMAhDrawn() >= osdConfig()->cap_alarm
)
651 SET_BLINK(OSD_MAH_DRAWN
);
653 CLR_BLINK(OSD_MAH_DRAWN
);
655 if (alt
>= osdConfig()->alt_alarm
)
656 SET_BLINK(OSD_ALTITUDE
);
658 CLR_BLINK(OSD_ALTITUDE
);
661 void osdResetAlarms(void)
663 CLR_BLINK(OSD_RSSI_VALUE
);
664 CLR_BLINK(OSD_MAIN_BATT_VOLTAGE
);
665 CLR_BLINK(OSD_MAIN_BATT_WARNING
);
666 CLR_BLINK(OSD_GPS_SATS
);
667 CLR_BLINK(OSD_FLYTIME
);
668 CLR_BLINK(OSD_MAH_DRAWN
);
669 CLR_BLINK(OSD_ALTITUDE
);
670 CLR_BLINK(OSD_AVG_CELL_VOLTAGE
);
673 static void osdResetStats(void)
675 stats
.max_current
= 0;
677 stats
.min_voltage
= 500;
678 stats
.max_current
= 0;
680 stats
.max_altitude
= 0;
683 static void osdUpdateStats(void)
687 value
= CM_S_TO_KM_H(GPS_speed
);
689 if (stats
.max_speed
< value
)
690 stats
.max_speed
= value
;
692 if (stats
.min_voltage
> getBatteryVoltage())
693 stats
.min_voltage
= getBatteryVoltage();
695 value
= getAmperage() / 100;
696 if (stats
.max_current
< value
)
697 stats
.max_current
= value
;
699 if (stats
.min_rssi
> statRssi
)
700 stats
.min_rssi
= statRssi
;
702 if (stats
.max_altitude
< baro
.BaroAlt
)
703 stats
.max_altitude
= baro
.BaroAlt
;
706 static void osdGetBlackboxStatusString(char * buff
, uint8_t len
)
708 bool storageDeviceIsWorking
= false;
709 uint32_t storageUsed
= 0;
710 uint32_t storageTotal
= 0;
712 switch (blackboxConfig()->device
)
715 case BLACKBOX_DEVICE_SDCARD
:
716 storageDeviceIsWorking
= sdcard_isInserted() && sdcard_isFunctional() && (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY
);
717 if (storageDeviceIsWorking
) {
718 storageTotal
= sdcard_getMetadata()->numBlocks
/ 2000;
719 storageUsed
= storageTotal
- (afatfs_getContiguousFreeSpace() / 1024000);
725 case BLACKBOX_DEVICE_FLASH
:
726 storageDeviceIsWorking
= flashfsIsReady();
727 if (storageDeviceIsWorking
) {
728 const flashGeometry_t
*geometry
= flashfsGetGeometry();
729 storageTotal
= geometry
->totalSize
/ 1024;
730 storageUsed
= flashfsGetOffset() / 1024;
736 storageDeviceIsWorking
= true;
739 if (storageDeviceIsWorking
) {
740 uint16_t storageUsedPercent
= (storageUsed
* 100) / storageTotal
;
741 snprintf(buff
, len
, "%d%%", storageUsedPercent
);
743 snprintf(buff
, len
, "FAULT");
747 static void osdShowStats(void)
752 displayClearScreen(osdDisplayPort
);
753 displayWrite(osdDisplayPort
, 2, top
++, " --- STATS ---");
755 if (STATE(GPS_FIX
)) {
756 displayWrite(osdDisplayPort
, 2, top
, "MAX SPEED :");
757 itoa(stats
.max_speed
, buff
, 10);
758 displayWrite(osdDisplayPort
, 22, top
++, buff
);
761 displayWrite(osdDisplayPort
, 2, top
, "MIN BATTERY :");
762 sprintf(buff
, "%d.%1dV", stats
.min_voltage
/ 10, stats
.min_voltage
% 10);
763 displayWrite(osdDisplayPort
, 22, top
++, buff
);
765 displayWrite(osdDisplayPort
, 2, top
, "MIN RSSI :");
766 itoa(stats
.min_rssi
, buff
, 10);
768 displayWrite(osdDisplayPort
, 22, top
++, buff
);
770 if (batteryConfig()->currentMeterSource
!= CURRENT_METER_NONE
) {
771 displayWrite(osdDisplayPort
, 2, top
, "MAX CURRENT :");
772 itoa(stats
.max_current
, buff
, 10);
774 displayWrite(osdDisplayPort
, 22, top
++, buff
);
776 displayWrite(osdDisplayPort
, 2, top
, "USED MAH :");
777 itoa(getMAhDrawn(), buff
, 10);
778 strcat(buff
, "\x07");
779 displayWrite(osdDisplayPort
, 22, top
++, buff
);
782 displayWrite(osdDisplayPort
, 2, top
, "MAX ALTITUDE :");
783 int32_t alt
= osdGetAltitude(stats
.max_altitude
);
784 sprintf(buff
, "%c%d.%01d%c", alt
< 0 ? '-' : ' ', abs(alt
/ 100), abs((alt
% 100) / 10), osdGetAltitudeSymbol());
785 displayWrite(osdDisplayPort
, 22, top
++, buff
);
787 if (blackboxConfig()->device
&& blackboxConfig()->device
!= BLACKBOX_DEVICE_SERIAL
) {
788 displayWrite(osdDisplayPort
, 2, top
, "BLACKBOX :");
789 osdGetBlackboxStatusString(buff
, 10);
790 displayWrite(osdDisplayPort
, 22, top
++, buff
);
793 refreshTimeout
= 60 * REFRESH_1S
;
796 // called when motors armed
797 static void osdArmMotors(void)
799 displayClearScreen(osdDisplayPort
);
800 displayWrite(osdDisplayPort
, 12, 7, "ARMED");
801 refreshTimeout
= REFRESH_1S
/ 2;
805 static void osdRefresh(timeUs_t currentTimeUs
)
807 static uint8_t lastSec
= 0;
811 if (armState
!= ARMING_FLAG(ARMED
)) {
812 if (ARMING_FLAG(ARMED
))
813 osdArmMotors(); // reset statistic etc
815 osdShowStats(); // show statistic
817 armState
= ARMING_FLAG(ARMED
);
822 sec
= currentTimeUs
/ 1000000;
824 if (ARMING_FLAG(ARMED
) && sec
!= lastSec
) {
829 if (refreshTimeout
) {
830 if (IS_HI(THROTTLE
) || IS_HI(PITCH
)) // hide statistics
834 displayClearScreen(osdDisplayPort
);
838 blinkState
= (currentTimeUs
/ 200000) % 2;
841 if (!displayIsGrabbed(osdDisplayPort
)) {
844 displayHeartbeat(osdDisplayPort
); // heartbeat to stop Minim OSD going back into native mode
847 cmsUpdate(currentTimeUs
);
854 * Called periodically by the scheduler
856 void osdUpdate(timeUs_t currentTimeUs
)
858 static uint32_t counter
= 0;
859 #ifdef MAX7456_DMA_CHANNEL_TX
860 // don't touch buffers if DMA transaction is in progress
861 if (displayIsTransferInProgress(osdDisplayPort
)) {
864 #endif // MAX7456_DMA_CHANNEL_TX
866 // redraw values in buffer
868 #define DRAW_FREQ_DENOM 5
870 #define DRAW_FREQ_DENOM 10 // MWOSD @ 115200 baud
872 if (counter
++ % DRAW_FREQ_DENOM
== 0) {
873 osdRefresh(currentTimeUs
);
874 } else { // rest of time redraw screen 10 chars per idle so it doesn't lock the main idle
875 displayDrawScreen(osdDisplayPort
);
879 // do not allow ARM if we are in menu
880 if (displayIsGrabbed(osdDisplayPort
)) {
881 DISABLE_ARMING_FLAG(OK_TO_ARM
);