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 Original OSD code created by Marcin Baliniak
20 OSD-CMS separation by jflyper
21 CMS-displayPort separation by jflyper and martinbudden
24 //#define CMS_PAGE_DEBUG // For multi-page/menu debugging
25 //#define CMS_MENU_DEBUG // For external menu content creators
36 #include "build/build_config.h"
37 #include "build/debug.h"
38 #include "build/version.h"
41 #include "cms/cms_menu_builtin.h"
42 #include "cms/cms_menu_osd.h"
43 #include "cms/cms_types.h"
45 #include "common/maths.h"
46 #include "common/printf.h"
47 #include "common/typeconversion.h"
48 #include "common/utils.h"
50 #include "drivers/system.h"
51 #include "drivers/time.h"
53 // For rcData, stopAllMotors, stopPwmAllMotors
54 #include "config/feature.h"
55 #include "config/parameter_group.h"
56 #include "config/parameter_group_ids.h"
59 #include "fc/config.h"
60 #include "fc/rc_controls.h"
61 #include "fc/runtime_config.h"
62 #include "fc/settings.h"
64 #include "flight/mixer.h"
68 #include "io/rcdevice_cam.h"
72 // DisplayPort management
74 #ifndef CMS_MAX_DEVICE
75 #define CMS_MAX_DEVICE 4
78 // Should be as big as the maximum number of rows displayed
79 // simultaneously in the tallest supported screen.
80 static uint8_t entry_flags
[32];
82 #define IS_PRINTVALUE(p, row) (entry_flags[row] & PRINT_VALUE)
83 #define SET_PRINTVALUE(p, row) { entry_flags[row] |= PRINT_VALUE; }
84 #define CLR_PRINTVALUE(p, row) { entry_flags[row] &= ~PRINT_VALUE; }
86 #define IS_PRINTLABEL(p, row) (entry_flags[row] & PRINT_LABEL)
87 #define SET_PRINTLABEL(p, row) { entry_flags[row] |= PRINT_LABEL; }
88 #define CLR_PRINTLABEL(p, row) { entry_flags[row] &= ~PRINT_LABEL; }
90 #define IS_DYNAMIC(p) ((p)->flags & DYNAMIC)
91 #define IS_READONLY(p) ((p)->flags & READONLY)
93 static displayPort_t
*pCurrentDisplay
;
95 static displayPort_t
*cmsDisplayPorts
[CMS_MAX_DEVICE
];
96 static int cmsDeviceCount
;
97 static int cmsCurrentDevice
= -1;
98 static timeMs_t cmsYieldUntil
= 0;
100 bool cmsDisplayPortRegister(displayPort_t
*pDisplay
)
102 if (cmsDeviceCount
== CMS_MAX_DEVICE
)
105 cmsDisplayPorts
[cmsDeviceCount
++] = pDisplay
;
110 static displayPort_t
*cmsDisplayPortSelectCurrent(void)
112 if (cmsDeviceCount
== 0)
115 if (cmsCurrentDevice
< 0)
116 cmsCurrentDevice
= 0;
118 return cmsDisplayPorts
[cmsCurrentDevice
];
121 static displayPort_t
*cmsDisplayPortSelectNext(void)
123 if (cmsDeviceCount
== 0)
126 cmsCurrentDevice
= (cmsCurrentDevice
+ 1) % cmsDeviceCount
; // -1 Okay
128 return cmsDisplayPorts
[cmsCurrentDevice
];
131 #define CMS_UPDATE_INTERVAL_US 50000 // Interval of key scans (microsec)
132 #define CMS_POLL_INTERVAL_US 100000 // Interval of polling dynamic values (microsec)
134 // XXX LEFT_MENU_COLUMN and RIGHT_MENU_COLUMN must be adjusted
135 // dynamically depending on size of the active output device,
136 // or statically to accomodate sizes of all supported devices.
138 // Device characteristics
141 // 128x64 with 5x7 (6x8) : 21 cols x 8 rows
146 // HoTT Telemetry Screen
150 #define LEFT_MENU_COLUMN 1
151 #define RIGHT_MENU_COLUMN(p) ((p)->cols - 8)
152 #define MAX_MENU_ITEMS(p) ((p)->rows - 2)
154 bool cmsInMenu
= false;
156 typedef struct cmsCtx_s
{
157 const CMS_Menu
*menu
; // menu for this context
158 uint8_t page
; // page in the menu
159 int8_t cursorRow
; // cursorRow in the page
162 static cmsCtx_t menuStack
[10];
163 static uint8_t menuStackIdx
= 0;
165 static int8_t pageCount
; // Number of pages in the current menu
166 static const OSD_Entry
*pageTop
; // First entry for the current page
167 static uint8_t pageMaxRow
; // Max row in the current page
169 static cmsCtx_t currentCtx
;
171 #ifdef CMS_MENU_DEBUG // For external menu content creators
173 static char menuErrLabel
[21 + 1] = "RANDOM DATA";
175 static const OSD_Entry menuErrEntries
[] = {
176 { "BROKEN MENU", OME_Label
, NULL
, NULL
, 0 },
177 { menuErrLabel
, OME_Label
, NULL
, NULL
, 0 },
183 static const CMS_Menu menuErr
= {
193 #ifdef CMS_PAGE_DEBUG
194 #define cmsPageDebug() { \
195 debug[0] = pageCount; \
196 debug[1] = currentCtx.page; \
197 debug[2] = pageMaxRow; \
198 debug[3] = currentCtx.cursorRow; } struct _dummy
200 #define cmsPageDebug()
203 static void cmsUpdateMaxRow(displayPort_t
*instance
)
207 for (const OSD_Entry
*ptr
= pageTop
; ptr
->type
!= OME_END
; ptr
++) {
211 if (pageMaxRow
> MAX_MENU_ITEMS(instance
)) {
212 pageMaxRow
= MAX_MENU_ITEMS(instance
);
218 static uint8_t cmsCursorAbsolute(displayPort_t
*instance
)
220 return currentCtx
.cursorRow
+ currentCtx
.page
* MAX_MENU_ITEMS(instance
);
223 static void cmsPageSelect(displayPort_t
*instance
, int8_t newpage
)
225 currentCtx
.page
= (newpage
+ pageCount
) % pageCount
;
226 pageTop
= ¤tCtx
.menu
->entries
[currentCtx
.page
* MAX_MENU_ITEMS(instance
)];
227 cmsUpdateMaxRow(instance
);
228 displayClearScreen(instance
);
231 static void cmsPageNext(displayPort_t
*instance
)
233 cmsPageSelect(instance
, currentCtx
.page
+ 1);
236 static void cmsPagePrev(displayPort_t
*instance
)
238 cmsPageSelect(instance
, currentCtx
.page
- 1);
241 static bool cmsElementIsLabel(OSD_MenuElement element
)
243 return element
== OME_Label
|| element
== OME_LabelFunc
;
246 static void cmsFormatFloat(int32_t value
, char *floatString
)
251 itoa(100000 + value
, floatString
, 10); // Create string from abs of integer value
255 floatString
[0] = floatString
[1];
256 floatString
[1] = floatString
[2];
257 floatString
[2] = '.';
260 // usuwam koncowe zera i kropke
261 // Keep the first decimal place
262 for (k
= 5; k
> 3; k
--)
263 if (floatString
[k
] == '0' || floatString
[k
] == '.')
268 // oraz zero wiodonce
269 if (floatString
[0] == '0')
270 floatString
[0] = ' ';
273 static void cmsPadToSize(char *buf
, int size
)
277 for (i
= 0 ; i
< size
; i
++) {
282 for ( ; i
< size
; i
++) {
289 static int cmsDrawMenuEntry(displayPort_t
*pDisplay
, const OSD_Entry
*p
, uint8_t row
, uint8_t screenRow
)
291 #define CMS_DRAW_BUFFER_LEN 32u
292 char buff
[CMS_DRAW_BUFFER_LEN
];
297 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
298 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, p
->data
);
299 CLR_PRINTVALUE(p
, screenRow
);
305 if (IS_PRINTVALUE(p
, screenRow
)) {
307 int colPos
= RIGHT_MENU_COLUMN(pDisplay
);
309 if ((p
->type
== OME_Submenu
) && p
->func
&& (p
->flags
& OPTSTRING
)) {
311 // Special case of sub menu entry with optional value display.
312 char *str
= p
->menufunc();
313 cnt
= displayWrite(pDisplay
, colPos
, row
, str
);
314 colPos
+= strlen(str
);
317 cnt
+= displayWrite(pDisplay
, colPos
, row
, ">");
319 CLR_PRINTVALUE(p
, screenRow
);
324 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
325 if (*((uint8_t *)(p
->data
))) {
326 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, "YES");
328 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, "NO ");
330 CLR_PRINTVALUE(p
, screenRow
);
335 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
336 bool (*func
)(bool *arg
) = p
->data
;
337 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, func(NULL
) ? "YES" : "NO ");
338 CLR_PRINTVALUE(p
, screenRow
);
343 if (IS_PRINTVALUE(p
, screenRow
)) {
344 const OSD_TAB_t
*ptr
= p
->data
;
345 char * str
= (char *)ptr
->names
[*ptr
->val
];
346 memcpy(buff
, str
, MAX(CMS_DRAW_BUFFER_LEN
, strlen(str
)));
347 cmsPadToSize(buff
, CMS_DRAW_BUFFER_LEN
);
348 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, buff
);
349 CLR_PRINTVALUE(p
, screenRow
);
354 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
356 if (IS_READONLY(p
)) {
359 const OSD_UINT8_t
*ptr
= p
->data
;
362 itoa(*val
, buff
, 10);
363 cmsPadToSize(buff
, 5);
364 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, buff
);
365 CLR_PRINTVALUE(p
, screenRow
);
370 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
372 if (IS_READONLY(p
)) {
375 const OSD_INT8_t
*ptr
= p
->data
;
378 itoa(*val
, buff
, 10);
379 cmsPadToSize(buff
, 5);
380 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, buff
);
381 CLR_PRINTVALUE(p
, screenRow
);
386 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
388 if (IS_READONLY(p
)) {
391 const OSD_UINT16_t
*ptr
= p
->data
;
394 itoa(*val
, buff
, 10);
395 cmsPadToSize(buff
, 5);
396 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, buff
);
397 CLR_PRINTVALUE(p
, screenRow
);
402 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
404 if (IS_READONLY(p
)) {
407 const OSD_INT16_t
*ptr
= p
->data
;
410 itoa(*val
, buff
, 10);
411 cmsPadToSize(buff
, 5);
412 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, buff
);
413 CLR_PRINTVALUE(p
, screenRow
);
418 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
419 const OSD_FLOAT_t
*ptr
= p
->data
;
420 cmsFormatFloat(*ptr
->val
* ptr
->multipler
, buff
);
421 cmsPadToSize(buff
, 5);
422 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
) - 1, row
, buff
); // XXX One char left ???
423 CLR_PRINTVALUE(p
, screenRow
);
428 if (IS_PRINTVALUE(p
, screenRow
) && p
->data
) {
430 const OSD_SETTING_t
*ptr
= p
->data
;
431 const setting_t
*var
= &settingsTable
[ptr
->val
];
433 const void *valuePointer
= setting_get_value_pointer(var
);
434 switch (SETTING_TYPE(var
)) {
436 value
= *(uint8_t *)valuePointer
;
439 value
= *(int8_t *)valuePointer
;
442 value
= *(uint16_t *)valuePointer
;
445 value
= *(int16_t *)valuePointer
;
448 value
= *(uint32_t *)valuePointer
;
451 // XXX: This bypasses the data types. However, we
452 // don't have any VAR_FLOAT settings which require
454 ftoa(*(float *)valuePointer
, buff
);
457 if (buff
[0] == '\0') {
458 const char *suffix
= NULL
;
459 switch (CMS_DATA_TYPE(p
)) {
460 case CMS_DATA_TYPE_ANGULAR_RATE
:
461 // Setting is in degrees/10 per second
466 switch (SETTING_MODE(var
)) {
468 if (SETTING_TYPE(var
) == VAR_UINT32
) {
469 tfp_sprintf(buff
, "%u", (unsigned)value
);
471 tfp_sprintf(buff
, "%d", (int)value
);
476 const char *str
= NULL
;
477 if (var
->config
.lookup
.tableIndex
< LOOKUP_TABLE_COUNT
) {
478 const lookupTableEntry_t
*tableEntry
= &settingLookupTables
[var
->config
.lookup
.tableIndex
];
479 if (value
< tableEntry
->valueCount
) {
480 str
= tableEntry
->values
[value
];
483 strncpy(buff
, str
? str
: "INVALID", sizeof(buff
) - 1);
488 strcat(buff
, suffix
);
491 cmsPadToSize(buff
, 8);
492 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, buff
);
493 CLR_PRINTVALUE(p
, screenRow
);
499 if (IS_PRINTVALUE(p
, screenRow
)) {
500 // A label with optional string, immediately following text
501 const char *text
= p
->data
;
502 if (p
->type
== OME_LabelFunc
) {
503 // Label is generated by a function
504 bool (*label_func
)(char *buf
, unsigned size
) = p
->data
;
505 if (label_func(buff
, sizeof(buff
))) {
512 cnt
= displayWrite(pDisplay
, LEFT_MENU_COLUMN
+ 2 + strlen(p
->text
), row
, text
);
514 CLR_PRINTVALUE(p
, screenRow
);
526 #ifdef CMS_MENU_DEBUG
527 // Shouldn't happen. Notify creator of this menu content.
528 cnt
= displayWrite(pDisplay
, RIGHT_MENU_COLUMN(pDisplay
), row
, "BADENT");
536 static void cmsDrawMenu(displayPort_t
*pDisplay
, uint32_t currentTimeUs
)
543 uint8_t top
= (pDisplay
->rows
- pageMaxRow
) / 2 - 1;
545 // Polled (dynamic) value display denominator.
547 bool drawPolled
= false;
548 static uint32_t lastPolledUs
= 0;
550 if (currentTimeUs
> lastPolledUs
+ CMS_POLL_INTERVAL_US
) {
552 lastPolledUs
= currentTimeUs
;
555 uint32_t room
= displayTxBytesFree(pDisplay
);
557 if (pDisplay
->cleared
) {
558 // Mark all labels and values for printing
559 memset(entry_flags
, PRINT_LABEL
| PRINT_VALUE
, sizeof(entry_flags
));
560 pDisplay
->cleared
= false;
561 } else if (drawPolled
) {
562 for (p
= pageTop
, i
= 0 ; p
<= pageTop
+ pageMaxRow
; p
++, i
++) {
564 SET_PRINTVALUE(p
, i
);
568 // Cursor manipulation
570 while (cmsElementIsLabel((pageTop
+ currentCtx
.cursorRow
)->type
)) // skip label
571 currentCtx
.cursorRow
++;
575 if (pDisplay
->cursorRow
>= 0 && currentCtx
.cursorRow
!= pDisplay
->cursorRow
) {
576 room
-= displayWrite(pDisplay
, LEFT_MENU_COLUMN
, pDisplay
->cursorRow
+ top
, " ");
582 if (pDisplay
->cursorRow
!= currentCtx
.cursorRow
) {
583 room
-= displayWrite(pDisplay
, LEFT_MENU_COLUMN
, currentCtx
.cursorRow
+ top
, " >");
584 pDisplay
->cursorRow
= currentCtx
.cursorRow
;
591 for (i
= 0, p
= pageTop
; i
< MAX_MENU_ITEMS(pDisplay
) && p
->type
!= OME_END
; i
++, p
++) {
592 if (IS_PRINTLABEL(p
, i
)) {
593 uint8_t coloff
= LEFT_MENU_COLUMN
;
594 coloff
+= cmsElementIsLabel(p
->type
) ? 1 : 2;
595 room
-= displayWrite(pDisplay
, coloff
, i
+ top
, p
->text
);
596 CLR_PRINTLABEL(p
, i
);
604 // XXX Polled values at latter positions in the list may not be
605 // XXX printed if not enough room in the middle of the list.
607 for (i
= 0, p
= pageTop
; i
< MAX_MENU_ITEMS(pDisplay
) && p
->type
!= OME_END
; i
++, p
++) {
608 if (IS_PRINTVALUE(p
, i
)) {
609 room
-= cmsDrawMenuEntry(pDisplay
, p
, top
+ i
, i
);
616 static void cmsMenuCountPage(displayPort_t
*pDisplay
)
619 for (p
= currentCtx
.menu
->entries
; p
->type
!= OME_END
; p
++);
620 pageCount
= (p
- currentCtx
.menu
->entries
- 1) / MAX_MENU_ITEMS(pDisplay
) + 1;
623 STATIC_UNIT_TESTED
long cmsMenuBack(displayPort_t
*pDisplay
); // Forward; will be resolved after merging
625 long cmsMenuChange(displayPort_t
*pDisplay
, const CMS_Menu
*pMenu
, const OSD_Entry
*from
)
631 #ifdef CMS_MENU_DEBUG
632 if (pMenu
->GUARD_type
!= OME_MENU
) {
633 // ptr isn't pointing to a CMS_Menu.
634 if (pMenu
->GUARD_type
<= OME_MAX
) {
635 strncpy(menuErrLabel
, pMenu
->GUARD_text
, sizeof(menuErrLabel
) - 1);
637 strncpy(menuErrLabel
, "LABEL UNKNOWN", sizeof(menuErrLabel
) - 1);
643 if (pMenu
!= currentCtx
.menu
) {
644 // Stack the current menu and move to a new menu.
646 menuStack
[menuStackIdx
++] = currentCtx
;
648 currentCtx
.menu
= pMenu
;
649 currentCtx
.cursorRow
= 0;
651 if (pMenu
->onEnter
&& (pMenu
->onEnter(from
) == MENU_CHAIN_BACK
)) {
652 return cmsMenuBack(pDisplay
);
655 cmsMenuCountPage(pDisplay
);
656 cmsPageSelect(pDisplay
, 0);
658 // The (pMenu == curretMenu) case occurs when reopening for display cycling
659 // currentCtx.cursorRow has been saved as absolute; convert it back to page + relative
661 int8_t cursorAbs
= currentCtx
.cursorRow
;
662 currentCtx
.cursorRow
= cursorAbs
% MAX_MENU_ITEMS(pDisplay
);
663 cmsMenuCountPage(pDisplay
);
664 cmsPageSelect(pDisplay
, cursorAbs
/ MAX_MENU_ITEMS(pDisplay
));
672 STATIC_UNIT_TESTED
long cmsMenuBack(displayPort_t
*pDisplay
)
674 // Let onExit function decide whether to allow exit or not.
676 if (currentCtx
.menu
->onExit
&& currentCtx
.menu
->onExit(pageTop
+ currentCtx
.cursorRow
) < 0) {
684 currentCtx
= menuStack
[--menuStackIdx
];
686 cmsMenuCountPage(pDisplay
);
687 cmsPageSelect(pDisplay
, currentCtx
.page
);
694 STATIC_UNIT_TESTED
void cmsMenuOpen(void)
698 pCurrentDisplay
= cmsDisplayPortSelectCurrent();
699 if (!pCurrentDisplay
)
702 currentCtx
= (cmsCtx_t
){ &menuMain
, 0, 0 };
703 ENABLE_ARMING_FLAG(ARMING_DISABLED_CMS_MENU
);
706 displayPort_t
*pNextDisplay
= cmsDisplayPortSelectNext();
707 if (pNextDisplay
!= pCurrentDisplay
) {
708 // DisplayPort has been changed.
709 // Convert cursorRow to absolute value
710 currentCtx
.cursorRow
= cmsCursorAbsolute(pCurrentDisplay
);
711 displayRelease(pCurrentDisplay
);
712 pCurrentDisplay
= pNextDisplay
;
717 displayGrab(pCurrentDisplay
); // grab the display for use by the CMS
718 cmsMenuChange(pCurrentDisplay
, currentCtx
.menu
, NULL
);
721 static void cmsTraverseGlobalExit(const CMS_Menu
*pMenu
)
723 for (const OSD_Entry
*p
= pMenu
->entries
; p
->type
!= OME_END
; p
++) {
724 if (p
->type
== OME_Submenu
) {
725 cmsTraverseGlobalExit(p
->data
);
729 if (pMenu
->onGlobalExit
) {
730 pMenu
->onGlobalExit(NULL
);
734 long cmsMenuExit(displayPort_t
*pDisplay
, const void *ptr
)
736 int exitType
= (int)ptr
;
739 case CMS_EXIT_SAVEREBOOT
:
741 cmsTraverseGlobalExit(&menuMain
);
743 if (currentCtx
.menu
->onExit
)
744 currentCtx
.menu
->onExit((OSD_Entry
*)NULL
); // Forced exit
746 saveConfigAndNotify();
755 displayRelease(pDisplay
);
756 currentCtx
.menu
= NULL
;
758 if (exitType
== CMS_EXIT_SAVEREBOOT
) {
759 displayClearScreen(pDisplay
);
760 displayWrite(pDisplay
, 5, 3, "REBOOTING...");
762 displayResync(pDisplay
); // Was max7456RefreshAll(); why at this timing?
771 DISABLE_ARMING_FLAG(ARMING_DISABLED_CMS_MENU
);
776 void cmsYieldDisplay(displayPort_t
*pPort
, timeMs_t duration
)
778 cmsYieldUntil
= millis() + duration
;
779 displayRelease(pPort
);
782 // Stick/key detection and key codes
784 #define IS_HI(X) (rcData[X] > 1750)
785 #define IS_LO(X) (rcData[X] < 1250)
786 #define IS_MID(X) (rcData[X] > 1250 && rcData[X] < 1750)
796 #define BUTTON_TIME 250 // msec
797 #define BUTTON_PAUSE 500 // msec
799 STATIC_UNIT_TESTED
uint16_t cmsHandleKey(displayPort_t
*pDisplay
, uint8_t key
)
801 uint16_t res
= BUTTON_TIME
;
804 if (!currentCtx
.menu
)
807 if (key
== KEY_MENU
) {
812 if (key
== KEY_ESC
) {
813 cmsMenuBack(pDisplay
);
817 if (key
== KEY_DOWN
) {
818 if (currentCtx
.cursorRow
< pageMaxRow
) {
819 currentCtx
.cursorRow
++;
821 cmsPageNext(pDisplay
);
822 currentCtx
.cursorRow
= 0; // Goto top in any case
827 currentCtx
.cursorRow
--;
829 // Skip non-title labels
830 if (cmsElementIsLabel((pageTop
+ currentCtx
.cursorRow
)->type
) && currentCtx
.cursorRow
> 0)
831 currentCtx
.cursorRow
--;
833 if (currentCtx
.cursorRow
== -1 || cmsElementIsLabel((pageTop
+ currentCtx
.cursorRow
)->type
)) {
834 // Goto previous page
835 cmsPagePrev(pDisplay
);
836 currentCtx
.cursorRow
= pageMaxRow
;
840 if (key
== KEY_DOWN
|| key
== KEY_UP
)
843 p
= pageTop
+ currentCtx
.cursorRow
;
847 if (key
== KEY_RIGHT
) {
848 cmsMenuChange(pDisplay
, p
->data
, p
);
854 if (p
->func
&& key
== KEY_RIGHT
) {
855 long retval
= p
->func(pDisplay
, p
->data
);
856 if (retval
== MENU_CHAIN_BACK
)
857 cmsMenuBack(pDisplay
);
863 if (p
->func
&& key
== KEY_RIGHT
) {
864 p
->func(pDisplay
, p
->data
);
870 cmsMenuBack(pDisplay
);
876 uint8_t *val
= (uint8_t *)p
->data
;
877 if (key
== KEY_RIGHT
)
881 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
883 p
->func(pDisplay
, p
);
890 bool (*func
)(bool *arg
) = p
->data
;
891 bool val
= key
== KEY_RIGHT
;
893 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
899 if (IS_READONLY(p
)) {
903 const OSD_UINT8_t
*ptr
= p
->data
;
904 if (key
== KEY_RIGHT
) {
905 if (*ptr
->val
< ptr
->max
)
906 *ptr
->val
+= ptr
->step
;
909 if (*ptr
->val
> ptr
->min
)
910 *ptr
->val
-= ptr
->step
;
912 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
914 p
->func(pDisplay
, p
);
920 if (p
->type
== OME_TAB
) {
921 const OSD_TAB_t
*ptr
= p
->data
;
923 if (key
== KEY_RIGHT
) {
924 if (*ptr
->val
< ptr
->max
)
932 p
->func(pDisplay
, p
->data
);
933 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
938 if (IS_READONLY(p
)) {
942 const OSD_INT8_t
*ptr
= p
->data
;
943 if (key
== KEY_RIGHT
) {
944 if (*ptr
->val
< ptr
->max
)
945 *ptr
->val
+= ptr
->step
;
948 if (*ptr
->val
> ptr
->min
)
949 *ptr
->val
-= ptr
->step
;
951 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
953 p
->func(pDisplay
, p
);
959 if (IS_READONLY(p
)) {
963 const OSD_UINT16_t
*ptr
= p
->data
;
964 if (key
== KEY_RIGHT
) {
965 if (*ptr
->val
< ptr
->max
)
966 *ptr
->val
+= ptr
->step
;
969 if (*ptr
->val
> ptr
->min
)
970 *ptr
->val
-= ptr
->step
;
972 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
974 p
->func(pDisplay
, p
);
980 if (IS_READONLY(p
)) {
984 const OSD_INT16_t
*ptr
= p
->data
;
985 if (key
== KEY_RIGHT
) {
986 if (*ptr
->val
< ptr
->max
)
987 *ptr
->val
+= ptr
->step
;
990 if (*ptr
->val
> ptr
->min
)
991 *ptr
->val
-= ptr
->step
;
993 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
995 p
->func(pDisplay
, p
);
1002 const OSD_SETTING_t
*ptr
= p
->data
;
1003 const setting_t
*var
= &settingsTable
[ptr
->val
];
1004 setting_min_t min
= setting_get_min(var
);
1005 setting_max_t max
= setting_get_max(var
);
1006 float step
= ptr
->step
?: 1;
1007 if (key
!= KEY_RIGHT
) {
1010 const void *valuePointer
= setting_get_value_pointer(var
);
1011 switch (SETTING_TYPE(var
)) {
1014 uint8_t val
= *(uint8_t *)valuePointer
;
1015 val
= MIN(MAX(val
+ step
, (uint8_t)min
), (uint8_t)max
);
1016 *(uint8_t *)valuePointer
= val
;
1021 int8_t val
= *(int8_t *)valuePointer
;
1022 val
= MIN(MAX(val
+ step
, (int8_t)min
), (int8_t)max
);
1023 *(int8_t *)valuePointer
= val
;
1028 uint16_t val
= *(uint16_t *)valuePointer
;
1029 val
= MIN(MAX(val
+ step
, (uint16_t)min
), (uint16_t)max
);
1030 *(uint16_t *)valuePointer
= val
;
1035 int16_t val
= *(int16_t *)valuePointer
;
1036 val
= MIN(MAX(val
+ step
, (int16_t)min
), (int16_t)max
);
1037 *(int16_t *)valuePointer
= val
;
1042 uint32_t val
= *(uint32_t *)valuePointer
;
1043 val
= MIN(MAX(val
+ step
, (uint32_t)min
), (uint32_t)max
);
1044 *(uint32_t *)valuePointer
= val
;
1049 float val
= *(float *)valuePointer
;
1050 val
= MIN(MAX(val
+ step
, (float)min
), (float)max
);
1051 *(float *)valuePointer
= val
;
1056 SET_PRINTVALUE(p
, currentCtx
.cursorRow
);
1058 p
->func(pDisplay
, p
);
1078 uint16_t cmsHandleKeyWithRepeat(displayPort_t
*pDisplay
, uint8_t key
, int repeatCount
)
1082 for (int i
= 0 ; i
< repeatCount
; i
++) {
1083 ret
= cmsHandleKey(pDisplay
, key
);
1089 static uint16_t cmsScanKeys(timeMs_t currentTimeMs
, timeMs_t lastCalledMs
, int16_t rcDelayMs
)
1091 static int holdCount
= 1;
1092 static int repeatCount
= 1;
1093 static int repeatBase
= 0;
1099 uint8_t key
= KEY_NONE
;
1101 if (IS_MID(THROTTLE
) && IS_LO(YAW
) && IS_HI(PITCH
) && !ARMING_FLAG(ARMED
)) {
1104 else if (IS_HI(PITCH
)) {
1107 else if (IS_LO(PITCH
)) {
1110 else if (IS_LO(ROLL
)) {
1113 else if (IS_HI(ROLL
)) {
1116 else if (IS_HI(YAW
) || IS_LO(YAW
))
1121 if (key
== KEY_NONE
) {
1122 // No 'key' pressed, reset repeat control
1127 // The 'key' is being pressed; keep counting
1131 if (rcDelayMs
> 0) {
1132 rcDelayMs
-= (currentTimeMs
- lastCalledMs
);
1134 rcDelayMs
= cmsHandleKeyWithRepeat(pCurrentDisplay
, key
, repeatCount
);
1136 // Key repeat effect is implemented in two phases.
1137 // First phldase is to decrease rcDelayMs reciprocal to hold time.
1138 // When rcDelayMs reached a certain limit (scheduling interval),
1139 // repeat rate will not raise anymore, so we call key handler
1140 // multiple times (repeatCount).
1142 // XXX Caveat: Most constants are adjusted pragmatically.
1143 // XXX Rewrite this someday, so it uses actual hold time instead
1144 // of holdCount, which depends on the scheduling interval.
1146 if (((key
== KEY_LEFT
) || (key
== KEY_RIGHT
)) && (holdCount
> 20)) {
1148 // Decrease rcDelayMs reciprocally
1150 rcDelayMs
/= (holdCount
- 20);
1152 // When we reach the scheduling limit,
1154 if (rcDelayMs
<= 50) {
1156 // start calling handler multiple times.
1158 if (repeatBase
== 0)
1159 repeatBase
= holdCount
;
1161 if (holdCount
< 100) {
1162 repeatCount
= repeatCount
+ (holdCount
- repeatBase
) / 5;
1164 if (repeatCount
> 5) {
1168 repeatCount
= repeatCount
+ holdCount
- repeatBase
;
1170 if (repeatCount
> 50) {
1180 void cmsUpdate(uint32_t currentTimeUs
)
1183 if(rcdeviceInMenu
) {
1188 static int16_t rcDelayMs
= BUTTON_TIME
;
1190 static timeMs_t lastCalledMs
= 0;
1191 static uint32_t lastCmsHeartBeatMs
= 0;
1193 const timeMs_t currentTimeMs
= currentTimeUs
/ 1000;
1196 // Detect menu invocation
1197 if (IS_MID(THROTTLE
) && IS_LO(YAW
) && IS_HI(PITCH
) && !ARMING_FLAG(ARMED
)) {
1199 rcDelayMs
= BUTTON_PAUSE
; // Tends to overshoot if BUTTON_TIME
1203 // Check if we're yielding and its's time to stop it
1204 if (cmsYieldUntil
> 0 && currentTimeMs
> cmsYieldUntil
) {
1206 displayGrab(pCurrentDisplay
);
1207 displayClearScreen(pCurrentDisplay
);
1210 // Only scan keys and draw if we're not yielding
1211 if (cmsYieldUntil
== 0) {
1212 rcDelayMs
= cmsScanKeys(currentTimeMs
, lastCalledMs
, rcDelayMs
);
1213 // Check again, the keypress might have produced a yield
1214 if (cmsYieldUntil
== 0) {
1215 cmsDrawMenu(pCurrentDisplay
, currentTimeUs
);
1219 if (currentTimeMs
> lastCmsHeartBeatMs
+ 500) {
1220 // Heart beat for external CMS display device @ 500msec
1221 // (Timeout @ 1000msec)
1222 displayHeartbeat(pCurrentDisplay
);
1223 lastCmsHeartBeatMs
= currentTimeMs
;
1227 // Some key (command), notably flash erase, takes too long to use the
1228 // currentTimeMs to be used as lastCalledMs (freezes CMS for a minute or so
1230 lastCalledMs
= millis();
1233 void cmsHandler(timeUs_t currentTimeUs
)
1235 if (cmsDeviceCount
< 0)
1238 static timeUs_t lastCalledUs
= 0;
1240 if (currentTimeUs
>= lastCalledUs
+ CMS_UPDATE_INTERVAL_US
) {
1241 lastCalledUs
= currentTimeUs
;
1242 cmsUpdate(currentTimeUs
);
1249 cmsCurrentDevice
= -1;