Avoid resetting page cycle index when re-enabling page cycling so that
[betaflight.git] / src / main / io / display.c
blob65f94d2178dcd82e63201ec2244f282003832aca
1 /*
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/>.
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <stdarg.h>
21 #include <string.h>
23 #include "platform.h"
24 #include "version.h"
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"
40 #ifdef DISPLAY
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"
48 #include "rx/rx.h"
50 #include "io/rc_controls.h"
52 #include "flight/pid.h"
53 #include "flight/imu.h"
55 #ifdef GPS
56 #include "io/gps.h"
57 #include "flight/navigation.h"
58 #endif
60 #include "config/runtime_config.h"
62 #include "config/config.h"
64 #include "display.h"
66 controlRateConfig_t *getControlRateConfig(uint8_t profileIndex);
68 //#define ENABLE_DEBUG_OLED_PAGE
70 #define MILLISECONDS_IN_A_SECOND (1000 * 1000)
72 #define DISPLAY_UPDATE_FREQUENCY (MILLISECONDS_IN_A_SECOND / 10)
73 #define PAGE_CYCLE_FREQUENCY (MILLISECONDS_IN_A_SECOND * 5)
75 static uint32_t nextDisplayUpdateAt = 0;
77 static rxConfig_t *rxConfig;
79 #define PAGE_TITLE_LINE_COUNT 1
81 static char lineBuffer[SCREEN_CHARACTER_COLUMN_COUNT + 1];
83 const char* pageTitles[] = {
84 "CLEANFLIGHT",
85 "ARMED",
86 "BATTERY",
87 "SENSORS",
88 "RX",
89 "PROFILE"
90 #ifdef GPS
91 ,"GPS"
92 #endif
93 #ifdef ENABLE_DEBUG_OLED_PAGE
94 ,"DEBUG"
95 #endif
98 #define PAGE_COUNT (PAGE_RX + 1)
100 const uint8_t cyclePageIds[] = {
101 PAGE_PROFILE,
102 #ifdef GPS
103 PAGE_GPS,
104 #endif
105 PAGE_RX,
106 PAGE_BATTERY,
107 PAGE_SENSORS
108 #ifdef ENABLE_DEBUG_OLED_PAGE
109 ,PAGE_DEBUG,
110 #endif
113 #define CYCLE_PAGE_ID_COUNT (sizeof(cyclePageIds) / sizeof(cyclePageIds[0]))
115 static const char* tickerCharacters = "|/-\\";
116 #define TICKER_CHARACTER_COUNT (sizeof(tickerCharacters) / sizeof(char))
118 typedef enum {
119 PAGE_STATE_FLAG_NONE = 0,
120 PAGE_STATE_FLAG_CYCLE_ENABLED = (1 << 0),
121 PAGE_STATE_FLAG_FORCE_PAGE_CHANGE = (1 << 1)
122 } pageFlags_e;
124 typedef struct pageState_s {
125 bool pageChanging;
126 pageId_e pageId;
127 pageId_e pageIdBeforeArming;
128 uint8_t pageFlags;
129 uint8_t cycleIndex;
130 uint32_t nextPageAt;
131 } pageState_t;
133 static pageState_t pageState;
135 void resetDisplay(void) {
136 ug2864hsweg01InitI2C();
139 void LCDprint(uint8_t i) {
140 i2c_OLED_send_char(i);
143 void padLineBuffer(void)
145 uint8_t length = strlen(lineBuffer);
146 while (length < sizeof(lineBuffer) - 1) {
147 lineBuffer[length++] = ' ';
151 // LCDbar(n,v) : draw a bar graph - n number of chars for width, v value in % to display
152 void drawHorizonalPercentageBar(uint8_t width,uint8_t percent) {
153 uint8_t i, j;
155 if (percent > 100)
156 percent = 100;
158 j = (width * percent) / 100;
160 for (i = 0; i < j; i++)
161 LCDprint(159); // full
163 if (j < width)
164 LCDprint(154 + (percent * width * 5 / 100 - 5 * j)); // partial fill
166 for (i = j + 1; i < width; i++)
167 LCDprint(154); // empty
170 #if 0
171 void fillScreenWithCharacters()
173 for (uint8_t row = 0; row < SCREEN_CHARACTER_ROW_COUNT; row++) {
174 for (uint8_t column = 0; column < SCREEN_CHARACTER_COLUMN_COUNT; column++) {
175 i2c_OLED_set_xy(column, row);
176 i2c_OLED_send_char('A' + column);
180 #endif
182 void updateTicker(void)
184 static uint8_t tickerIndex = 0;
185 i2c_OLED_set_xy(SCREEN_CHARACTER_COLUMN_COUNT - 1, 0);
186 i2c_OLED_send_char(tickerCharacters[tickerIndex]);
187 tickerIndex++;
188 tickerIndex = tickerIndex % TICKER_CHARACTER_COUNT;
191 void showTitle()
193 i2c_OLED_set_line(0);
194 i2c_OLED_send_string(pageTitles[pageState.pageId]);
197 void handlePageChange(void)
199 // Some OLED displays do not respond on the first initialisation so refresh the display
200 // when the page changes in the hopes the hardware responds. This also allows the
201 // user to power off/on the display or connect it while powered.
202 resetDisplay();
204 i2c_OLED_clear_display_quick();
205 showTitle();
208 void drawRxChannel(uint8_t channelIndex, uint8_t width)
210 uint32_t percentage;
212 LCDprint(rcChannelLetters[channelIndex]);
214 percentage = (constrain(rcData[channelIndex], PWM_RANGE_MIN, PWM_RANGE_MAX) - PWM_RANGE_MIN) * 100 / (PWM_RANGE_MAX - PWM_RANGE_MIN);
215 drawHorizonalPercentageBar(width - 1, percentage);
218 #define HALF_SCREEN_CHARACTER_COLUMN_COUNT (SCREEN_CHARACTER_COLUMN_COUNT / 2)
219 #define IS_SCREEN_CHARACTER_COLUMN_COUNT_ODD (SCREEN_CHARACTER_COLUMN_COUNT & 1)
221 #define RX_CHANNELS_PER_PAGE_COUNT 14
222 void showRxPage(void)
225 for (uint8_t channelIndex = 0; channelIndex < rxRuntimeConfig.channelCount && channelIndex < RX_CHANNELS_PER_PAGE_COUNT; channelIndex += 2) {
226 i2c_OLED_set_line((channelIndex / 2) + PAGE_TITLE_LINE_COUNT);
228 drawRxChannel(channelIndex, HALF_SCREEN_CHARACTER_COLUMN_COUNT);
230 if (channelIndex >= rxRuntimeConfig.channelCount) {
231 continue;
234 if (IS_SCREEN_CHARACTER_COLUMN_COUNT_ODD) {
235 LCDprint(' ');
238 drawRxChannel(channelIndex + PAGE_TITLE_LINE_COUNT, HALF_SCREEN_CHARACTER_COLUMN_COUNT);
242 void showWelcomePage(void)
244 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
246 tfp_sprintf(lineBuffer, "v%s (%s)", FC_VERSION_STRING, shortGitRevision);
247 i2c_OLED_set_line(rowIndex++);
248 i2c_OLED_send_string(lineBuffer);
250 tfp_sprintf(lineBuffer, "Target: %s", targetName);
251 i2c_OLED_set_line(rowIndex++);
252 i2c_OLED_send_string(lineBuffer);
255 void showArmedPage(void)
259 void showProfilePage(void)
261 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
263 tfp_sprintf(lineBuffer, "Profile: %d", getCurrentProfile());
264 i2c_OLED_set_line(rowIndex++);
265 i2c_OLED_send_string(lineBuffer);
267 uint8_t currentRateProfileIndex = getCurrentControlRateProfile();
268 tfp_sprintf(lineBuffer, "Rate profile: %d", currentRateProfileIndex);
269 i2c_OLED_set_line(rowIndex++);
270 i2c_OLED_send_string(lineBuffer);
272 controlRateConfig_t *controlRateConfig = getControlRateConfig(currentRateProfileIndex);
274 tfp_sprintf(lineBuffer, "RC Expo: %d", controlRateConfig->rcExpo8);
275 padLineBuffer();
276 i2c_OLED_set_line(rowIndex++);
277 i2c_OLED_send_string(lineBuffer);
279 tfp_sprintf(lineBuffer, "RC Rate: %d", controlRateConfig->rcRate8);
280 padLineBuffer();
281 i2c_OLED_set_line(rowIndex++);
282 i2c_OLED_send_string(lineBuffer);
284 tfp_sprintf(lineBuffer, "R&P Rate: %d", controlRateConfig->rollPitchRate);
285 padLineBuffer();
286 i2c_OLED_set_line(rowIndex++);
287 i2c_OLED_send_string(lineBuffer);
289 tfp_sprintf(lineBuffer, "Yaw Rate: %d", controlRateConfig->yawRate);
290 padLineBuffer();
291 i2c_OLED_set_line(rowIndex++);
292 i2c_OLED_send_string(lineBuffer);
295 #define SATELLITE_COUNT (sizeof(GPS_svinfo_cno) / sizeof(GPS_svinfo_cno[0]))
296 #define SATELLITE_GRAPH_LEFT_OFFSET ((SCREEN_CHARACTER_COLUMN_COUNT - SATELLITE_COUNT) / 2)
298 #ifdef GPS
299 void showGpsPage() {
300 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
302 i2c_OLED_set_xy(MAX(0, SATELLITE_GRAPH_LEFT_OFFSET), rowIndex++);
304 uint32_t index;
305 for (index = 0; index < SATELLITE_COUNT && index < SCREEN_CHARACTER_COLUMN_COUNT; index++) {
306 uint8_t bargraphValue = ((uint16_t) GPS_svinfo_cno[index] * VERTICAL_BARGRAPH_CHARACTER_COUNT) / (GPS_DBHZ_MAX - 1);
307 bargraphValue = MIN(bargraphValue, VERTICAL_BARGRAPH_CHARACTER_COUNT - 1);
308 i2c_OLED_send_char(VERTICAL_BARGRAPH_ZERO_CHARACTER + bargraphValue);
311 char fixChar = STATE(GPS_FIX) ? 'Y' : 'N';
312 tfp_sprintf(lineBuffer, "Satellites: %d Fix: %c", GPS_numSat, fixChar);
313 padLineBuffer();
314 i2c_OLED_set_line(rowIndex++);
315 i2c_OLED_send_string(lineBuffer);
317 tfp_sprintf(lineBuffer, "Lat: %d Lon: %d", GPS_coord[LAT] / GPS_DEGREES_DIVIDER, GPS_coord[LON] / GPS_DEGREES_DIVIDER);
318 padLineBuffer();
319 i2c_OLED_set_line(rowIndex++);
320 i2c_OLED_send_string(lineBuffer);
322 tfp_sprintf(lineBuffer, "Spd: %d cm/s GC: %d", GPS_speed, GPS_ground_course);
323 padLineBuffer();
324 i2c_OLED_set_line(rowIndex++);
325 i2c_OLED_send_string(lineBuffer);
327 tfp_sprintf(lineBuffer, "RX: %d Delta: %d", GPS_packetCount, gpsData.lastMessage - gpsData.lastLastMessage);
328 padLineBuffer();
329 i2c_OLED_set_line(rowIndex++);
330 i2c_OLED_send_string(lineBuffer);
332 tfp_sprintf(lineBuffer, "ERRs: %d TOs: %d", gpsData.errors, gpsData.timeouts);
333 padLineBuffer();
334 i2c_OLED_set_line(rowIndex++);
335 i2c_OLED_send_string(lineBuffer);
337 strncpy(lineBuffer, gpsPacketLog, GPS_PACKET_LOG_ENTRY_COUNT);
338 padLineBuffer();
339 i2c_OLED_set_line(rowIndex++);
340 i2c_OLED_send_string(lineBuffer);
342 #ifdef GPS_PH_DEBUG
343 tfp_sprintf(lineBuffer, "Angles: P:%d R:%d", GPS_angle[PITCH], GPS_angle[ROLL]);
344 padLineBuffer();
345 i2c_OLED_set_line(rowIndex++);
346 i2c_OLED_send_string(lineBuffer);
347 #endif
349 #if 0
350 tfp_sprintf(lineBuffer, "%d %d %d %d", debug[0], debug[1], debug[2], debug[3]);
351 padLineBuffer();
352 i2c_OLED_set_line(rowIndex++);
353 i2c_OLED_send_string(lineBuffer);
354 #endif
356 #endif
358 void showBatteryPage(void)
360 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
362 if (feature(FEATURE_VBAT)) {
363 tfp_sprintf(lineBuffer, "Volts: %d.%1d Cells: %d", vbat / 10, vbat % 10, batteryCellCount);
364 padLineBuffer();
365 i2c_OLED_set_line(rowIndex++);
366 i2c_OLED_send_string(lineBuffer);
368 uint8_t batteryPercentage = calculateBatteryPercentage();
369 i2c_OLED_set_line(rowIndex++);
370 drawHorizonalPercentageBar(SCREEN_CHARACTER_COLUMN_COUNT, batteryPercentage);
373 if (feature(FEATURE_CURRENT_METER)) {
374 tfp_sprintf(lineBuffer, "Amps: %d.%2d mAh: %d", amperage / 100, amperage % 100, mAhDrawn);
375 padLineBuffer();
376 i2c_OLED_set_line(rowIndex++);
377 i2c_OLED_send_string(lineBuffer);
379 uint8_t capacityPercentage = calculateBatteryCapacityRemainingPercentage();
380 i2c_OLED_set_line(rowIndex++);
381 drawHorizonalPercentageBar(SCREEN_CHARACTER_COLUMN_COUNT, capacityPercentage);
385 void showSensorsPage(void)
387 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
388 static const char *format = "%s %5d %5d %5d";
390 i2c_OLED_set_line(rowIndex++);
391 i2c_OLED_send_string(" X Y Z");
393 if (sensors(SENSOR_ACC)) {
394 tfp_sprintf(lineBuffer, format, "ACC", accSmooth[X], accSmooth[Y], accSmooth[Z]);
395 padLineBuffer();
396 i2c_OLED_set_line(rowIndex++);
397 i2c_OLED_send_string(lineBuffer);
400 if (sensors(SENSOR_GYRO)) {
401 tfp_sprintf(lineBuffer, format, "GYR", gyroADC[X], gyroADC[Y], gyroADC[Z]);
402 padLineBuffer();
403 i2c_OLED_set_line(rowIndex++);
404 i2c_OLED_send_string(lineBuffer);
407 #ifdef MAG
408 if (sensors(SENSOR_MAG)) {
409 tfp_sprintf(lineBuffer, format, "MAG", magADC[X], magADC[Y], magADC[Z]);
410 padLineBuffer();
411 i2c_OLED_set_line(rowIndex++);
412 i2c_OLED_send_string(lineBuffer);
414 #endif
416 tfp_sprintf(lineBuffer, format, "I&H", inclination.values.rollDeciDegrees, inclination.values.pitchDeciDegrees, heading);
417 padLineBuffer();
418 i2c_OLED_set_line(rowIndex++);
419 i2c_OLED_send_string(lineBuffer);
421 uint8_t length;
423 ftoa(EstG.A[X], lineBuffer);
424 length = strlen(lineBuffer);
425 while (length < HALF_SCREEN_CHARACTER_COLUMN_COUNT) {
426 lineBuffer[length++] = ' ';
427 lineBuffer[length+1] = 0;
429 ftoa(EstG.A[Y], lineBuffer + length);
430 padLineBuffer();
431 i2c_OLED_set_line(rowIndex++);
432 i2c_OLED_send_string(lineBuffer);
434 ftoa(EstG.A[Z], lineBuffer);
435 length = strlen(lineBuffer);
436 while (length < HALF_SCREEN_CHARACTER_COLUMN_COUNT) {
437 lineBuffer[length++] = ' ';
438 lineBuffer[length+1] = 0;
440 ftoa(smallAngle, lineBuffer + length);
441 padLineBuffer();
442 i2c_OLED_set_line(rowIndex++);
443 i2c_OLED_send_string(lineBuffer);
447 #ifdef ENABLE_DEBUG_OLED_PAGE
449 void showDebugPage(void)
451 uint8_t rowIndex;
453 for (rowIndex = 0; rowIndex < 4; rowIndex++) {
454 tfp_sprintf(lineBuffer, "%d = %5d", rowIndex, debug[rowIndex]);
455 padLineBuffer();
456 i2c_OLED_set_line(rowIndex + PAGE_TITLE_LINE_COUNT);
457 i2c_OLED_send_string(lineBuffer);
460 #endif
462 void updateDisplay(void)
464 uint32_t now = micros();
465 static uint8_t previousArmedState = 0;
467 bool updateNow = (int32_t)(now - nextDisplayUpdateAt) >= 0L;
468 if (!updateNow) {
469 return;
472 nextDisplayUpdateAt = now + DISPLAY_UPDATE_FREQUENCY;
474 bool armedState = ARMING_FLAG(ARMED) ? true : false;
475 bool armedStateChanged = armedState != previousArmedState;
476 previousArmedState = armedState;
478 if (armedState) {
479 if (!armedStateChanged) {
480 return;
482 pageState.pageIdBeforeArming = pageState.pageId;
483 pageState.pageId = PAGE_ARMED;
484 pageState.pageChanging = true;
485 } else {
486 if (armedStateChanged) {
487 pageState.pageFlags |= PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
488 pageState.pageId = pageState.pageIdBeforeArming;
491 pageState.pageChanging = (pageState.pageFlags & PAGE_STATE_FLAG_FORCE_PAGE_CHANGE) ||
492 (((int32_t)(now - pageState.nextPageAt) >= 0L && (pageState.pageFlags & PAGE_STATE_FLAG_CYCLE_ENABLED)));
493 if (pageState.pageChanging && (pageState.pageFlags & PAGE_STATE_FLAG_CYCLE_ENABLED)) {
494 pageState.cycleIndex++;
495 pageState.cycleIndex = pageState.cycleIndex % CYCLE_PAGE_ID_COUNT;
496 pageState.pageId = cyclePageIds[pageState.cycleIndex];
500 if (pageState.pageChanging) {
501 handlePageChange();
502 pageState.pageFlags &= ~PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
503 pageState.nextPageAt = now + PAGE_CYCLE_FREQUENCY;
506 switch(pageState.pageId) {
507 case PAGE_WELCOME:
508 showWelcomePage();
509 break;
510 case PAGE_ARMED:
511 showArmedPage();
512 break;
513 case PAGE_BATTERY:
514 showBatteryPage();
515 break;
516 case PAGE_SENSORS:
517 showSensorsPage();
518 break;
519 case PAGE_RX:
520 showRxPage();
521 break;
522 case PAGE_PROFILE:
523 showProfilePage();
524 break;
525 #ifdef GPS
526 case PAGE_GPS:
527 if (feature(FEATURE_GPS)) {
528 showGpsPage();
529 } else {
530 pageState.pageFlags |= PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
532 break;
533 #endif
534 #ifdef ENABLE_DEBUG_OLED_PAGE
535 case PAGE_DEBUG:
536 showDebugPage();
537 break;
538 #endif
540 if (!armedState) {
541 updateTicker();
545 void displaySetPage(pageId_e pageId)
547 pageState.pageId = pageId;
548 pageState.pageFlags |= PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
551 void displayInit(rxConfig_t *rxConfigToUse)
553 delay(200);
554 resetDisplay();
555 delay(200);
557 rxConfig = rxConfigToUse;
559 memset(&pageState, 0, sizeof(pageState));
560 displaySetPage(PAGE_WELCOME);
562 updateDisplay();
564 displaySetNextPageChangeAt(micros() + (1000 * 1000 * 5));
567 void displayShowFixedPage(pageId_e pageId)
569 displaySetPage(pageId);
570 displayDisablePageCycling();
573 void displaySetNextPageChangeAt(uint32_t futureMicros)
575 pageState.nextPageAt = futureMicros;
578 void displayEnablePageCycling(void)
580 pageState.pageFlags |= PAGE_STATE_FLAG_CYCLE_ENABLED;
583 void displayResetPageCycling(void)
585 pageState.cycleIndex = CYCLE_PAGE_ID_COUNT - 1; // start at first page
589 void displayDisablePageCycling(void)
591 pageState.pageFlags &= ~PAGE_STATE_FLAG_CYCLE_ENABLED;
594 #endif