5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
23 #define BIGSIZE MIDSIZE
24 #define LBOX_CENTERX (BOX_WIDTH/2 + 16)
25 #define RBOX_CENTERX (LCD_W-LBOX_CENTERX-1)
26 #define MODELNAME_X (15)
27 #define MODELNAME_Y (11)
28 #define VBATT_X (MODELNAME_X+26)
29 #define VBATT_Y (FH+3)
30 #define VBATTUNIT_X (VBATT_X-2)
31 #define VBATTUNIT_Y VBATT_Y
32 #define BITMAP_X ((LCD_W-64)/2)
33 #define BITMAP_Y (LCD_H/2)
34 #define PHASE_X BITMAP_X
35 #define PHASE_Y (3*FH)
36 #define PHASE_FLAGS (0)
41 #define REBOOT_X (LCD_W-FW)
42 #define VSWITCH_X(i) (((i>=MAX_LOGICAL_SWITCHES*3/4) ? BITMAP_X+28 : ((i>=MAX_LOGICAL_SWITCHES/2) ? BITMAP_X+25 : ((i>=MAX_LOGICAL_SWITCHES/4) ? 21 : 18))) + 3*i)
43 #define VSWITCH_Y (LCD_H-9)
44 #define TRIM_LH_X (32+9)
46 #define TRIM_RV_X (LCD_W-11)
47 #define TRIM_RH_X (LCD_W-32-9)
50 #define MARKER_WIDTH 5
52 const pm_uchar logo_taranis
[] PROGMEM
= {
56 const pm_uchar icons
[] PROGMEM
= {
60 #define ICON_RSSI 0, 9
61 #define ICON_SPEAKER0 9, 8
62 #define ICON_SPEAKER1 17, 8
63 #define ICON_SPEAKER2 25, 8
64 #define ICON_SPEAKER3 33, 8
65 #define ICON_SD 41, 11
66 #define ICON_LOGS 51, 11
67 #define ICON_TRAINER 61, 11
68 #define ICON_TRAINEE 71, 11
69 #define ICON_USB 81, 11
70 #define ICON_REBOOT 91, 11
71 #define ICON_ALTITUDE 102, 9
73 void doMainScreenGraphics()
75 int16_t calibStickVert
= calibratedAnalogs
[CONVERT_MODE(1)];
76 if (g_model
.throttleReversed
&& CONVERT_MODE(1) == THR_STICK
)
77 calibStickVert
= -calibStickVert
;
78 drawStick(LBOX_CENTERX
, calibratedAnalogs
[CONVERT_MODE(0)], calibStickVert
);
80 calibStickVert
= calibratedAnalogs
[CONVERT_MODE(2)];
81 if (g_model
.throttleReversed
&& CONVERT_MODE(2) == THR_STICK
)
82 calibStickVert
= -calibStickVert
;
83 drawStick(RBOX_CENTERX
, calibratedAnalogs
[CONVERT_MODE(3)], calibStickVert
);
86 void displayTrims(uint8_t phase
)
88 for (unsigned int i
=0; i
<NUM_STICKS
; i
++) {
89 coord_t x
[4] = { TRIM_LH_X
, TRIM_LV_X
, TRIM_RV_X
, TRIM_RH_X
};
90 uint8_t vert
[4] = { 0, 1, 1, 0 };
92 unsigned int stickIndex
= CONVERT_MODE(i
);
96 int32_t trim
= getTrimValue(phase
, i
);
99 if (val
< TRIM_MIN
|| val
> TRIM_MAX
) {
102 if (val
< -(TRIM_LEN
+1)*4) {
105 else if (val
> (TRIM_LEN
+1)*4) {
114 lcdDrawSolidVerticalLine(xm
, ym
-TRIM_LEN
, TRIM_LEN
*2);
115 if (i
!=2 || !g_model
.thrTrim
) {
116 lcdDrawSolidVerticalLine(xm
-1, ym
-1, 3);
117 lcdDrawSolidVerticalLine(xm
+1, ym
-1, 3);
120 lcdDrawFilledRect(xm
-3, ym
-3, 7, 7, SOLID
, att
|ERASE
);
122 lcdDrawSolidHorizontalLine(xm
-1, ym
-1, 3);
125 lcdDrawSolidHorizontalLine(xm
-1, ym
+1, 3);
128 lcdDrawSolidHorizontalLine(xm
-1, ym
, 3);
130 if (g_model
.displayTrims
!= DISPLAY_TRIMS_NEVER
&& trim
!= 0) {
131 if (g_model
.displayTrims
== DISPLAY_TRIMS_ALWAYS
|| (trimsDisplayTimer
> 0 && (trimsDisplayMask
& (1<<i
)))) {
132 lcdDrawNumber(trim
>0 ? 22 : 54, xm
-2, -abs(trim
), RIGHT
|TINSIZE
|VERTICAL
);
138 lcdDrawSolidHorizontalLine(xm
-TRIM_LEN
, ym
, TRIM_LEN
*2);
139 lcdDrawSolidHorizontalLine(xm
-1, ym
-1, 3);
140 lcdDrawSolidHorizontalLine(xm
-1, ym
+1, 3);
142 lcdDrawFilledRect(xm
-3, ym
-3, 7, 7, SOLID
, att
|ERASE
);
144 lcdDrawSolidVerticalLine(xm
+1, ym
-1, 3);
147 lcdDrawSolidVerticalLine(xm
-1, ym
-1, 3);
150 lcdDrawSolidVerticalLine(xm
, ym
-1, 3);
152 if (g_model
.displayTrims
!= DISPLAY_TRIMS_NEVER
&& trim
!= 0) {
153 if (g_model
.displayTrims
== DISPLAY_TRIMS_ALWAYS
|| (trimsDisplayTimer
> 0 && (trimsDisplayMask
& (1<<i
)))) {
154 lcdDrawNumber((stickIndex
==0 ? TRIM_LH_X
: TRIM_RH_X
)+(trim
>0 ? -11 : 20), ym
-2, -abs(trim
), RIGHT
|TINSIZE
);
158 lcdDrawSquare(xm
-3, ym
-3, 7, att
);
164 for (uint8_t i
=NUM_STICKS
; i
<NUM_STICKS
+NUM_POTS
+NUM_SLIDERS
; i
++) {
166 if (i
< SLIDER1
) continue; // TODO change and display more values
167 coord_t x
= ((i
==SLIDER1
|| i
==SLIDER3
) ? 3 : LCD_W
-5);
168 int8_t y
= (i
<SLIDER3
? LCD_H
/2+1 : 1);
170 if (i
== POT3
) continue;
171 coord_t x
= ((i
==POT1
|| i
==SLIDER1
) ? 3 : LCD_W
-5);
172 int8_t y
= (i
>=SLIDER1
? LCD_H
/2+1 : 1);
174 lcdDrawSolidVerticalLine(x
, y
, LCD_H
/2-2);
175 lcdDrawSolidVerticalLine(x
+1, y
, LCD_H
/2-2);
177 y
-= ((calibratedAnalogs
[i
]+RESX
)*(LCD_H
/2-4)/(RESX
*2)); // calculate once per loop
178 lcdDrawSolidVerticalLine(x
-1, y
, 2);
179 lcdDrawSolidVerticalLine(x
+2, y
, 2);
187 #define BAR_NOTIFS_X BAR_X+133
188 #define BAR_VOLUME_X BAR_X+147
189 #define BAR_TIME_X BAR_X+159
191 void displayTopBarGauge(coord_t x
, int count
, bool blinking
=false)
193 if (!blinking
|| BLINK_ON_PHASE
)
194 lcdDrawFilledRect(x
+1, BAR_Y
+2, 11, 5, SOLID
, ERASE
);
195 for (int i
=0; i
<count
; i
+=2)
196 lcdDrawSolidVerticalLine(x
+2+i
, BAR_Y
+3, 3);
199 #define LCD_NOTIF_ICON(x, icon) \
200 lcdDrawBitmap(x, BAR_Y, icons, icon); \
201 lcdDrawSolidHorizontalLine(x, BAR_Y+8, 11)
206 uint8_t altitude_icon_x
;
209 putsVBat(BAR_X
+2, BAR_Y
+1, LEFT
);
210 batt_icon_x
= lcdLastRightPos
;
211 lcdDrawRect(batt_icon_x
+FW
, BAR_Y
+1, 13, 7);
212 lcdDrawSolidVerticalLine(batt_icon_x
+FW
+13, BAR_Y
+2, 5);
214 if (TELEMETRY_STREAMING()) {
216 LCD_ICON(batt_icon_x
+3*FW
+3, BAR_Y
, ICON_RSSI
);
217 lcdDrawRect(batt_icon_x
+5*FW
, BAR_Y
+1, 13, 7);
220 altitude_icon_x
= batt_icon_x
+7*FW
+3;
221 if (g_model
.frsky
.voltsSource
) {
222 uint8_t item
= g_model
.frsky
.voltsSource
-1;
223 if (item
< MAX_TELEMETRY_SENSORS
) {
224 TelemetryItem
& voltsItem
= telemetryItems
[item
];
225 if (voltsItem
.isAvailable()) {
226 drawSensorCustomValue(batt_icon_x
+7*FW
+2, BAR_Y
+1, item
, voltsItem
.value
, LEFT
);
227 altitude_icon_x
= lcdLastRightPos
+1;
233 if (g_model
.frsky
.altitudeSource
) {
234 uint8_t item
= g_model
.frsky
.altitudeSource
-1;
235 if (item
< MAX_TELEMETRY_SENSORS
) {
236 TelemetryItem
& altitudeItem
= telemetryItems
[item
];
237 if (altitudeItem
.isAvailable()) {
238 LCD_ICON(altitude_icon_x
, BAR_Y
, ICON_ALTITUDE
);
239 int32_t value
= altitudeItem
.value
/ g_model
.telemetrySensors
[item
].getPrecDivisor();
240 drawValueWithUnit(altitude_icon_x
+2*FW
-1, BAR_Y
+1, value
, g_model
.telemetrySensors
[item
].unit
, LEFT
);
247 coord_t x
= BAR_NOTIFS_X
;
248 #if defined(LOG_TELEMETRY) || defined(WATCHDOG_DISABLED)
249 LCD_NOTIF_ICON(x
, ICON_REBOOT
);
252 if (unexpectedShutdown
) {
253 LCD_NOTIF_ICON(x
, ICON_REBOOT
);
259 LCD_NOTIF_ICON(x
, ICON_USB
);
264 if (TRAINER_CONNECTED()) {
265 LCD_NOTIF_ICON(x
, ICON_TRAINEE
);
269 else if (IS_TRAINER_INPUT_VALID()) {
270 LCD_NOTIF_ICON(x
, ICON_TRAINER
);
274 if (isFunctionActive(FUNCTION_LOGS
)) {
275 LCD_NOTIF_ICON(x
, ICON_LOGS
);
280 if (requiredSpeakerVolume
== 0 || g_eeGeneral
.beepMode
== e_mode_quiet
)
281 LCD_ICON(BAR_VOLUME_X
, BAR_Y
, ICON_SPEAKER0
);
282 else if (requiredSpeakerVolume
<= 6)
283 LCD_ICON(BAR_VOLUME_X
, BAR_Y
, ICON_SPEAKER1
);
284 else if (requiredSpeakerVolume
<= 12)
285 LCD_ICON(BAR_VOLUME_X
, BAR_Y
, ICON_SPEAKER2
);
286 else if (requiredSpeakerVolume
<= 18)
287 LCD_ICON(BAR_VOLUME_X
, BAR_Y
, ICON_SPEAKER2
);
289 LCD_ICON(BAR_VOLUME_X
, BAR_Y
, ICON_SPEAKER3
);
292 drawRtcTime(BAR_TIME_X
, BAR_Y
+1, LEFT
|TIMEBLINK
);
295 lcdDrawFilledRect(BAR_X
, BAR_Y
, BAR_W
, BAR_H
, SOLID
, FILL_WHITE
|GREY(12)|ROUND
);
297 /* The inside of the Batt gauge */
298 displayTopBarGauge(batt_icon_x
+FW
, GET_TXBATT_BARS(), IS_TXBATT_WARNING());
300 /* The inside of the RSSI gauge */
301 if (TELEMETRY_RSSI() > 0) {
302 displayTopBarGauge(batt_icon_x
+5*FW
, TELEMETRY_RSSI() / 10, TELEMETRY_RSSI() < g_model
.rssiAlarms
.getWarningRssi());
308 // Main and Second timer
309 for (unsigned int i
=0; i
<2; i
++) {
310 if (g_model
.timers
[i
].mode
) {
311 TimerState
& timerState
= timersStates
[i
];
312 TimerData
& timerData
= g_model
.timers
[i
];
313 uint8_t y
= TIMERS_Y
+ i
*TIMERS_H
;
314 if (ZLEN(timerData
.name
) > 0) {
315 lcdDrawSizedText(TIMERS_X
, y
-7, timerData
.name
, LEN_TIMER_NAME
, ZCHAR
|SMLSIZE
);
318 drawTimerMode(TIMERS_X
, y
-7, timerData
.mode
, SMLSIZE
);
320 drawTimer(TIMERS_X
, y
, timerState
.val
, TIMEHOUR
|MIDSIZE
|LEFT
, TIMEHOUR
|MIDSIZE
|LEFT
);
321 if (timerData
.persistent
) {
322 lcdDrawChar(TIMERS_R
, y
+1, 'P', SMLSIZE
);
324 if (timerState
.val
< 0) {
325 if (BLINK_ON_PHASE
) {
326 lcdDrawFilledRect(TIMERS_X
-7, y
-8, 60, 20);
333 void menuMainViewChannelsMonitor(event_t event
)
336 case EVT_KEY_BREAK(KEY_PAGE
):
337 case EVT_KEY_BREAK(KEY_EXIT
):
338 chainMenu(menuMainView
);
343 return menuChannelsView(event
);
346 void onMainViewMenu(const char *result
)
348 if (result
== STR_RESET_TIMER1
) {
351 else if (result
== STR_RESET_TIMER2
) {
355 else if (result
== STR_RESET_TIMER3
) {
359 else if (result
== STR_VIEW_NOTES
) {
362 else if (result
== STR_RESET_SUBMENU
) {
363 POPUP_MENU_ADD_ITEM(STR_RESET_FLIGHT
);
364 POPUP_MENU_ADD_ITEM(STR_RESET_TIMER1
);
365 POPUP_MENU_ADD_ITEM(STR_RESET_TIMER2
);
366 POPUP_MENU_ADD_ITEM(STR_RESET_TIMER3
);
367 POPUP_MENU_ADD_ITEM(STR_RESET_TELEMETRY
);
369 else if (result
== STR_RESET_TELEMETRY
) {
372 else if (result
== STR_RESET_FLIGHT
) {
375 else if (result
== STR_STATISTICS
) {
376 chainMenu(menuStatisticsView
);
378 else if (result
== STR_ABOUT_US
) {
379 chainMenu(menuAboutView
);
383 void displaySwitch(coord_t x
, coord_t y
, int width
, unsigned int index
)
385 if (SWITCH_EXISTS(index
)) {
386 int val
= getValue(MIXSRC_FIRST_SWITCH
+index
);
389 lcdDrawSolidHorizontalLine(x
, y
, width
);
390 lcdDrawSolidHorizontalLine(x
, y
+2, width
);
393 lcdDrawSolidHorizontalLine(x
, y
, width
);
394 lcdDrawSolidHorizontalLine(x
, y
+2, width
);
399 lcdDrawChar(width
==5 ? x
+1 : x
, y
, 'A'+index
, TINSIZE
);
403 lcdDrawSolidHorizontalLine(x
, y
, width
);
404 lcdDrawSolidHorizontalLine(x
, y
+2, width
);
406 lcdDrawSolidHorizontalLine(x
, y
+4, width
);
407 lcdDrawSolidHorizontalLine(x
, y
+6, width
);
413 bool isMenuAvailable(int index
)
416 return modelHasNotes();
426 for (int i
=0; i
<NUM_SWITCHES
; ++i
) {
427 if (SWITCH_EXISTS(i
)) {
434 void menuMainView(event_t event
)
436 static bool secondPage
= false;
438 STICK_SCROLL_DISABLE();
443 killEvents(KEY_EXIT
);
445 killEvents(KEY_DOWN
);
452 case EVT_KEY_LONG(KEY_ENTER
):
454 if (modelHasNotes()) {
455 POPUP_MENU_ADD_ITEM(STR_VIEW_NOTES
);
457 POPUP_MENU_ADD_ITEM(STR_RESET_SUBMENU
);
458 POPUP_MENU_ADD_ITEM(STR_STATISTICS
);
459 POPUP_MENU_ADD_ITEM(STR_ABOUT_US
);
460 POPUP_MENU_START(onMainViewMenu
);
463 #if MENUS_LOCK != 2/*no menus*/
464 case EVT_KEY_BREAK(KEY_MENU
):
465 pushMenu(menuModelSelect
);
468 case EVT_KEY_LONG(KEY_MENU
):
469 pushMenu(menuRadioSetup
);
474 case EVT_KEY_BREAK(KEY_PAGE
):
475 storageDirty(EE_MODEL
);
477 if (g_model
.view
>= VIEW_COUNT
) {
479 chainMenu(menuMainViewChannelsMonitor
);
483 case EVT_KEY_LONG(KEY_PAGE
):
484 if (!IS_FAI_ENABLED())
485 chainMenu(menuViewTelemetryFrsky
);
489 case EVT_KEY_FIRST(KEY_EXIT
):
491 if (gvarDisplayTimer
> 0) {
492 gvarDisplayTimer
= 0;
497 case EVT_KEY_FIRST(KEY_RIGHT
):
498 case EVT_KEY_FIRST(KEY_LEFT
):
499 #if defined(ROTARY_ENCODER_NAVIGATION)
500 case EVT_ROTARY_LEFT
:
501 case EVT_ROTARY_RIGHT
:
503 secondPage
= !secondPage
;
508 int mode
= mixerCurrentFlightMode
;
509 lcdDrawSizedText(PHASE_X
, PHASE_Y
, g_model
.flightModeData
[mode
].name
, sizeof(g_model
.flightModeData
[mode
].name
), ZCHAR
|PHASE_FLAGS
);
512 putsModelName(MODELNAME_X
, MODELNAME_Y
, g_model
.header
.name
, g_eeGeneral
.currModel
, BIGSIZE
);
520 // Sliders (Pots / Sliders)
523 lcdDrawBitmap(BITMAP_X
, BITMAP_Y
, modelBitmap
);
526 if (getSwitchCount() > 8) {
527 for (int i
=0; i
<NUM_SWITCHES
; ++i
) {
528 div_t qr
= div(i
, 9);
529 if (g_model
.view
== VIEW_INPUTS
) {
530 div_t qr2
= div(qr
.rem
, 5);
531 if (i
>= 14) qr2
.rem
+= 1;
532 const coord_t x
[4] = { 50, 142 };
533 const coord_t y
[4] = { 25, 42, 25, 42 };
534 displaySwitch(x
[qr
.quot
]+qr2
.rem
*4, y
[qr2
.quot
], 3, i
);
537 displaySwitch(17+qr
.rem
*6, 25+qr
.quot
*17, 5, i
);
543 for (int i
=0; i
<NUM_SWITCHES
; ++i
) {
544 if (SWITCH_EXISTS(i
)) {
545 getvalue_t val
= getValue(MIXSRC_FIRST_SWITCH
+i
);
546 getvalue_t sw
= ((val
< 0) ? 3*i
+1 : ((val
== 0) ? 3*i
+2 : 3*i
+3));
547 drawSwitch((g_model
.view
== VIEW_INPUTS
) ? (index
<4 ? 8*FW
+1 : 23*FW
+2) : (index
<4 ? 3*FW
+1 : 8*FW
-2), (index
%4)*FH
+3*FH
, sw
, 0);
553 if (g_model
.view
== VIEW_TIMERS
) {
556 else if (g_model
.view
== VIEW_INPUTS
) {
558 doMainScreenGraphics();
562 int sw
= (secondPage
&& MAX_LOGICAL_SWITCHES
> 32 ? 32 : 0);
563 const int end
= sw
+ 32;
565 lcdDrawText(TRIM_RH_X
- TRIM_LEN
/2 + 1, y
, "LS");
566 lcdDrawNumber(lcdLastRightPos
+ 1, y
, sw
+ 1, LEFT
|LEADING0
, 2);
567 lcdDrawText(lcdLastRightPos
, y
, "-");
568 lcdDrawNumber(lcdLastRightPos
, y
, end
, LEFT
);
569 for ( ; sw
< end
; ++sw
) {
570 const div_t qr
= div(sw
+ 32 - end
, 10);
571 const uint8_t x
= TRIM_RH_X
- TRIM_LEN
+ qr
.rem
*5 + (qr
.rem
>= 5 ? 3 : 0);
572 y
= 13 + 11 * qr
.quot
;
573 LogicalSwitchData
* cs
= lswAddress(sw
);
574 if (cs
->func
== LS_FUNC_NONE
) {
575 lcdDrawSolidHorizontalLine(x
, y
+6, 4);
576 lcdDrawSolidHorizontalLine(x
, y
+7, 4);
578 else if (getSwitch(SWSRC_SW1
+sw
)) {
579 lcdDrawFilledRect(x
, y
, 4, 8);
582 lcdDrawRect(x
, y
, 4, 8);
588 if (gvarDisplayTimer
> 0) {
590 lcdDrawFilledRect(BITMAP_X
, BITMAP_Y
, 64, 32, SOLID
, ERASE
);
591 lcdDrawRect(BITMAP_X
, BITMAP_Y
, 64, 32);
592 drawStringWithIndex(BITMAP_X
+FW
, BITMAP_Y
+FH
-1, STR_GV
, gvarLastChanged
+1);
593 lcdDrawSizedText(BITMAP_X
+4*FW
+FW
/2, BITMAP_Y
+FH
-1, g_model
.gvars
[gvarLastChanged
].name
, LEN_GVAR_NAME
, ZCHAR
);
594 lcdDrawText(BITMAP_X
+FW
, BITMAP_Y
+2*FH
+3, PSTR("["), BOLD
);
595 drawGVarValue(BITMAP_X
+2*FW
, BITMAP_Y
+2*FH
+3, gvarLastChanged
, GVAR_VALUE(gvarLastChanged
, getGVarFlightMode(mixerCurrentFlightMode
, gvarLastChanged
)), LEFT
|BOLD
);
596 lcdDrawText(lcdLastRightPos
, BITMAP_Y
+2*FH
+3, PSTR("]"), BOLD
);