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/>.
26 #include "build_config.h"
28 #include "drivers/serial.h"
29 #include "drivers/system.h"
30 #include "drivers/display_ug2864hsweg01.h"
31 #include "drivers/sensor.h"
32 #include "drivers/accgyro.h"
33 #include "drivers/compass.h"
35 #include "common/printf.h"
36 #include "common/maths.h"
37 #include "common/axis.h"
38 #include "common/typeconversion.h"
42 #include "sensors/battery.h"
43 #include "sensors/sensors.h"
44 #include "sensors/compass.h"
45 #include "sensors/acceleration.h"
46 #include "sensors/gyro.h"
47 #include "sensors/barometer.h"
51 #include "io/rc_controls.h"
53 #include "flight/pid.h"
54 #include "flight/imu.h"
55 #include "flight/failsafe.h"
59 #include "flight/navigation_rewrite.h"
62 #include "config/runtime_config.h"
64 #include "config/config.h"
68 controlRateConfig_t
*getControlRateConfig(uint8_t profileIndex
);
70 #define MICROSECONDS_IN_A_SECOND (1000 * 1000)
72 #define DISPLAY_UPDATE_FREQUENCY (MICROSECONDS_IN_A_SECOND / 5)
73 #define PAGE_CYCLE_FREQUENCY (MICROSECONDS_IN_A_SECOND * 5)
75 static uint32_t nextDisplayUpdateAt
= 0;
76 static bool displayPresent
= false;
78 static rxConfig_t
*rxConfig
;
80 #define PAGE_TITLE_LINE_COUNT 1
82 static char lineBuffer
[SCREEN_CHARACTER_COLUMN_COUNT
+ 1];
84 #define HALF_SCREEN_CHARACTER_COLUMN_COUNT (SCREEN_CHARACTER_COLUMN_COUNT / 2)
85 #define IS_SCREEN_CHARACTER_COLUMN_COUNT_ODD (SCREEN_CHARACTER_COLUMN_COUNT & 1)
87 #if defined(DISPLAY_ARMED_BITMAP)
88 static uint8_t armedBitmapRLE
[] = { 128, 32,
89 '\x00','\x00','\x87','\xc0','\xe0','\xf8','\xfc','\xfc', // 0x0008
90 '\x02','\x7e','\x3e','\x1f','\x0f','\x0f','\x06','\xcf', // 0x0010
91 '\xff','\xff','\x04','\x7f','\x1f','\x8e','\xe0','\xf0', // 0x0018
92 '\xfc','\xfe','\x7f','\x3f','\x0f','\x0f','\x06','\x8f', // 0x0020
93 '\xcf','\xff','\xff','\x04','\x7f','\x1f','\x8e','\xe0', // 0x0028
94 '\xf0','\xfc','\xfe','\x7f','\x3f','\x0f','\x0f','\x06', // 0x0030
95 '\xcf','\xef','\xff','\xff','\x03','\x7f','\x1f','\x0f', // 0x0038
96 '\x0f','\x06','\xcf','\xff','\xff','\x04','\x7f','\x1f', // 0x0040
97 '\x8e','\xe0','\xf0','\xfc','\xfe','\xff','\xbf','\x8f', // 0x0048
98 '\x8f','\x05','\x0f','\x0f','\x08','\x07','\x07','\x02', // 0x0050
99 '\x83','\xe3','\xf1','\xfd','\xfe','\x7f','\x3f','\x0f', // 0x0058
100 '\x0f','\x07','\xcf','\xff','\xff','\x04','\x7f','\x1f', // 0x0060
101 '\x0e','\x00','\x00','\x03','\x80','\xc0','\xf0','\xf8', // 0x0068
102 '\xfe','\x7f','\x3f','\x1f','\x0f','\x0f','\x06','\x8f', // 0x0070
103 '\xef','\xff','\xff','\x03','\x7f','\x3f','\x8f','\xc7', // 0x0078
104 '\xf3','\xf8','\xfe','\xff','\x3f','\x1f','\x0f','\x0f', // 0x0080
105 '\x06','\x8f','\xcf','\xff','\xff','\x04','\x3f','\x9f', // 0x0088
106 '\xc3','\xf1','\xf8','\xfc','\xff','\x3f','\x1f','\x07', // 0x0090
107 '\x03','\x00','\x00','\x03','\x80','\xc0','\xf0','\xf8', // 0x0098
108 '\xfe','\xff','\x3f','\x1f','\x07','\x03','\x00','\x00', // 0x00a0
109 '\x03','\x80','\xc0','\xf0','\xf8','\xfe','\xff','\x3f', // 0x00a8
110 '\x9f','\xc7','\xf3','\xf8','\xfc','\xff','\xff','\x03', // 0x00b0
111 '\xf7','\xf3','\xf3','\x04','\xf1','\xf1','\x03','\xf0', // 0x00b8
112 '\xf0','\x09','\xf8','\xfe','\xff','\xff','\x03','\xf7', // 0x00c0
113 '\xf3','\xf0','\xf0','\x06','\xf8','\x7c','\x7e','\x3f', // 0x00c8
114 '\x3f','\x02','\x1f','\x07','\x03','\x00','\x00','\x86', // 0x00d0
118 static const char* const pageTitles
[] = {
124 static const char* const gpsFixTypeText
[] = {
130 static const char* tickerCharacters
= "|/-\\"; // use 2/4/8 characters so that the divide is optimal.
131 #define TICKER_CHARACTER_COUNT (sizeof(tickerCharacters) / sizeof(char))
133 static uint32_t nextPageAt
;
134 static bool forcePageChange
;
135 static pageId_e currentPageId
;
137 void resetDisplay(void) {
138 displayPresent
= ug2864hsweg01InitI2C();
141 void LCDprint(uint8_t i
) {
142 i2c_OLED_send_char(i
);
145 void padLineBufferToChar(uint8_t toChar
)
147 uint8_t length
= strlen(lineBuffer
);
148 while (length
< toChar
- 1) {
149 lineBuffer
[length
++] = ' ';
151 lineBuffer
[length
] = 0;
154 void padLineBuffer(void)
156 padLineBufferToChar(sizeof(lineBuffer
));
159 void padHalfLineBuffer(void)
161 uint8_t halfLineIndex
= sizeof(lineBuffer
) / 2;
162 padLineBufferToChar(halfLineIndex
);
165 // LCDbar(n,v) : draw a bar graph - n number of chars for width, v value in % to display
166 void drawHorizonalPercentageBar(uint8_t width
,uint8_t percent
) {
172 j
= (width
* percent
) / 100;
174 for (i
= 0; i
< j
; i
++)
175 LCDprint(159); // full
178 LCDprint(154 + (percent
* width
* 5 / 100 - 5 * j
)); // partial fill
180 for (i
= j
+ 1; i
< width
; i
++)
181 LCDprint(154); // empty
184 void updateTicker(void)
186 static uint8_t tickerIndex
= 0;
187 i2c_OLED_set_xy(SCREEN_CHARACTER_COLUMN_COUNT
- 1, 0);
188 i2c_OLED_send_char(tickerCharacters
[tickerIndex
]);
190 tickerIndex
= tickerIndex
% TICKER_CHARACTER_COUNT
;
193 void updateRxStatus(void)
195 i2c_OLED_set_xy(SCREEN_CHARACTER_COLUMN_COUNT
- 2, 0);
197 if (rxIsReceivingSignal()) {
199 } if (rxAreFlightChannelsValid()) {
202 i2c_OLED_send_char(rxStatus
);
205 void updateFailsafeStatus(void)
207 char failsafeIndicator
= '?';
208 switch (failsafePhase()) {
210 failsafeIndicator
= '-';
212 case FAILSAFE_RX_LOSS_DETECTED
:
213 failsafeIndicator
= 'R';
216 case FAILSAFE_RETURN_TO_HOME
:
217 failsafeIndicator
= 'H';
220 case FAILSAFE_LANDING
:
221 failsafeIndicator
= 'l';
223 case FAILSAFE_LANDED
:
224 failsafeIndicator
= 'L';
226 case FAILSAFE_RX_LOSS_MONITORING
:
227 failsafeIndicator
= 'M';
229 case FAILSAFE_RX_LOSS_RECOVERED
:
230 failsafeIndicator
= 'r';
233 i2c_OLED_set_xy(SCREEN_CHARACTER_COLUMN_COUNT
- 3, 0);
234 i2c_OLED_send_char(failsafeIndicator
);
239 #if defined(DISPLAY_ARMED_BITMAP)
240 if (currentPageId
!= PAGE_ARMED
) {
241 i2c_OLED_set_line(0);
242 i2c_OLED_send_string(pageTitles
[currentPageId
]);
245 i2c_OLED_set_line(0);
246 i2c_OLED_send_string(pageTitles
[currentPageId
]);
250 void showWelcomePage(void)
252 uint8_t rowIndex
= PAGE_TITLE_LINE_COUNT
;
254 tfp_sprintf(lineBuffer
, "v%s (%s)", FC_VERSION_STRING
, shortGitRevision
);
255 i2c_OLED_set_line(rowIndex
++);
256 i2c_OLED_send_string(lineBuffer
);
258 i2c_OLED_set_line(rowIndex
++);
259 i2c_OLED_send_string(targetName
);
262 #if defined(DISPLAY_ARMED_BITMAP)
263 // RLE compressed bitmaps must be 128 width with vertical data orientation, and size included in file.
264 void bitmapDecompressAndShow(uint8_t *bitmap
)
266 uint8_t data
= 0, count
= 0;
268 uint8_t width
= *bitmap
;
270 uint8_t height
= *bitmap
;
272 uint16_t bitmapSize
= (width
* height
) / 8;
273 for (i
= 0; i
< bitmapSize
; i
++) {
277 if(data
== *bitmap
) {
287 i2c_OLED_send_byte(data
);
291 void showArmedPage(void)
293 i2c_OLED_set_line(2);
294 bitmapDecompressAndShow(armedBitmapRLE
);
297 void showArmedPage(void)
302 void showStatusPage(void)
304 uint8_t rowIndex
= PAGE_TITLE_LINE_COUNT
;
306 if (feature(FEATURE_VBAT
)) {
307 i2c_OLED_set_line(rowIndex
++);
308 tfp_sprintf(lineBuffer
, "V: %d.%1d ", vbat
/ 10, vbat
% 10);
309 padLineBufferToChar(12);
310 i2c_OLED_send_string(lineBuffer
);
312 uint8_t batteryPercentage
= calculateBatteryPercentage();
313 drawHorizonalPercentageBar(10, batteryPercentage
);
316 if (feature(FEATURE_CURRENT_METER
)) {
317 i2c_OLED_set_line(rowIndex
++);
318 tfp_sprintf(lineBuffer
, "mAh: %d", mAhDrawn
);
319 padLineBufferToChar(12);
320 i2c_OLED_send_string(lineBuffer
);
322 uint8_t capacityPercentage
= calculateBatteryCapacityRemainingPercentage();
323 drawHorizonalPercentageBar(10, capacityPercentage
);
329 if (feature(FEATURE_GPS
)) {
330 tfp_sprintf(lineBuffer
, "Sats: %d", gpsSol
.numSat
);
332 i2c_OLED_set_line(rowIndex
);
333 i2c_OLED_send_string(lineBuffer
);
335 tfp_sprintf(lineBuffer
, "Fix: %s", gpsFixTypeText
[gpsSol
.fixType
]);
337 i2c_OLED_set_xy(HALF_SCREEN_CHARACTER_COLUMN_COUNT
, rowIndex
++);
338 i2c_OLED_send_string(lineBuffer
);
340 tfp_sprintf(lineBuffer
, "HDOP: %d.%1d", gpsSol
.hdop
/ 100, gpsSol
.hdop
% 100);
342 i2c_OLED_set_line(rowIndex
++);
343 i2c_OLED_send_string(lineBuffer
);
345 tfp_sprintf(lineBuffer
, "La/Lo: %d/%d", gpsSol
.llh
.lat
/ GPS_DEGREES_DIVIDER
, gpsSol
.llh
.lon
/ GPS_DEGREES_DIVIDER
);
347 i2c_OLED_set_line(rowIndex
++);
348 i2c_OLED_send_string(lineBuffer
);
354 if (sensors(SENSOR_MAG
)) {
355 tfp_sprintf(lineBuffer
, "HDG: %d", DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
357 i2c_OLED_set_line(rowIndex
);
358 i2c_OLED_send_string(lineBuffer
);
363 if (sensors(SENSOR_BARO
)) {
364 int32_t alt
= baroCalculateAltitude();
365 tfp_sprintf(lineBuffer
, "Alt: %d", alt
/ 100);
367 i2c_OLED_set_xy(HALF_SCREEN_CHARACTER_COLUMN_COUNT
, rowIndex
);
368 i2c_OLED_send_string(lineBuffer
);
374 void updateDisplay(void)
376 uint32_t now
= micros();
377 static uint8_t previousArmedState
= 0;
379 bool pageChanging
= false;
380 bool updateNow
= (int32_t)(now
- nextDisplayUpdateAt
) >= 0L;
386 nextDisplayUpdateAt
= now
+ DISPLAY_UPDATE_FREQUENCY
;
388 bool armedState
= ARMING_FLAG(ARMED
) ? true : false;
389 bool armedStateChanged
= armedState
!= previousArmedState
;
390 previousArmedState
= armedState
;
393 if (!armedStateChanged
) {
396 currentPageId
= PAGE_ARMED
;
399 if (armedStateChanged
) {
400 currentPageId
= PAGE_STATUS
;
404 if ((currentPageId
== PAGE_WELCOME
) && ((int32_t)(now
- nextPageAt
) >= 0L)) {
405 currentPageId
= PAGE_STATUS
;
409 if (forcePageChange
) {
411 forcePageChange
= false;
416 // Some OLED displays do not respond on the first initialisation so refresh the display
417 // when the page changes in the hopes the hardware responds. This also allows the
418 // user to power off/on the display or connect it while powered.
419 if (!displayPresent
) {
423 if (!displayPresent
) {
427 i2c_OLED_clear_display_quick();
431 if (!displayPresent
) {
435 switch(currentPageId
) {
448 updateFailsafeStatus();
454 void displaySetPage(pageId_e newPageId
)
456 currentPageId
= newPageId
;
457 forcePageChange
= true;
460 void displayInit(rxConfig_t
*rxConfigToUse
)
466 rxConfig
= rxConfigToUse
;
468 displaySetPage(PAGE_WELCOME
);
469 displaySetNextPageChangeAt(micros() + (1000 * 1000 * 5));
474 void displaySetNextPageChangeAt(uint32_t futureMicros
)
476 nextPageAt
= futureMicros
;