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/>.
27 #include "build/version.h"
28 #include "build/build_config.h"
30 #include "drivers/time.h"
31 #include "drivers/display_ug2864hsweg01.h"
35 #include "common/printf.h"
36 #include "common/maths.h"
37 #include "common/axis.h"
38 #include "common/typeconversion.h"
40 #include "fc/config.h"
41 #include "fc/controlrate_profile.h"
42 #include "fc/runtime_config.h"
43 #include "fc/rc_controls.h"
45 #include "flight/pid.h"
46 #include "flight/imu.h"
47 #include "flight/failsafe.h"
49 #include "io/dashboard.h"
50 #include "io/displayport_oled.h"
56 #include "navigation/navigation.h"
60 #include "sensors/battery.h"
61 #include "sensors/sensors.h"
62 #include "sensors/compass.h"
63 #include "sensors/acceleration.h"
64 #include "sensors/boardalignment.h"
65 #include "sensors/gyro.h"
66 #include "sensors/barometer.h"
68 #include "config/feature.h"
71 controlRateConfig_t
*getControlRateConfig(uint8_t profileIndex
);
73 #define MICROSECONDS_IN_A_SECOND (1000 * 1000)
75 #define DASHBOARD_UPDATE_FREQUENCY (MICROSECONDS_IN_A_SECOND / 5)
76 #define PAGE_CYCLE_FREQUENCY (MICROSECONDS_IN_A_SECOND * 5)
78 static timeUs_t nextDisplayUpdateAt
= 0;
79 static bool displayPresent
= false;
81 static displayPort_t
*displayPort
;
83 #define PAGE_TITLE_LINE_COUNT 1
85 static char lineBuffer
[SCREEN_CHARACTER_COLUMN_COUNT
+ 1];
87 #define HALF_SCREEN_CHARACTER_COLUMN_COUNT (SCREEN_CHARACTER_COLUMN_COUNT / 2)
88 #define IS_SCREEN_CHARACTER_COLUMN_COUNT_ODD (SCREEN_CHARACTER_COLUMN_COUNT & 1)
90 #if defined(DASHBOARD_ARMED_BITMAP)
91 static uint8_t armedBitmapRLE
[] = { 128, 64,
92 '\x00','\x00','\x04','\x80','\x80','\x03','\xc0','\xc0', // 0x0008
93 '\x02','\x80','\x80','\x03','\x00','\x00','\x0b','\x80', // 0x0010
94 '\x80','\x02','\xc0','\xc0','\x02','\x80','\x80','\x03', // 0x0018
95 '\x00','\x00','\x63','\xfc','\xfe','\xdf','\x03','\x01', // 0x0020
96 '\x71','\x79','\xf9','\xf1','\xc1','\x83','\x8f','\xfe', // 0x0028
97 '\xfc','\x00','\x00','\x04','\xf8','\xfe','\xff','\x07', // 0x0030
98 '\x83','\xf1','\xf1','\x02','\x79','\x71','\x21','\x03', // 0x0038
99 '\x87','\xfe','\xfc','\x70','\x00','\x00','\x07','\xff', // 0x0040
100 '\xff','\x03','\x00','\x00','\x05','\xff','\xff','\x03', // 0x0048
101 '\x3f','\x7c','\xf8','\xf0','\xc0','\x80','\x00','\x00', // 0x0050
102 '\x0b','\xfe','\xff','\xff','\x03','\x00','\x00','\x0c', // 0x0058
103 '\xc0','\xf0','\xfe','\x7f','\x0f','\x7f','\xfe','\xf8', // 0x0060
104 '\xc0','\x00','\x00','\x06','\x07','\x1f','\xff','\xfc', // 0x0068
105 '\xf0','\x80','\x00','\x00','\x0e','\xe0','\xf8','\xfe', // 0x0070
106 '\x3f','\x0f','\x01','\x00','\x00','\x03','\x01','\x03', // 0x0078
107 '\x07','\x0e','\x0e','\x02','\x0c','\x0c','\x03','\x0d', // 0x0080
108 '\x0b','\x03','\xff','\xfe','\xfe','\x02','\x86','\x07', // 0x0088
109 '\x07','\x02','\x87','\xce','\xfe','\xff','\x23','\x03', // 0x0090
110 '\x0d','\x0c','\x0c','\x04','\x0e','\x07','\x03','\x01', // 0x0098
111 '\x00','\x00','\x08','\xff','\xff','\x03','\x00','\x00', // 0x00a0
112 '\x05','\xff','\xff','\x03','\xfe','\x00','\x00','\x02', // 0x00a8
113 '\x03','\x07','\x1f','\x3e','\x7c','\xf8','\xe0','\xc0', // 0x00b0
114 '\x00','\x00','\x06','\xff','\xff','\x04','\x00','\x00', // 0x00b8
115 '\x09','\xe0','\xf8','\xff','\x3f','\x07','\x01','\x00', // 0x00c0
116 '\x00','\x03','\x01','\x07','\x3f','\xff','\xf8','\xe0', // 0x00c8
117 '\x80','\x00','\x00','\x05','\x03','\x1f','\x7f','\xfe', // 0x00d0
118 '\xf0','\xc0','\x00','\x00','\x08','\xe0','\xfc','\xff', // 0x00d8
119 '\x3f','\x07','\x01','\x00','\x00','\x06','\xe0','\xf8', // 0x00e0
120 '\x7c','\x1c','\x0e','\xc6','\xc6','\x03','\xe6','\x74', // 0x00e8
121 '\x38','\x78','\xe3','\xe7','\x1f','\x3f','\x3f','\x02', // 0x00f0
122 '\x1f','\xef','\xe7','\xf9','\x38','\x78','\xf6','\xe6', // 0x00f8
123 '\xc6','\xc6','\x02','\x8e','\x0c','\x3c','\xf8','\xf0', // 0x0100
124 '\xc0','\x00','\x00','\x07','\xff','\xff','\x03','\x00', // 0x0108
125 '\x00','\x05','\xff','\xff','\x04','\x00','\x00','\x07', // 0x0110
126 '\x01','\x03','\x07','\x1f','\x3e','\xfc','\xf0','\xe0', // 0x0118
127 '\x80','\x0f','\xff','\xff','\x03','\x00','\x00','\x05', // 0x0120
128 '\x80','\xe0','\xfc','\xff','\x3f','\x0f','\x0c','\x0c', // 0x0128
129 '\x0b','\x0f','\x1f','\xff','\xfc','\xf0','\x80','\x00', // 0x0130
130 '\x00','\x05','\x03','\x0f','\x7f','\xfe','\xf8','\xc0', // 0x0138
131 '\x00','\x80','\xf0','\xfc','\xff','\x3f','\x07','\x00', // 0x0140
132 '\x00','\x0a','\x07','\x1f','\x1e','\x38','\x30','\x71', // 0x0148
133 '\x63','\x63','\x02','\x71','\x30','\x38','\x1e','\x0f', // 0x0150
134 '\x07','\x00','\x00','\x04','\x03','\x0f','\x1f','\x38', // 0x0158
135 '\x30','\x71','\x63','\x63','\x02','\x73','\x31','\x38', // 0x0160
136 '\x3c','\x1f','\x0f','\x03','\x00','\x00','\x07','\x1f', // 0x0168
137 '\x3f','\x3f','\x02','\x00','\x00','\x05','\x1f','\x3f', // 0x0170
138 '\x3f','\x02','\x0f','\x00','\x00','\x0d','\x01','\x03', // 0x0178
139 '\x0f','\x1f','\x3f','\x3f','\x02','\x1f','\x00','\x00', // 0x0180
140 '\x03','\x10','\x3c','\x3f','\x1f','\x03','\x00','\x00', // 0x0188
141 '\x11','\x03','\x1f','\x3f','\x3e','\x18','\x00','\x00', // 0x0190
142 '\x06','\x01','\x0f','\x1f','\x3f','\x3f','\x02','\x1f', // 0x0198
143 '\x03','\x00','\x00','\xaf','\xc0','\xf0','\xf8','\x38', // 0x01a0
144 '\xf8','\xf8','\x02','\xe0','\x00','\x00','\x04','\xf8', // 0x01a8
145 '\xf8','\x03','\x18','\x18','\x03','\xf8','\xf8','\x02', // 0x01b0
146 '\xf0','\x00','\x00','\x02','\xf8','\xf8','\x03','\x38', // 0x01b8
147 '\xf8','\xe0','\x00','\xe0','\xf8','\x38','\xf8','\xf8', // 0x01c0
148 '\x03','\x00','\x00','\x02','\xf8','\xf8','\x03','\x98', // 0x01c8
149 '\x98','\x05','\x18','\x00','\x00','\x02','\xf8','\xf8', // 0x01d0
150 '\x03','\x18','\x18','\x04','\x38','\xf8','\xf0','\xe0', // 0x01d8
151 '\x00','\x00','\x42','\x20','\x38','\x3e','\x3f','\x0f', // 0x01e0
152 '\x07','\x06','\x06','\x02','\x0f','\x3f','\x3f','\x02', // 0x01e8
153 '\x3c','\x20','\x00','\x3f','\x3f','\x03','\x00','\x03', // 0x01f0
154 '\x1f','\x3f','\x39','\x20','\x00','\x00','\x02','\x3f', // 0x01f8
155 '\x3f','\x03','\x00','\x0f','\x3f','\x3c','\x0f','\x01', // 0x0200
156 '\x00','\x3f','\x3f','\x03','\x00','\x00','\x02','\x3f', // 0x0208
157 '\x3f','\x03','\x31','\x31','\x05','\x30','\x00','\x00', // 0x0210
158 '\x02','\x3f','\x3f','\x03','\x30','\x30','\x04','\x3c', // 0x0218
159 '\x1f','\x0f','\x07','\x00','\x00','\x22',
163 static const char* const pageTitles
[] = {
169 static const char* const gpsFixTypeText
[] = {
175 static const char* tickerCharacters
= "|/-\\"; // use 2/4/8 characters so that the divide is optimal.
176 #define TICKER_CHARACTER_COUNT (sizeof(tickerCharacters) / sizeof(char))
178 static timeUs_t nextPageAt
;
179 static bool forcePageChange
;
180 static pageId_e currentPageId
;
182 static void resetDisplay(void)
184 displayPresent
= ug2864hsweg01InitI2C();
187 static void LCDprint(uint8_t i
)
189 i2c_OLED_send_char(i
);
192 static void padLineBufferToChar(uint8_t toChar
)
194 uint8_t length
= strlen(lineBuffer
);
195 while (length
< toChar
- 1) {
196 lineBuffer
[length
++] = ' ';
198 lineBuffer
[length
] = 0;
202 static void padLineBuffer(void)
204 padLineBufferToChar(sizeof(lineBuffer
));
207 static void padHalfLineBuffer(void)
209 uint8_t halfLineIndex
= sizeof(lineBuffer
) / 2;
210 padLineBufferToChar(halfLineIndex
);
214 // LCDbar(n,v) : draw a bar graph - n number of chars for width, v value in % to display
215 static void drawHorizonalPercentageBar(uint8_t width
,uint8_t percent
) {
221 j
= (width
* percent
) / 100;
223 for (i
= 0; i
< j
; i
++)
224 LCDprint(159); // full
227 LCDprint(154 + (percent
* width
* 5 / 100 - 5 * j
)); // partial fill
229 for (i
= j
+ 1; i
< width
; i
++)
230 LCDprint(154); // empty
233 static void updateTicker(void)
235 static uint8_t tickerIndex
= 0;
236 i2c_OLED_set_xy(SCREEN_CHARACTER_COLUMN_COUNT
- 1, 0);
237 i2c_OLED_send_char(tickerCharacters
[tickerIndex
]);
239 tickerIndex
= tickerIndex
% TICKER_CHARACTER_COUNT
;
242 static void updateRxStatus(void)
244 i2c_OLED_set_xy(SCREEN_CHARACTER_COLUMN_COUNT
- 2, 0);
246 if (rxIsReceivingSignal()) {
248 } if (rxAreFlightChannelsValid()) {
251 i2c_OLED_send_char(rxStatus
);
254 static void updateFailsafeStatus(void)
256 char failsafeIndicator
= '?';
257 switch (failsafePhase()) {
259 failsafeIndicator
= '-';
261 case FAILSAFE_RX_LOSS_DETECTED
:
262 failsafeIndicator
= 'R';
264 case FAILSAFE_RX_LOSS_IDLE
:
265 failsafeIndicator
= 'I';
267 case FAILSAFE_RETURN_TO_HOME
:
268 failsafeIndicator
= 'H';
270 case FAILSAFE_LANDING
:
271 failsafeIndicator
= 'l';
273 case FAILSAFE_LANDED
:
274 failsafeIndicator
= 'L';
276 case FAILSAFE_RX_LOSS_MONITORING
:
277 failsafeIndicator
= 'M';
279 case FAILSAFE_RX_LOSS_RECOVERED
:
280 failsafeIndicator
= 'r';
283 i2c_OLED_set_xy(SCREEN_CHARACTER_COLUMN_COUNT
- 3, 0);
284 i2c_OLED_send_char(failsafeIndicator
);
287 static void showTitle(void)
289 #if defined(DASHBOARD_ARMED_BITMAP)
290 if (currentPageId
!= PAGE_ARMED
) {
291 i2c_OLED_set_line(0);
292 i2c_OLED_send_string(pageTitles
[currentPageId
]);
295 i2c_OLED_set_line(0);
296 i2c_OLED_send_string(pageTitles
[currentPageId
]);
300 static void showWelcomePage(void)
302 uint8_t rowIndex
= PAGE_TITLE_LINE_COUNT
;
304 tfp_sprintf(lineBuffer
, "v%s (%s)", FC_VERSION_STRING
, shortGitRevision
);
305 i2c_OLED_set_line(rowIndex
++);
306 i2c_OLED_send_string(lineBuffer
);
308 i2c_OLED_set_line(rowIndex
++);
309 i2c_OLED_send_string(targetName
);
312 #if defined(DASHBOARD_ARMED_BITMAP)
313 // RLE compressed bitmaps must be 128 width with vertical data orientation, and size included in file.
314 static void bitmapDecompressAndShow(uint8_t *bitmap
)
316 uint8_t data
= 0, count
= 0;
318 uint8_t width
= *bitmap
;
320 uint8_t height
= *bitmap
;
322 uint16_t bitmapSize
= (width
* height
) / 8;
323 for (i
= 0; i
< bitmapSize
; i
++) {
327 if (data
== *bitmap
) {
337 i2c_OLED_send_byte(data
);
341 static void showArmedPage(void)
343 i2c_OLED_set_line(2);
344 bitmapDecompressAndShow(armedBitmapRLE
);
347 void showArmedPage(void)
352 static void showStatusPage(void)
354 uint8_t rowIndex
= PAGE_TITLE_LINE_COUNT
;
356 if (feature(FEATURE_VBAT
)) {
357 i2c_OLED_set_line(rowIndex
++);
358 tfp_sprintf(lineBuffer
, "V: %d.%02d ", getBatteryVoltage() / 100, getBatteryVoltage() % 100);
359 padLineBufferToChar(12);
360 i2c_OLED_send_string(lineBuffer
);
362 uint8_t batteryPercentage
= calculateBatteryPercentage();
363 drawHorizonalPercentageBar(10, batteryPercentage
);
366 if (feature(FEATURE_CURRENT_METER
)) {
367 i2c_OLED_set_line(rowIndex
++);
368 tfp_sprintf(lineBuffer
, "mAh: %d", (int)getMAhDrawn());
369 padLineBufferToChar(12);
370 i2c_OLED_send_string(lineBuffer
);
372 uint8_t capacityPercentage
= calculateBatteryPercentage();
373 drawHorizonalPercentageBar(10, capacityPercentage
);
377 if (feature(FEATURE_GPS
)) {
378 tfp_sprintf(lineBuffer
, "Sats: %d", gpsSol
.numSat
);
380 i2c_OLED_set_line(rowIndex
);
381 i2c_OLED_send_string(lineBuffer
);
383 tfp_sprintf(lineBuffer
, "Fix: %s", gpsFixTypeText
[gpsSol
.fixType
]);
385 i2c_OLED_set_xy(HALF_SCREEN_CHARACTER_COLUMN_COUNT
, rowIndex
++);
386 i2c_OLED_send_string(lineBuffer
);
388 tfp_sprintf(lineBuffer
, "HDOP: %d.%02d", gpsSol
.hdop
/ 100, gpsSol
.hdop
% 100);
390 i2c_OLED_set_line(rowIndex
++);
391 i2c_OLED_send_string(lineBuffer
);
393 tfp_sprintf(lineBuffer
, "La/Lo: %d/%d", (int)(gpsSol
.llh
.lat
/ GPS_DEGREES_DIVIDER
), (int)(gpsSol
.llh
.lon
/ GPS_DEGREES_DIVIDER
));
395 i2c_OLED_set_line(rowIndex
++);
396 i2c_OLED_send_string(lineBuffer
);
402 if (sensors(SENSOR_MAG
)) {
403 tfp_sprintf(lineBuffer
, "HDG: %d", DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
405 i2c_OLED_set_line(rowIndex
);
406 i2c_OLED_send_string(lineBuffer
);
411 if (sensors(SENSOR_BARO
)) {
412 int32_t alt
= baroCalculateAltitude();
413 tfp_sprintf(lineBuffer
, "Alt: %d", (int)(alt
/ 100));
415 i2c_OLED_set_xy(HALF_SCREEN_CHARACTER_COLUMN_COUNT
, rowIndex
);
416 i2c_OLED_send_string(lineBuffer
);
421 void dashboardUpdate(timeUs_t currentTimeUs
)
423 static uint8_t previousArmedState
= 0;
427 static bool wasGrabbed
= false;
428 if (displayIsGrabbed(displayPort
)) {
431 } else if (wasGrabbed
) {
435 pageChanging
= false;
438 pageChanging
= false;
441 bool updateNow
= (int32_t)(currentTimeUs
- nextDisplayUpdateAt
) >= 0L;
447 nextDisplayUpdateAt
= currentTimeUs
+ DASHBOARD_UPDATE_FREQUENCY
;
449 bool armedState
= ARMING_FLAG(ARMED
) ? true : false;
450 bool armedStateChanged
= armedState
!= previousArmedState
;
451 previousArmedState
= armedState
;
454 if (!armedStateChanged
) {
457 currentPageId
= PAGE_ARMED
;
460 if (armedStateChanged
) {
461 currentPageId
= PAGE_STATUS
;
465 if ((currentPageId
== PAGE_WELCOME
) && ((int32_t)(currentTimeUs
- nextPageAt
) >= 0L)) {
466 currentPageId
= PAGE_STATUS
;
470 if (forcePageChange
) {
472 forcePageChange
= false;
477 // Some OLED displays do not respond on the first initialisation so refresh the display
478 // when the page changes in the hopes the hardware responds. This also allows the
479 // user to power off/on the display or connect it while powered.
480 if (!displayPresent
) {
484 if (!displayPresent
) {
488 i2c_OLED_clear_display_quick();
492 if (!displayPresent
) {
496 switch (currentPageId
) {
509 updateFailsafeStatus();
515 void dashboardSetPage(pageId_e newPageId
)
517 currentPageId
= newPageId
;
518 forcePageChange
= true;
521 void dashboardInit(void)
527 displayPort
= displayPortOledInit();
529 cmsDisplayPortRegister(displayPort
);
532 dashboardSetPage(PAGE_WELCOME
);
533 const timeUs_t now
= micros();
534 dashboardSetNextPageChangeAt(now
+ 5 * MICROSECONDS_IN_A_SECOND
);
536 dashboardUpdate(now
);
539 void dashboardSetNextPageChangeAt(timeUs_t futureMicros
)
541 nextPageAt
= futureMicros
;