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)
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/>.
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"
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"
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"
63 #include "io/dashboard.h"
64 #include "io/displayport_oled.h"
66 #include "blackbox/blackbox_io.h"
67 #include "blackbox/blackbox.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
{
110 static const char tickerCharacters
[] = "|/-\\"; // use 2/4/8 characters so that the divide is optimal.
111 #define TICKER_CHARACTER_COUNT ARRAYLEN(tickerCharacters)
114 PAGE_STATE_FLAG_NONE
= 0,
115 PAGE_STATE_FLAG_CYCLE_ENABLED
= (1 << 0),
116 PAGE_STATE_FLAG_FORCE_PAGE_CHANGE
= (1 << 1),
119 typedef struct pageState_s
{
121 const pageEntry_t
*page
;
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;
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;
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
)
168 j
= (width
* percent
) / 100;
170 for (i
= 0; i
< j
; i
++)
171 LCDprint(159); // full
174 LCDprint(154 + (percent
* width
* 5 / 100 - 5 * j
)); // partial fill
176 for (i
= j
+ 1; i
< width
; i
++)
177 LCDprint(154); // empty
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
);
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
]);
198 tickerIndex
= tickerIndex
% TICKER_CHARACTER_COUNT
;
201 static void updateRxStatus(void)
203 i2c_OLED_set_xy(dev
, SCREEN_CHARACTER_COLUMN_COUNT
- 2, 0);
205 if (isRxReceivingSignal()) {
207 } if (rxAreFlightChannelsValid()) {
210 i2c_OLED_send_char(dev
, rxStatus
);
213 static void updateFailsafeStatus(void)
215 char failsafeIndicator
= '?';
216 switch (failsafePhase()) {
218 failsafeIndicator
= '-';
220 case FAILSAFE_RX_LOSS_DETECTED
:
221 failsafeIndicator
= 'R';
223 case FAILSAFE_LANDING
:
224 failsafeIndicator
= 'l';
226 case FAILSAFE_LANDED
:
227 failsafeIndicator
= 'L';
229 case FAILSAFE_RX_LOSS_MONITORING
:
230 failsafeIndicator
= 'M';
232 case FAILSAFE_RX_LOSS_RECOVERED
:
233 failsafeIndicator
= 'r';
235 case FAILSAFE_GPS_RESCUE
:
236 failsafeIndicator
= 'G';
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
);
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
) {
275 if (IS_SCREEN_CHARACTER_COLUMN_COUNT_ODD
) {
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",
312 pidProfile
->pid
[axis
].P
,
313 pidProfile
->pid
[axis
].I
,
314 pidProfile
->pid
[axis
].D
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");
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
]
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
]
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
]
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)
370 static void showGpsPage(void)
372 if (!featureIsEnabled(FEATURE_GPS
)) {
373 pageState
.pageFlags
|= PAGE_STATE_FLAG_FORCE_PAGE_CHANGE
;
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
;
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
++);
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
);
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
);
407 i2c_OLED_set_line(dev
, rowIndex
++);
408 i2c_OLED_send_string(dev
, lineBuffer
);
410 tfp_sprintf(lineBuffer
, "Spd: %d", gpsSol
.groundSpeed
);
412 i2c_OLED_set_line(dev
, rowIndex
);
413 i2c_OLED_send_string(dev
, lineBuffer
);
415 tfp_sprintf(lineBuffer
, "GC: %d", gpsSol
.groundCourse
);
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
);
422 i2c_OLED_set_line(dev
, rowIndex
);
423 i2c_OLED_send_string(dev
, lineBuffer
);
425 tfp_sprintf(lineBuffer
, "ERRs: %d", gpsData
.errors
);
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
);
432 i2c_OLED_set_line(dev
, rowIndex
);
433 i2c_OLED_send_string(dev
, lineBuffer
);
435 tfp_sprintf(lineBuffer
, "TOs: %d", gpsData
.timeouts
);
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
);
442 i2c_OLED_set_line(dev
, rowIndex
++);
443 i2c_OLED_send_string(dev
, lineBuffer
);
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());
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());
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");
487 if (sensors(SENSOR_ACC
)) {
488 tfp_sprintf(lineBuffer
, format
, "ACC", lrintf(acc
.accADC
.x
), lrintf(acc
.accADC
.y
), lrintf(acc
.accADC
.z
));
490 i2c_OLED_set_line(dev
, rowIndex
++);
491 i2c_OLED_send_string(dev
, lineBuffer
);
495 if (sensors(SENSOR_GYRO
)) {
496 tfp_sprintf(lineBuffer
, format
, "GYR", lrintf(gyro
.gyroADCf
[X
]), lrintf(gyro
.gyroADCf
[Y
]), lrintf(gyro
.gyroADCf
[Z
]));
498 i2c_OLED_set_line(dev
, rowIndex
++);
499 i2c_OLED_send_string(dev
, lineBuffer
);
503 if (sensors(SENSOR_MAG
)) {
504 tfp_sprintf(lineBuffer
, format
, "MAG", lrintf(mag
.magADC
.x
), lrintf(mag
.magADC
.y
), lrintf(mag
.magADC
.z
));
506 i2c_OLED_set_line(dev
, rowIndex
++);
507 i2c_OLED_send_string(dev
, lineBuffer
);
511 tfp_sprintf(lineBuffer
, format
, "I&H", attitude
.values
.roll
, attitude
.values
.pitch
, DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
513 i2c_OLED_set_line(dev
, rowIndex
++);
514 i2c_OLED_send_string(dev
, lineBuffer
);
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);
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);
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%");
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
);
560 i2c_OLED_set_line(dev
, rowIndex
++);
561 i2c_OLED_send_string(dev
, lineBuffer
);
562 if (rowIndex
> SCREEN_CHARACTER_ROW_COUNT
) {
570 static void showBBPage(void)
572 uint8_t rowIndex
= PAGE_TITLE_LINE_COUNT
;
574 if (isBlackboxDeviceWorking()) {
575 switch (blackboxConfig()->device
) {
576 case BLACKBOX_DEVICE_SDCARD
:
577 fileNo
= blackboxGetLogFileNo();
579 tfp_sprintf(lineBuffer
, "File no: %d", fileNo
);
581 tfp_sprintf(lineBuffer
, "Not ready yet");
585 tfp_sprintf(lineBuffer
, "Not supp. dev.");
589 tfp_sprintf(lineBuffer
, "BB not working");
593 i2c_OLED_set_line(dev
, rowIndex
++);
594 i2c_OLED_send_string(dev
, lineBuffer
);
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
]);
605 i2c_OLED_set_line(dev
, rowIndex
+ PAGE_TITLE_LINE_COUNT
);
606 i2c_OLED_send_string(dev
, lineBuffer
);
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
},
617 { PAGE_GPS
, "GPS", showGpsPage
, PAGE_FLAGS_NONE
},
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
},
624 { PAGE_BB
, "BLACK BOX", showBBPage
, PAGE_FLAGS_NONE
},
626 #ifdef ENABLE_DEBUG_DASHBOARD_PAGE
627 { PAGE_DEBUG
, "DEBUG", showDebugPage
, PAGE_FLAGS_NONE
},
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;
647 if (displayIsGrabbed(displayPort
)) {
652 const bool updateNow
= (int32_t)(currentTimeUs
- nextDisplayUpdateAt
) >= 0L;
657 nextDisplayUpdateAt
= currentTimeUs
+ DISPLAY_UPDATE_FREQUENCY
;
659 bool armedState
= ARMING_FLAG(ARMED
) ? true : false;
660 bool armedStateChanged
= armedState
!= previousArmedState
;
661 previousArmedState
= armedState
;
664 if (!armedStateChanged
) {
667 dashboardSetPage(PAGE_ARMED
);
668 pageState
.pageChanging
= true;
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
)) {
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.
695 if (!dashboardPresent
) {
701 if (!dashboardPresent
) {
705 pageState
.page
->drawFn();
708 updateFailsafeStatus();
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
;
731 displayPort
= displayPortOledInit(dev
);
733 if (dashboardPresent
) {
734 cmsDisplayPortRegister(displayPort
);
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