Add Winbond W25Q512J support (#14036)
[betaflight.git] / src / main / io / dashboard.c
bloba76d858001b324ac6102bde9b5c3e3be94fe4495
1 /*
2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
8 * any later version.
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <string.h>
24 #include <math.h>
26 #include "platform.h"
28 #ifdef USE_DASHBOARD
30 #include "common/utils.h"
32 #include "build/version.h"
33 #include "build/debug.h"
35 #include "build/build_config.h"
37 #include "drivers/bus.h"
38 #include "drivers/display.h"
39 #include "drivers/display_ug2864hsweg01.h"
40 #include "drivers/time.h"
42 #include "cms/cms.h"
44 #include "common/printf.h"
45 #include "common/maths.h"
46 #include "common/axis.h"
47 #include "common/typeconversion.h"
49 #include "config/feature.h"
50 #include "pg/dashboard.h"
51 #include "pg/rx.h"
53 #include "config/config.h"
54 #include "fc/controlrate_profile.h"
55 #include "fc/rc_controls.h"
56 #include "fc/runtime_config.h"
58 #include "flight/pid.h"
59 #include "flight/imu.h"
60 #include "flight/failsafe.h"
62 #include "io/gps.h"
63 #include "io/dashboard.h"
64 #include "io/displayport_oled.h"
66 #include "blackbox/blackbox_io.h"
67 #include "blackbox/blackbox.h"
69 #include "rx/rx.h"
71 #include "scheduler/scheduler.h"
73 #include "sensors/acceleration.h"
74 #include "sensors/battery.h"
75 #include "sensors/compass.h"
76 #include "sensors/gyro.h"
77 #include "sensors/sensors.h"
79 #define MICROSECONDS_IN_A_SECOND (1000 * 1000)
81 #define DISPLAY_UPDATE_FREQUENCY (MICROSECONDS_IN_A_SECOND / 5)
82 #define PAGE_CYCLE_FREQUENCY (MICROSECONDS_IN_A_SECOND * 5)
84 static extDevice_t *dev;
86 static uint32_t nextDisplayUpdateAt = 0;
87 static bool dashboardPresent = false;
89 static displayPort_t *displayPort;
91 #define PAGE_TITLE_LINE_COUNT 1
93 static char lineBuffer[SCREEN_CHARACTER_COLUMN_COUNT + 1];
95 #define HALF_SCREEN_CHARACTER_COLUMN_COUNT (SCREEN_CHARACTER_COLUMN_COUNT / 2)
96 #define IS_SCREEN_CHARACTER_COLUMN_COUNT_ODD (SCREEN_CHARACTER_COLUMN_COUNT & 1)
98 typedef void (*pageFnPtr)(void);
100 #define PAGE_FLAGS_NONE 0
101 #define PAGE_FLAGS_SKIP_CYCLING (1 << 0)
103 typedef struct pageEntry_s {
104 pageId_e id;
105 char *title;
106 pageFnPtr drawFn;
107 uint8_t flags;
108 } pageEntry_t;
110 static const char tickerCharacters[] = "|/-\\"; // use 2/4/8 characters so that the divide is optimal.
111 #define TICKER_CHARACTER_COUNT ARRAYLEN(tickerCharacters)
113 typedef enum {
114 PAGE_STATE_FLAG_NONE = 0,
115 PAGE_STATE_FLAG_CYCLE_ENABLED = (1 << 0),
116 PAGE_STATE_FLAG_FORCE_PAGE_CHANGE = (1 << 1),
117 } pageFlags_e;
119 typedef struct pageState_s {
120 bool pageChanging;
121 const pageEntry_t *page;
122 uint8_t pageFlags;
123 uint8_t cycleIndex;
124 uint32_t nextPageAt;
125 } pageState_t;
127 static pageState_t pageState;
129 static void resetDisplay(void)
131 dashboardPresent = ug2864hsweg01InitI2C(dev);
134 void LCDprint(uint8_t i)
136 i2c_OLED_send_char(dev, i);
139 static void padLineBuffer(void)
141 uint8_t length = strlen(lineBuffer);
142 while (length < sizeof(lineBuffer) - 1) {
143 lineBuffer[length++] = ' ';
145 lineBuffer[length] = 0;
148 #ifdef USE_GPS
149 static void padHalfLineBuffer(void)
151 uint8_t halfLineIndex = sizeof(lineBuffer) / 2;
152 uint8_t length = strlen(lineBuffer);
153 while (length < halfLineIndex - 1) {
154 lineBuffer[length++] = ' ';
156 lineBuffer[length] = 0;
158 #endif
160 // LCDbar(n,v) : draw a bar graph - n number of chars for width, v value in % to display
161 static void drawHorizonalPercentageBar(uint8_t width,uint8_t percent)
163 uint8_t i, j;
165 if (percent > 100)
166 percent = 100;
168 j = (width * percent) / 100;
170 for (i = 0; i < j; i++)
171 LCDprint(159); // full
173 if (j < width)
174 LCDprint(154 + (percent * width * 5 / 100 - 5 * j)); // partial fill
176 for (i = j + 1; i < width; i++)
177 LCDprint(154); // empty
180 #if 0
181 static void fillScreenWithCharacters(void)
183 for (uint8_t row = 0; row < SCREEN_CHARACTER_ROW_COUNT; row++) {
184 for (uint8_t column = 0; column < SCREEN_CHARACTER_COLUMN_COUNT; column++) {
185 i2c_OLED_set_xy(dev, column, row);
186 i2c_OLED_send_char(dev, 'A' + column);
190 #endif
192 static void updateTicker(void)
194 static uint8_t tickerIndex = 0;
195 i2c_OLED_set_xy(dev, SCREEN_CHARACTER_COLUMN_COUNT - 1, 0);
196 i2c_OLED_send_char(dev, tickerCharacters[tickerIndex]);
197 tickerIndex++;
198 tickerIndex = tickerIndex % TICKER_CHARACTER_COUNT;
201 static void updateRxStatus(void)
203 i2c_OLED_set_xy(dev, SCREEN_CHARACTER_COLUMN_COUNT - 2, 0);
204 char rxStatus = '!';
205 if (isRxReceivingSignal()) {
206 rxStatus = 'r';
207 } if (rxAreFlightChannelsValid()) {
208 rxStatus = 'R';
210 i2c_OLED_send_char(dev, rxStatus);
213 static void updateFailsafeStatus(void)
215 char failsafeIndicator = '?';
216 switch (failsafePhase()) {
217 case FAILSAFE_IDLE:
218 failsafeIndicator = '-';
219 break;
220 case FAILSAFE_RX_LOSS_DETECTED:
221 failsafeIndicator = 'R';
222 break;
223 case FAILSAFE_LANDING:
224 failsafeIndicator = 'l';
225 break;
226 case FAILSAFE_LANDED:
227 failsafeIndicator = 'L';
228 break;
229 case FAILSAFE_RX_LOSS_MONITORING:
230 failsafeIndicator = 'M';
231 break;
232 case FAILSAFE_RX_LOSS_RECOVERED:
233 failsafeIndicator = 'r';
234 break;
235 case FAILSAFE_GPS_RESCUE:
236 failsafeIndicator = 'G';
237 break;
239 i2c_OLED_set_xy(dev, SCREEN_CHARACTER_COLUMN_COUNT - 3, 0);
240 i2c_OLED_send_char(dev, failsafeIndicator);
243 static void showTitle(void)
245 i2c_OLED_set_line(dev, 0);
246 i2c_OLED_send_string(dev, pageState.page->title);
249 static void handlePageChange(void)
251 i2c_OLED_clear_display_quick(dev);
252 showTitle();
255 static void drawRxChannel(uint8_t channelIndex, uint8_t width)
257 LCDprint(rcChannelLetters[channelIndex]);
259 const uint32_t percentage = (constrain(rcData[channelIndex], PWM_RANGE_MIN, PWM_RANGE_MAX) - PWM_RANGE_MIN) * 100 / PWM_RANGE;
260 drawHorizonalPercentageBar(width - 1, percentage);
263 #define RX_CHANNELS_PER_PAGE_COUNT 14
264 static void showRxPage(void)
266 for (int channelIndex = 0; channelIndex < rxRuntimeState.channelCount && channelIndex < RX_CHANNELS_PER_PAGE_COUNT; channelIndex += 2) {
267 i2c_OLED_set_line(dev, (channelIndex / 2) + PAGE_TITLE_LINE_COUNT);
269 drawRxChannel(channelIndex, HALF_SCREEN_CHARACTER_COLUMN_COUNT);
271 if (channelIndex >= rxRuntimeState.channelCount) {
272 continue;
275 if (IS_SCREEN_CHARACTER_COLUMN_COUNT_ODD) {
276 LCDprint(' ');
279 drawRxChannel(channelIndex + PAGE_TITLE_LINE_COUNT, HALF_SCREEN_CHARACTER_COLUMN_COUNT);
283 static void showWelcomePage(void)
285 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
287 tfp_sprintf(lineBuffer, "v%s (%s)", FC_VERSION_STRING, shortGitRevision);
288 i2c_OLED_set_line(dev, rowIndex++);
289 i2c_OLED_send_string(dev, lineBuffer);
291 i2c_OLED_set_line(dev, rowIndex++);
292 i2c_OLED_send_string(dev, targetName);
295 static void showArmedPage(void)
299 static void showProfilePage(void)
301 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
303 tfp_sprintf(lineBuffer, "Profile: %d", getCurrentPidProfileIndex());
304 i2c_OLED_set_line(dev, rowIndex++);
305 i2c_OLED_send_string(dev, lineBuffer);
307 static const char* const axisTitles[3] = {"ROL", "PIT", "YAW"};
308 const pidProfile_t *pidProfile = currentPidProfile;
309 for (int axis = 0; axis < 3; ++axis) {
310 tfp_sprintf(lineBuffer, "%s P:%3d I:%3d D:%3d",
311 axisTitles[axis],
312 pidProfile->pid[axis].P,
313 pidProfile->pid[axis].I,
314 pidProfile->pid[axis].D
316 padLineBuffer();
317 i2c_OLED_set_line(dev, rowIndex++);
318 i2c_OLED_send_string(dev, lineBuffer);
322 static void showRateProfilePage(void)
324 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
326 const uint8_t currentRateProfileIndex = getCurrentControlRateProfileIndex();
327 tfp_sprintf(lineBuffer, "Rate profile: %d", currentRateProfileIndex);
328 i2c_OLED_set_line(dev, rowIndex++);
329 i2c_OLED_send_string(dev, lineBuffer);
331 const controlRateConfig_t *controlRateConfig = controlRateProfiles(currentRateProfileIndex);
333 tfp_sprintf(lineBuffer, " R P Y");
334 padLineBuffer();
335 i2c_OLED_set_line(dev, rowIndex++);
336 i2c_OLED_send_string(dev, lineBuffer);
338 tfp_sprintf(lineBuffer, "RcRate %3d %3d %3d",
339 controlRateConfig->rcRates[FD_ROLL],
340 controlRateConfig->rcRates[FD_PITCH],
341 controlRateConfig->rcRates[FD_YAW]
343 padLineBuffer();
344 i2c_OLED_set_line(dev, rowIndex++);
345 i2c_OLED_send_string(dev, lineBuffer);
347 tfp_sprintf(lineBuffer, "Super %3d %3d %3d",
348 controlRateConfig->rates[FD_ROLL],
349 controlRateConfig->rates[FD_PITCH],
350 controlRateConfig->rates[FD_YAW]
352 padLineBuffer();
353 i2c_OLED_set_line(dev, rowIndex++);
354 i2c_OLED_send_string(dev, lineBuffer);
356 tfp_sprintf(lineBuffer, "Expo %3d %3d %3d",
357 controlRateConfig->rcExpo[FD_ROLL],
358 controlRateConfig->rcExpo[FD_PITCH],
359 controlRateConfig->rcExpo[FD_YAW]
361 padLineBuffer();
362 i2c_OLED_set_line(dev, rowIndex++);
363 i2c_OLED_send_string(dev, lineBuffer);
366 #define SATELLITE_COUNT ARRAYLEN(GPS_svinfo)
367 #define SATELLITE_GRAPH_LEFT_OFFSET ((SCREEN_CHARACTER_COLUMN_COUNT - SATELLITE_COUNT) / 2)
369 #ifdef USE_GPS
370 static void showGpsPage(void)
372 if (!featureIsEnabled(FEATURE_GPS)) {
373 pageState.pageFlags |= PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
374 return;
377 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
379 static uint8_t gpsTicker = 0;
380 static uint32_t lastGPSSvInfoReceivedCount = 0;
381 if (dashboardGpsNavSvInfoRcvCount != lastGPSSvInfoReceivedCount) {
382 lastGPSSvInfoReceivedCount = dashboardGpsNavSvInfoRcvCount;
383 gpsTicker++;
384 gpsTicker = gpsTicker % TICKER_CHARACTER_COUNT;
387 i2c_OLED_set_xy(dev, 0, rowIndex);
388 i2c_OLED_send_char(dev, tickerCharacters[gpsTicker]);
390 i2c_OLED_set_xy(dev, MAX(0, (uint8_t)SATELLITE_GRAPH_LEFT_OFFSET), rowIndex++);
392 uint32_t index;
393 for (index = 0; index < SATELLITE_COUNT && index < SCREEN_CHARACTER_COLUMN_COUNT; index++) {
394 uint8_t bargraphOffset = ((uint16_t) GPS_svinfo[index].cno * VERTICAL_BARGRAPH_CHARACTER_COUNT) / (GPS_DBHZ_MAX - 1);
395 bargraphOffset = MIN(bargraphOffset, VERTICAL_BARGRAPH_CHARACTER_COUNT - 1);
396 i2c_OLED_send_char(dev, VERTICAL_BARGRAPH_ZERO_CHARACTER + bargraphOffset);
399 char fixChar = STATE(GPS_FIX) ? 'Y' : 'N';
400 tfp_sprintf(lineBuffer, "Sats: %d Fix: %c", gpsSol.numSat, fixChar);
401 padLineBuffer();
402 i2c_OLED_set_line(dev, rowIndex++);
403 i2c_OLED_send_string(dev, lineBuffer);
405 tfp_sprintf(lineBuffer, "La/Lo: %d/%d", gpsSol.llh.lat / GPS_DEGREES_DIVIDER, gpsSol.llh.lon / GPS_DEGREES_DIVIDER);
406 padLineBuffer();
407 i2c_OLED_set_line(dev, rowIndex++);
408 i2c_OLED_send_string(dev, lineBuffer);
410 tfp_sprintf(lineBuffer, "Spd: %d", gpsSol.groundSpeed);
411 padHalfLineBuffer();
412 i2c_OLED_set_line(dev, rowIndex);
413 i2c_OLED_send_string(dev, lineBuffer);
415 tfp_sprintf(lineBuffer, "GC: %d", gpsSol.groundCourse);
416 padHalfLineBuffer();
417 i2c_OLED_set_xy(dev, HALF_SCREEN_CHARACTER_COLUMN_COUNT, rowIndex++);
418 i2c_OLED_send_string(dev, lineBuffer);
420 tfp_sprintf(lineBuffer, "RX: %d", dashboardGpsPacketCount);
421 padHalfLineBuffer();
422 i2c_OLED_set_line(dev, rowIndex);
423 i2c_OLED_send_string(dev, lineBuffer);
425 tfp_sprintf(lineBuffer, "ERRs: %d", gpsData.errors);
426 padHalfLineBuffer();
427 i2c_OLED_set_xy(dev, HALF_SCREEN_CHARACTER_COLUMN_COUNT, rowIndex++);
428 i2c_OLED_send_string(dev, lineBuffer);
430 tfp_sprintf(lineBuffer, "Dt: %d", gpsSol.navIntervalMs);
431 padHalfLineBuffer();
432 i2c_OLED_set_line(dev, rowIndex);
433 i2c_OLED_send_string(dev, lineBuffer);
435 tfp_sprintf(lineBuffer, "TOs: %d", gpsData.timeouts);
436 padHalfLineBuffer();
437 i2c_OLED_set_xy(dev, HALF_SCREEN_CHARACTER_COLUMN_COUNT, rowIndex++);
438 i2c_OLED_send_string(dev, lineBuffer);
440 strncpy(lineBuffer, dashboardGpsPacketLog, GPS_PACKET_LOG_ENTRY_COUNT);
441 padHalfLineBuffer();
442 i2c_OLED_set_line(dev, rowIndex++);
443 i2c_OLED_send_string(dev, lineBuffer);
445 #endif
447 static void showBatteryPage(void)
449 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
451 if (batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE) {
452 tfp_sprintf(lineBuffer, "Volts: %d.%02d Cells: %d", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount());
453 padLineBuffer();
454 i2c_OLED_set_line(dev, rowIndex++);
455 i2c_OLED_send_string(dev, lineBuffer);
457 uint8_t batteryPercentage = calculateBatteryPercentageRemaining();
458 i2c_OLED_set_line(dev, rowIndex++);
459 drawHorizonalPercentageBar(SCREEN_CHARACTER_COLUMN_COUNT, batteryPercentage);
462 if (batteryConfig()->currentMeterSource != CURRENT_METER_NONE) {
464 int32_t amperage = getAmperage();
465 // 123456789012345678901
466 // Amp: DDD.D mAh: DDDDD
467 tfp_sprintf(lineBuffer, "Amp: %d.%d mAh: %d", amperage / 100, (amperage % 100) / 10, getMAhDrawn());
468 padLineBuffer();
469 i2c_OLED_set_line(dev, rowIndex++);
470 i2c_OLED_send_string(dev, lineBuffer);
472 uint8_t capacityPercentage = calculateBatteryPercentageRemaining();
473 i2c_OLED_set_line(dev, rowIndex++);
474 drawHorizonalPercentageBar(SCREEN_CHARACTER_COLUMN_COUNT, capacityPercentage);
478 static void showSensorsPage(void)
480 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
481 static const char *format = "%s %5d %5d %5d";
483 i2c_OLED_set_line(dev, rowIndex++);
484 i2c_OLED_send_string(dev, " X Y Z");
486 #if defined(USE_ACC)
487 if (sensors(SENSOR_ACC)) {
488 tfp_sprintf(lineBuffer, format, "ACC", lrintf(acc.accADC.x), lrintf(acc.accADC.y), lrintf(acc.accADC.z));
489 padLineBuffer();
490 i2c_OLED_set_line(dev, rowIndex++);
491 i2c_OLED_send_string(dev, lineBuffer);
493 #endif
495 if (sensors(SENSOR_GYRO)) {
496 tfp_sprintf(lineBuffer, format, "GYR", lrintf(gyro.gyroADCf[X]), lrintf(gyro.gyroADCf[Y]), lrintf(gyro.gyroADCf[Z]));
497 padLineBuffer();
498 i2c_OLED_set_line(dev, rowIndex++);
499 i2c_OLED_send_string(dev, lineBuffer);
502 #ifdef USE_MAG
503 if (sensors(SENSOR_MAG)) {
504 tfp_sprintf(lineBuffer, format, "MAG", lrintf(mag.magADC.x), lrintf(mag.magADC.y), lrintf(mag.magADC.z));
505 padLineBuffer();
506 i2c_OLED_set_line(dev, rowIndex++);
507 i2c_OLED_send_string(dev, lineBuffer);
509 #endif
511 tfp_sprintf(lineBuffer, format, "I&H", attitude.values.roll, attitude.values.pitch, DECIDEGREES_TO_DEGREES(attitude.values.yaw));
512 padLineBuffer();
513 i2c_OLED_set_line(dev, rowIndex++);
514 i2c_OLED_send_string(dev, lineBuffer);
517 uint8_t length;
519 ftoa(EstG.A[X], lineBuffer);
520 length = strlen(lineBuffer);
521 while (length < HALF_SCREEN_CHARACTER_COLUMN_COUNT) {
522 lineBuffer[length++] = ' ';
523 lineBuffer[length+1] = 0;
525 ftoa(EstG.A[Y], lineBuffer + length);
526 padLineBuffer();
527 i2c_OLED_set_line(dev, rowIndex++);
528 i2c_OLED_send_string(dev, lineBuffer);
530 ftoa(EstG.A[Z], lineBuffer);
531 length = strlen(lineBuffer);
532 while (length < HALF_SCREEN_CHARACTER_COLUMN_COUNT) {
533 lineBuffer[length++] = ' ';
534 lineBuffer[length+1] = 0;
536 ftoa(smallAngle, lineBuffer + length);
537 padLineBuffer();
538 i2c_OLED_set_line(dev, rowIndex++);
539 i2c_OLED_send_string(dev, lineBuffer);
544 static void showTasksPage(void)
546 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
547 static const char *format = "%2d%6d%5d%4d%4d";
549 i2c_OLED_set_line(dev, rowIndex++);
550 i2c_OLED_send_string(dev, "Task max avg mx% av%");
551 taskInfo_t taskInfo;
552 for (taskId_e taskId = 0; taskId < TASK_COUNT; ++taskId) {
553 getTaskInfo(taskId, &taskInfo);
554 if (taskInfo.isEnabled && taskId != TASK_SERIAL) {// don't waste a line of the display showing serial taskInfo
555 const int taskFrequency = (int)(1000000.0f / ((float)taskInfo.latestDeltaTimeUs));
556 const int maxLoad = taskInfo.maxExecutionTimeUs == 0 ? 0 : (taskInfo.maxExecutionTimeUs * taskFrequency) / 1000;
557 const int averageLoad = taskInfo.averageExecutionTime10thUs == 0 ? 0 : (taskInfo.averageExecutionTime10thUs * taskFrequency) / 10000;
558 tfp_sprintf(lineBuffer, format, taskId, taskInfo.maxExecutionTimeUs, taskInfo.averageExecutionTime10thUs / 10, maxLoad, averageLoad);
559 padLineBuffer();
560 i2c_OLED_set_line(dev, rowIndex++);
561 i2c_OLED_send_string(dev, lineBuffer);
562 if (rowIndex > SCREEN_CHARACTER_ROW_COUNT) {
563 break;
569 #ifdef USE_BLACKBOX
570 static void showBBPage(void)
572 uint8_t rowIndex = PAGE_TITLE_LINE_COUNT;
573 int8_t fileNo;
574 if (isBlackboxDeviceWorking()) {
575 switch (blackboxConfig()->device) {
576 case BLACKBOX_DEVICE_SDCARD:
577 fileNo = blackboxGetLogFileNo();
578 if( fileNo > 0) {
579 tfp_sprintf(lineBuffer, "File no: %d", fileNo);
580 } else {
581 tfp_sprintf(lineBuffer, "Not ready yet");
583 break;
584 default:
585 tfp_sprintf(lineBuffer, "Not supp. dev.");
586 break;
588 } else {
589 tfp_sprintf(lineBuffer, "BB not working");
592 padLineBuffer();
593 i2c_OLED_set_line(dev, rowIndex++);
594 i2c_OLED_send_string(dev, lineBuffer);
596 #endif
598 #ifdef ENABLE_DEBUG_DASHBOARD_PAGE
600 static void showDebugPage(void)
602 for (int rowIndex = 0; rowIndex < 4; rowIndex++) {
603 tfp_sprintf(lineBuffer, "%d = %5d", rowIndex, debug[rowIndex]);
604 padLineBuffer();
605 i2c_OLED_set_line(dev, rowIndex + PAGE_TITLE_LINE_COUNT);
606 i2c_OLED_send_string(dev, lineBuffer);
609 #endif
611 static const pageEntry_t pages[PAGE_COUNT] = {
612 { PAGE_WELCOME, FC_FIRMWARE_NAME, showWelcomePage, PAGE_FLAGS_SKIP_CYCLING },
613 { PAGE_ARMED, "ARMED", showArmedPage, PAGE_FLAGS_SKIP_CYCLING },
614 { PAGE_PROFILE, "PROFILE", showProfilePage, PAGE_FLAGS_NONE },
615 { PAGE_RPROF, "RATE PROFILE", showRateProfilePage,PAGE_FLAGS_NONE },
616 #ifdef USE_GPS
617 { PAGE_GPS, "GPS", showGpsPage, PAGE_FLAGS_NONE },
618 #endif
619 { PAGE_RX, "RX", showRxPage, PAGE_FLAGS_NONE },
620 { PAGE_BATTERY, "BATTERY", showBatteryPage, PAGE_FLAGS_NONE },
621 { PAGE_SENSORS, "SENSORS", showSensorsPage, PAGE_FLAGS_NONE },
622 { PAGE_TASKS, "TASKS", showTasksPage, PAGE_FLAGS_NONE },
623 #ifdef USE_BLACKBOX
624 { PAGE_BB, "BLACK BOX", showBBPage, PAGE_FLAGS_NONE },
625 #endif
626 #ifdef ENABLE_DEBUG_DASHBOARD_PAGE
627 { PAGE_DEBUG, "DEBUG", showDebugPage, PAGE_FLAGS_NONE },
628 #endif
631 void dashboardSetPage(pageId_e pageId)
633 for (int i = 0; i < PAGE_COUNT; i++) {
634 const pageEntry_t *candidatePage = &pages[i];
635 if (candidatePage->id == pageId) {
636 pageState.page = candidatePage;
639 pageState.pageFlags |= PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
642 void dashboardUpdate(timeUs_t currentTimeUs)
644 static uint8_t previousArmedState = 0;
646 #ifdef USE_CMS
647 if (displayIsGrabbed(displayPort)) {
648 return;
650 #endif
652 const bool updateNow = (int32_t)(currentTimeUs - nextDisplayUpdateAt) >= 0L;
653 if (!updateNow) {
654 return;
657 nextDisplayUpdateAt = currentTimeUs + DISPLAY_UPDATE_FREQUENCY;
659 bool armedState = ARMING_FLAG(ARMED) ? true : false;
660 bool armedStateChanged = armedState != previousArmedState;
661 previousArmedState = armedState;
663 if (armedState) {
664 if (!armedStateChanged) {
665 return;
667 dashboardSetPage(PAGE_ARMED);
668 pageState.pageChanging = true;
669 } else {
670 if (armedStateChanged) {
671 pageState.pageFlags |= PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
674 pageState.pageChanging = (pageState.pageFlags & PAGE_STATE_FLAG_FORCE_PAGE_CHANGE) ||
675 (((int32_t)(currentTimeUs - pageState.nextPageAt) >= 0L && (pageState.pageFlags & PAGE_STATE_FLAG_CYCLE_ENABLED)));
676 if (pageState.pageChanging && (pageState.pageFlags & PAGE_STATE_FLAG_CYCLE_ENABLED)) {
678 do {
679 pageState.cycleIndex++;
680 pageState.cycleIndex = pageState.cycleIndex % PAGE_COUNT;
681 pageState.page = &pages[pageState.cycleIndex];
682 } while (pageState.page->flags & PAGE_FLAGS_SKIP_CYCLING);
686 if (pageState.pageChanging) {
687 pageState.pageFlags &= ~PAGE_STATE_FLAG_FORCE_PAGE_CHANGE;
688 pageState.nextPageAt = currentTimeUs + PAGE_CYCLE_FREQUENCY;
690 // Some OLED displays do not respond on the first initialisation so refresh the display
691 // when the page changes in the hopes the hardware responds. This also allows the
692 // user to power off/on the display or connect it while powered.
693 resetDisplay();
695 if (!dashboardPresent) {
696 return;
698 handlePageChange();
701 if (!dashboardPresent) {
702 return;
705 pageState.page->drawFn();
707 if (!armedState) {
708 updateFailsafeStatus();
709 updateRxStatus();
710 updateTicker();
715 void dashboardInit(void)
717 static extDevice_t dashBoardDev;
718 // TODO Use bus singleton
719 static busDevice_t dashBoardBus;
721 dashBoardDev.bus = &dashBoardBus;
723 dashBoardBus.busType_u.i2c.device = I2C_CFG_TO_DEV(dashboardConfig()->device);
724 dashBoardDev.busType_u.i2c.address = dashboardConfig()->address;
725 dev = &dashBoardDev;
727 delay(200);
728 resetDisplay();
729 delay(200);
731 displayPort = displayPortOledInit(dev);
732 #if defined(USE_CMS)
733 if (dashboardPresent) {
734 cmsDisplayPortRegister(displayPort);
736 #endif
738 memset(&pageState, 0, sizeof(pageState));
739 dashboardSetPage(PAGE_WELCOME);
741 uint32_t now = micros();
742 dashboardUpdate(now);
744 dashboardSetNextPageChangeAt(now + PAGE_CYCLE_FREQUENCY);
747 void dashboardShowFixedPage(pageId_e pageId)
749 dashboardSetPage(pageId);
750 dashboardDisablePageCycling();
753 void dashboardSetNextPageChangeAt(timeUs_t futureMicros)
755 pageState.nextPageAt = futureMicros;
758 void dashboardEnablePageCycling(void)
760 pageState.pageFlags |= PAGE_STATE_FLAG_CYCLE_ENABLED;
763 void dashboardResetPageCycling(void)
765 pageState.cycleIndex = PAGE_COUNT - 1; // start at first page
769 void dashboardDisablePageCycling(void)
771 pageState.pageFlags &= ~PAGE_STATE_FLAG_CYCLE_ENABLED;
773 #endif // USE_DASHBOARD