1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-23 16:25:31 +03:00

Track state execution time for OSD, baro, rx and GPS tasks and inform scheduler of next state execution time

This commit is contained in:
Steve Evans 2020-08-14 16:42:20 +01:00 committed by Steve Evans
parent a63172cc1f
commit ab1baccc66
44 changed files with 1392 additions and 721 deletions

View file

@ -188,6 +188,14 @@ const osd_stats_e osdStatsDisplayOrder[OSD_STAT_COUNT] = {
OSD_STAT_TOTAL_DIST,
};
// Group elements in a number of groups to reduce task scheduling overhead
#define OSD_GROUP_COUNT 20
// Aim to render a group of elements within a target time
#define OSD_ELEMENT_RENDER_TARGET 40
// Allow a margin by which a group render can exceed that of the sum of the elements before declaring insane
// This will most likely be violated by a USB interrupt whilst using the CLI
#define OSD_ELEMENT_RENDER_GROUP_MARGIN 5
// Format a float to the specified number of decimal places with optional rounding.
// OSD symbols can optionally be placed before and after the formatted number (use SYM_NONE for no symbol).
// The formatString can be used for customized formatting of the integer part. Follow the printf style.
@ -291,27 +299,6 @@ void osdAnalyzeActiveElements(void)
osdDrawActiveElementsBackground(osdDisplayPort);
}
static void osdDrawElements(void)
{
// Hide OSD when OSDSW mode is active
if (IS_RC_MODE_ACTIVE(BOXOSD)) {
displayClearScreen(osdDisplayPort);
return;
}
if (backgroundLayerSupported) {
// Background layer is supported, overlay it onto the foreground
// so that we only need to draw the active parts of the elements.
displayLayerCopy(osdDisplayPort, DISPLAYPORT_LAYER_FOREGROUND, DISPLAYPORT_LAYER_BACKGROUND);
} else {
// Background layer not supported, just clear the foreground in preparation
// for drawing the elements including their backgrounds.
displayClearScreen(osdDisplayPort);
}
osdDrawActiveElements(osdDisplayPort);
}
const uint16_t osdTimerDefault[OSD_TIMER_COUNT] = {
OSD_TIMER(OSD_TIMER_SRC_ON, OSD_TIMER_PREC_SECOND, 10),
OSD_TIMER(OSD_TIMER_SRC_TOTAL_ARMED, OSD_TIMER_PREC_SECOND, 10)
@ -382,7 +369,7 @@ void pgResetFn_osdConfig(osdConfig_t *osdConfig)
osdConfig->camera_frame_height = 11;
osdConfig->stat_show_cell_value = false;
osdConfig->task_frequency = OSD_TASK_FREQUENCY_DEFAULT;
osdConfig->framerate_hz = OSD_FRAMERATE_DEFAULT_HZ;
osdConfig->cms_background_type = DISPLAY_BACKGROUND_TRANSPARENT;
}
@ -457,7 +444,6 @@ static void osdCompleteInitialization(void)
osdElementsInit(backgroundLayerSupported);
osdAnalyzeActiveElements();
displayCommitTransaction(osdDisplayPort);
osdIsReady = true;
}
@ -474,10 +460,6 @@ void osdInit(displayPort_t *osdDisplayPortToUse, osdDisplayPortDevice_e displayP
#ifdef USE_CMS
cmsDisplayPortRegister(osdDisplayPort);
#endif
if (displayCheckReady(osdDisplayPort, true)) {
osdCompleteInitialization();
}
}
static void osdResetStats(void)
@ -936,11 +918,12 @@ static timeDelta_t osdShowArmed(void)
return ret;
}
STATIC_UNIT_TESTED void osdRefresh(timeUs_t currentTimeUs)
static bool osdStatsVisible = false;
static bool osdStatsEnabled = false;
STATIC_UNIT_TESTED void osdDrawStats1(timeUs_t currentTimeUs)
{
static timeUs_t lastTimeUs = 0;
static bool osdStatsEnabled = false;
static bool osdStatsVisible = false;
static timeUs_t osdStatsRefreshTimeUs;
// detect arm/disarm
@ -963,7 +946,6 @@ STATIC_UNIT_TESTED void osdRefresh(timeUs_t currentTimeUs)
armState = ARMING_FLAG(ARMED);
}
if (ARMING_FLAG(ARMED)) {
osdUpdateStats();
timeUs_t deltaT = currentTimeUs - lastTimeUs;
@ -991,7 +973,10 @@ STATIC_UNIT_TESTED void osdRefresh(timeUs_t currentTimeUs)
}
}
lastTimeUs = currentTimeUs;
}
void osdDrawStats2(timeUs_t currentTimeUs)
{
displayBeginTransaction(osdDisplayPort, DISPLAY_TRANSACTION_OPT_RESET_DRAWING);
if (resumeRefreshAt) {
@ -1000,7 +985,6 @@ STATIC_UNIT_TESTED void osdRefresh(timeUs_t currentTimeUs)
if (IS_HI(THROTTLE) || IS_HI(PITCH)) {
resumeRefreshAt = currentTimeUs;
}
displayHeartbeat(osdDisplayPort);
return;
} else {
displayClearScreen(osdDisplayPort);
@ -1009,13 +993,15 @@ STATIC_UNIT_TESTED void osdRefresh(timeUs_t currentTimeUs)
stats.armed_time = 0;
}
}
#ifdef USE_ESC_SENSOR
if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
osdEscDataCombined = getEscSensorData(ESC_SENSOR_COMBINED);
}
#endif
}
void osdDrawStats3()
{
#if defined(USE_ACC)
if (sensors(SENSOR_ACC)
&& (VISIBLE(osdElementConfig()->item_pos[OSD_G_FORCE]) || osdStatGetState(OSD_STAT_MAX_G_FORCE))) {
@ -1027,79 +1013,311 @@ STATIC_UNIT_TESTED void osdRefresh(timeUs_t currentTimeUs)
osdGForce = sqrtf(osdGForce) * acc.dev.acc_1G_rec;
}
#endif
#ifdef USE_CMS
if (!displayIsGrabbed(osdDisplayPort))
#endif
{
osdUpdateAlarms();
osdDrawElements();
displayHeartbeat(osdDisplayPort);
}
displayCommitTransaction(osdDisplayPort);
}
/*
* Called periodically by the scheduler
*/
typedef enum {
OSD_STATE_INIT,
OSD_STATE_IDLE,
OSD_STATE_CHECK,
OSD_STATE_UPDATE_STATS1,
OSD_STATE_UPDATE_STATS2,
OSD_STATE_UPDATE_STATS3,
OSD_STATE_UPDATE_ALARMS,
OSD_STATE_UPDATE_CANVAS,
OSD_STATE_UPDATE_ELEMENTS,
OSD_STATE_UPDATE_HEARTBEAT,
OSD_STATE_COMMIT,
OSD_STATE_TRANSFER,
OSD_STATE_COUNT
} osdState_e;
osdState_e osdState = OSD_STATE_INIT;
#define OSD_UPDATE_INTERVAL_US (1000000 / osdConfig()->framerate_hz)
// Called periodically by the scheduler
bool osdUpdateCheck(timeUs_t currentTimeUs, timeDelta_t currentDeltaTimeUs)
{
UNUSED(currentDeltaTimeUs);
static timeUs_t osdUpdateDueUs = 0;
if (osdState == OSD_STATE_IDLE) {
// If the OSD is due a refresh, mark that as being the case
if (cmpTimeUs(currentTimeUs, osdUpdateDueUs) > 0) {
osdState = OSD_STATE_CHECK;
// Determine time of next update
if (osdUpdateDueUs) {
osdUpdateDueUs += OSD_UPDATE_INTERVAL_US;
} else {
osdUpdateDueUs = currentTimeUs + OSD_UPDATE_INTERVAL_US;
}
}
}
return (osdState != OSD_STATE_IDLE);
}
// Called when there is OSD update work to be done
void osdUpdate(timeUs_t currentTimeUs)
{
static uint32_t counter = 0;
static timeUs_t osdStateDurationUs[OSD_STATE_COUNT] = { 0 };
static timeUs_t osdElementDurationUs[OSD_ITEM_COUNT] = { 0 };
static timeUs_t osdElementGroupMembership[OSD_ITEM_COUNT];
static timeUs_t osdElementGroupTargetUs[OSD_GROUP_COUNT] = { 0 };
static timeUs_t osdElementGroupDurationUs[OSD_GROUP_COUNT] = { 0 };
static uint8_t osdElementGroup;
static bool firstPass = true;
uint8_t osdCurElementGroup = 0;
timeUs_t executeTimeUs;
osdState_e osdCurState = osdState;
if (!osdIsReady) {
if (osdState != OSD_STATE_UPDATE_CANVAS) {
ignoreTaskExecRate();
}
switch (osdState) {
case OSD_STATE_INIT:
if (!displayCheckReady(osdDisplayPort, false)) {
// Frsky osd need a display redraw after search for MAX7456 devices
if (osdDisplayPortDeviceType == OSD_DISPLAYPORT_DEVICE_FRSKYOSD) {
displayRedraw(osdDisplayPort);
} else {
ignoreTaskShortExecTime();
ignoreTaskExecTime();
}
return;
}
osdCompleteInitialization();
}
displayRedraw(osdDisplayPort);
osdState = OSD_STATE_COMMIT;
if (isBeeperOn()) {
showVisualBeeper = true;
}
break;
// don't touch buffers if DMA transaction is in progress
if (displayIsTransferInProgress(osdDisplayPort)) {
ignoreTaskShortExecTime();
return;
}
case OSD_STATE_CHECK:
if (isBeeperOn()) {
showVisualBeeper = true;
}
#ifdef USE_SLOW_MSP_DISPLAYPORT_RATE_WHEN_UNARMED
static uint32_t idlecounter = 0;
if (!ARMING_FLAG(ARMED)) {
if (idlecounter++ % 4 != 0) {
ignoreTaskShortExecTime();
// don't touch buffers if DMA transaction is in progress
if (displayIsTransferInProgress(osdDisplayPort)) {
break;
}
osdState = OSD_STATE_UPDATE_HEARTBEAT;
break;
case OSD_STATE_UPDATE_HEARTBEAT:
if (displayHeartbeat(osdDisplayPort)) {
// Extraordinary action was taken, so return without allowing osdStateDurationUs table to be updated
return;
}
}
#endif
// redraw values in buffer
if (counter % OSD_DRAW_FREQ_DENOM == 0) {
osdRefresh(currentTimeUs);
osdState = OSD_STATE_UPDATE_STATS1;
break;
case OSD_STATE_UPDATE_STATS1:
osdDrawStats1(currentTimeUs);
showVisualBeeper = false;
} else {
bool doDrawScreen = true;
#if defined(USE_CMS) && defined(USE_MSP_DISPLAYPORT) && defined(USE_OSD_OVER_MSP_DISPLAYPORT)
// For the MSP displayPort device only do the drawScreen once per
// logical OSD cycle as there is no output buffering needing to be flushed.
if (osdDisplayPortDeviceType == OSD_DISPLAYPORT_DEVICE_MSP) {
doDrawScreen = (counter % OSD_DRAW_FREQ_DENOM == 1);
}
osdState = OSD_STATE_UPDATE_STATS2;
break;
case OSD_STATE_UPDATE_STATS2:
osdDrawStats2(currentTimeUs);
osdState = OSD_STATE_UPDATE_STATS3;
break;
case OSD_STATE_UPDATE_STATS3:
osdDrawStats3();
#ifdef USE_CMS
if (!displayIsGrabbed(osdDisplayPort))
#endif
// Redraw a portion of the chars per idle to spread out the load and SPI bus utilization
if (doDrawScreen) {
displayDrawScreen(osdDisplayPort);
{
osdState = OSD_STATE_UPDATE_ALARMS;
break;
}
ignoreTaskShortExecTime();
osdState = OSD_STATE_COMMIT;
break;
case OSD_STATE_UPDATE_ALARMS:
osdUpdateAlarms();
if (resumeRefreshAt) {
osdState = OSD_STATE_IDLE;
} else {
osdState = OSD_STATE_UPDATE_CANVAS;
}
break;
case OSD_STATE_UPDATE_CANVAS:
// Hide OSD when OSDSW mode is active
if (IS_RC_MODE_ACTIVE(BOXOSD)) {
displayClearScreen(osdDisplayPort);
osdState = OSD_STATE_COMMIT;
break;
}
if (backgroundLayerSupported) {
// Background layer is supported, overlay it onto the foreground
// so that we only need to draw the active parts of the elements.
displayLayerCopy(osdDisplayPort, DISPLAYPORT_LAYER_FOREGROUND, DISPLAYPORT_LAYER_BACKGROUND);
} else {
// Background layer not supported, just clear the foreground in preparation
// for drawing the elements including their backgrounds.
displayClearScreen(osdDisplayPort);
}
#ifdef USE_GPS
static bool lastGpsSensorState;
// Handle the case that the GPS_SENSOR may be delayed in activation
// or deactivate if communication is lost with the module.
const bool currentGpsSensorState = sensors(SENSOR_GPS);
if (lastGpsSensorState != currentGpsSensorState) {
lastGpsSensorState = currentGpsSensorState;
osdAnalyzeActiveElements();
}
#endif // USE_GPS
osdSyncBlink();
uint8_t elementGroup;
uint8_t activeElements = osdGetActiveElementCount();
// Reset groupings
for (elementGroup = 0; elementGroup < OSD_GROUP_COUNT; elementGroup++) {
if (osdElementGroupDurationUs[elementGroup] > (osdElementGroupTargetUs[elementGroup] + OSD_ELEMENT_RENDER_GROUP_MARGIN)) {
osdElementGroupDurationUs[elementGroup] = 0;
}
osdElementGroupTargetUs[elementGroup] = 0;
}
elementGroup = 0;
// Based on the current element rendering, group to execute in approx 40us
for (uint8_t curElement = 0; curElement < activeElements; curElement++) {
if ((osdElementGroupTargetUs[elementGroup] == 0) ||
((osdElementGroupTargetUs[elementGroup] + osdElementDurationUs[curElement]) <= OSD_ELEMENT_RENDER_TARGET) ||
(elementGroup == (OSD_GROUP_COUNT - 1))) {
osdElementGroupTargetUs[elementGroup] += osdElementDurationUs[curElement];
// If group membership changes, reset the stats for the group
if (osdElementGroupMembership[curElement] != elementGroup) {
osdElementGroupDurationUs[elementGroup] = 0;
}
osdElementGroupMembership[curElement] = elementGroup;
} else {
elementGroup++;
// Try again for this element
curElement--;
}
}
// Start with group 0
osdElementGroup = 0;
if (activeElements > 0) {
osdState = OSD_STATE_UPDATE_ELEMENTS;
} else {
osdState = OSD_STATE_COMMIT;
}
break;
case OSD_STATE_UPDATE_ELEMENTS:
{
osdCurElementGroup = osdElementGroup;
bool moreElements = true;
do {
timeUs_t startElementTime = micros();
uint8_t osdCurElement = osdGetActiveElement();
// This element should be rendered in the next group
if (osdElementGroupMembership[osdCurElement] != osdElementGroup) {
osdElementGroup++;
break;
}
moreElements = osdDrawNextActiveElement(osdDisplayPort, currentTimeUs);
executeTimeUs = micros() - startElementTime;
if (executeTimeUs > osdElementDurationUs[osdCurElement]) {
osdElementDurationUs[osdCurElement] = executeTimeUs;
}
} while (moreElements);
if (moreElements) {
// There are more elements to draw
break;
}
osdElementGroup = 0;
osdState = OSD_STATE_COMMIT;
}
break;
case OSD_STATE_COMMIT:
displayCommitTransaction(osdDisplayPort);
if (resumeRefreshAt) {
osdState = OSD_STATE_IDLE;
} else {
osdState = OSD_STATE_TRANSFER;
}
break;
case OSD_STATE_TRANSFER:
// Wait for any current transfer to complete
if (displayIsTransferInProgress(osdDisplayPort)) {
break;
}
// Transfer may be broken into many parts
if (displayDrawScreen(osdDisplayPort)) {
break;
}
firstPass = false;
osdState = OSD_STATE_IDLE;
break;
case OSD_STATE_IDLE:
default:
osdState = OSD_STATE_IDLE;
break;
}
executeTimeUs = micros() - currentTimeUs;
// On the first pass no element groups will have been formed, so all elements will have been
// rendered which is unrepresentative, so ignore
if (!firstPass) {
if (osdCurState == OSD_STATE_UPDATE_ELEMENTS) {
if (executeTimeUs > osdElementGroupDurationUs[osdCurElementGroup]) {
osdElementGroupDurationUs[osdCurElementGroup] = executeTimeUs;
}
}
if (executeTimeUs > osdStateDurationUs[osdCurState]) {
osdStateDurationUs[osdCurState] = executeTimeUs;
}
}
if (osdState == OSD_STATE_UPDATE_ELEMENTS) {
schedulerSetNextStateTime(osdElementGroupDurationUs[osdElementGroup]);
} else {
if (osdState == OSD_STATE_IDLE) {
schedulerSetNextStateTime(osdStateDurationUs[OSD_STATE_CHECK]);
} else {
schedulerSetNextStateTime(osdStateDurationUs[osdState]);
}
ignoreTaskExecTime();
}
++counter;
}
void osdSuppressStats(bool flag)