1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-13 11:29:58 +03:00

Fix OSD task timing when using MSP (#13388)

Add uartWriteBuf() to improve performance
Optimised transmit buffer space check
Tidy up group duration calculations
Add uartBeginWrite, uartEndWrite and serialWriteBufNoFlush
Remove OSD grouping and check on the fly. Implement multi-pass artificial horizon rendering.
Fix rendering of camera frame
Fix stick overlay background rendering
Fix channel rendering
Fix ESC information rendering
Make Spec Prearm Display deterministic

Co-authored-by: Petr Ledvina <ledvinap@gmail.com>
This commit is contained in:
Steve Evans 2024-03-11 17:46:06 +00:00 committed by GitHub
parent 915caae88d
commit af51e00773
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 350 additions and 206 deletions

View file

@ -44,16 +44,16 @@ void serialWrite(serialPort_t *instance, uint8_t ch)
} }
void serialWriteBuf(serialPort_t *instance, const uint8_t *data, int count) void serialWriteBufNoFlush(serialPort_t *instance, const uint8_t *data, int count)
{ {
if (instance->vTable->writeBuf) { if (instance->vTable->writeBuf) {
instance->vTable->writeBuf(instance, data, count); instance->vTable->writeBuf(instance, data, count);
} else { } else {
// The transmit buffer is large enough to hold any single message, so only wait once
while (serialTxBytesFree(instance) < (uint32_t)count) {
};
for (const uint8_t *p = data; count > 0; count--, p++) { for (const uint8_t *p = data; count > 0; count--, p++) {
while (!serialTxBytesFree(instance)) {
};
serialWrite(instance, *p); serialWrite(instance, *p);
} }
} }
@ -107,11 +107,6 @@ void serialSetBaudRateCb(serialPort_t *serialPort, void (*cb)(serialPort_t *cont
} }
} }
void serialWriteBufShim(void *instance, const uint8_t *data, int count)
{
serialWriteBuf((serialPort_t *)instance, data, count);
}
void serialBeginWrite(serialPort_t *instance) void serialBeginWrite(serialPort_t *instance)
{ {
if (instance->vTable->beginWrite) if (instance->vTable->beginWrite)
@ -123,3 +118,14 @@ void serialEndWrite(serialPort_t *instance)
if (instance->vTable->endWrite) if (instance->vTable->endWrite)
instance->vTable->endWrite(instance); instance->vTable->endWrite(instance);
} }
void serialWriteBuf(serialPort_t *instance, const uint8_t *data, int count)
{
serialBeginWrite(instance);
serialWriteBufNoFlush(instance, data, count);
serialEndWrite(instance);
}
void serialWriteBufShim(void *instance, const uint8_t *data, int count)
{
serialWriteBuf((serialPort_t *)instance, data, count);
}

View file

@ -141,6 +141,7 @@ void serialWrite(serialPort_t *instance, uint8_t ch);
uint32_t serialRxBytesWaiting(const serialPort_t *instance); uint32_t serialRxBytesWaiting(const serialPort_t *instance);
uint32_t serialTxBytesFree(const serialPort_t *instance); uint32_t serialTxBytesFree(const serialPort_t *instance);
void serialWriteBuf(serialPort_t *instance, const uint8_t *data, int count); void serialWriteBuf(serialPort_t *instance, const uint8_t *data, int count);
void serialWriteBufNoFlush(serialPort_t *instance, const uint8_t *data, int count);
uint8_t serialRead(serialPort_t *instance); uint8_t serialRead(serialPort_t *instance);
void serialSetBaudRate(serialPort_t *instance, uint32_t baudRate); void serialSetBaudRate(serialPort_t *instance, uint32_t baudRate);
void serialSetMode(serialPort_t *instance, portMode_e mode); void serialSetMode(serialPort_t *instance, portMode_e mode);

View file

@ -27,6 +27,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <string.h>
#include "platform.h" #include "platform.h"
@ -34,6 +35,7 @@
#include "build/build_config.h" #include "build/build_config.h"
#include <common/maths.h>
#include "common/utils.h" #include "common/utils.h"
#include "drivers/dma.h" #include "drivers/dma.h"
@ -297,6 +299,71 @@ static void uartWrite(serialPort_t *instance, uint8_t ch)
} }
} }
static void uartBeginWrite(serialPort_t *instance)
{
uartPort_t *uartPort = (uartPort_t *)instance;
// Check if the TX line is being pulled low by an unpowered peripheral
if (uartPort->checkUsartTxOutput) {
uartPort->checkUsartTxOutput(uartPort);
}
}
static void uartWriteBuf(serialPort_t *instance, const void *data, int count)
{
uartPort_t *uartPort = (uartPort_t *)instance;
uartDevice_t *uart = container_of(uartPort, uartDevice_t, port);
const uint8_t *bytePtr = (const uint8_t*)data;
// Test if checkUsartTxOutput() detected TX line being pulled low by an unpowered peripheral
if (uart->txPinState == TX_PIN_MONITOR) {
// TX line is being pulled low, so don't transmit
return;
}
while (count > 0) {
// Calculate the available space to the end of the buffer
const int spaceToEnd = uartPort->port.txBufferSize - uartPort->port.txBufferHead;
// Determine the amount to copy in this iteration
const int chunkSize = MIN(spaceToEnd, count);
// Copy the chunk
memcpy((void *)&uartPort->port.txBuffer[uartPort->port.txBufferHead], bytePtr, chunkSize);
// Advance source pointer
bytePtr += chunkSize;
// Advance head, wrapping if necessary
uartPort->port.txBufferHead = (uartPort->port.txBufferHead + chunkSize) % uartPort->port.txBufferSize;
// Decrease remaining count
count -= chunkSize;
}
}
static void uartEndWrite(serialPort_t *instance)
{
uartPort_t *uartPort = (uartPort_t *)instance;
uartDevice_t *uart = container_of(uartPort, uartDevice_t, port);
// Check if the TX line is being pulled low by an unpowered peripheral
if (uart->txPinState == TX_PIN_MONITOR) {
// TX line is being pulled low, so don't transmit
return;
}
#ifdef USE_DMA
if (uartPort->txDMAResource) {
uartTryStartTxDMA(uartPort);
} else
#endif
{
#if defined(USE_HAL_DRIVER)
__HAL_UART_ENABLE_IT(&uartPort->Handle, UART_IT_TXE);
#elif defined(USE_ATBSP_DRIVER)
usart_interrupt_enable(uartPort->USARTx, USART_TDBE_INT, TRUE);
#else
USART_ITConfig(uartPort->USARTx, USART_IT_TXE, ENABLE);
#endif
}
}
const struct serialPortVTable uartVTable[] = { const struct serialPortVTable uartVTable[] = {
{ {
.serialWrite = uartWrite, .serialWrite = uartWrite,
@ -308,9 +375,9 @@ const struct serialPortVTable uartVTable[] = {
.setMode = uartSetMode, .setMode = uartSetMode,
.setCtrlLineStateCb = NULL, .setCtrlLineStateCb = NULL,
.setBaudRateCb = NULL, .setBaudRateCb = NULL,
.writeBuf = NULL, .writeBuf = uartWriteBuf,
.beginWrite = NULL, .beginWrite = uartBeginWrite,
.endWrite = NULL, .endWrite = uartEndWrite,
} }
}; };

View file

@ -317,9 +317,9 @@ static int mspSerialSendFrame(mspPort_t *msp, const uint8_t * hdr, int hdrLen, c
// Transmit frame // Transmit frame
serialBeginWrite(msp->port); serialBeginWrite(msp->port);
serialWriteBuf(msp->port, hdr, hdrLen); serialWriteBufNoFlush(msp->port, hdr, hdrLen);
serialWriteBuf(msp->port, data, dataLen); serialWriteBufNoFlush(msp->port, data, dataLen);
serialWriteBuf(msp->port, crc, crcLen); serialWriteBufNoFlush(msp->port, crc, crcLen);
serialEndWrite(msp->port); serialEndWrite(msp->port);
return totalFrameLength; return totalFrameLength;

View file

@ -206,13 +206,7 @@ const osd_stats_e osdStatsDisplayOrder[OSD_STAT_COUNT] = {
#define OSD_GROUP_COUNT OSD_ITEM_COUNT #define OSD_GROUP_COUNT OSD_ITEM_COUNT
// Aim to render a group of elements within a target time // Aim to render a group of elements within a target time
#define OSD_ELEMENT_RENDER_TARGET 30 #define OSD_ELEMENT_RENDER_TARGET 30
// 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
#if defined(STM32F411xE)
#define OSD_ELEMENT_RENDER_GROUP_MARGIN 7
#else
#define OSD_ELEMENT_RENDER_GROUP_MARGIN 2
#endif
#define OSD_TASK_MARGIN 1 #define OSD_TASK_MARGIN 1
// Decay the estimated max task duration by 1/(1 << OSD_EXEC_TIME_SHIFT) on every invocation // Decay the estimated max task duration by 1/(1 << OSD_EXEC_TIME_SHIFT) on every invocation
#define OSD_EXEC_TIME_SHIFT 8 #define OSD_EXEC_TIME_SHIFT 8
@ -1339,8 +1333,8 @@ typedef enum {
OSD_STATE_PROCESS_STATS2, OSD_STATE_PROCESS_STATS2,
OSD_STATE_PROCESS_STATS3, OSD_STATE_PROCESS_STATS3,
OSD_STATE_UPDATE_ALARMS, OSD_STATE_UPDATE_ALARMS,
OSD_STATE_REFRESH_PREARM,
OSD_STATE_UPDATE_CANVAS, OSD_STATE_UPDATE_CANVAS,
OSD_STATE_GROUP_ELEMENTS,
OSD_STATE_UPDATE_ELEMENTS, OSD_STATE_UPDATE_ELEMENTS,
OSD_STATE_UPDATE_HEARTBEAT, OSD_STATE_UPDATE_HEARTBEAT,
OSD_STATE_COMMIT, OSD_STATE_COMMIT,
@ -1379,13 +1373,8 @@ bool osdUpdateCheck(timeUs_t currentTimeUs, timeDelta_t currentDeltaTimeUs)
void osdUpdate(timeUs_t currentTimeUs) void osdUpdate(timeUs_t currentTimeUs)
{ {
static uint16_t osdStateDurationFractionUs[OSD_STATE_COUNT] = { 0 }; static uint16_t osdStateDurationFractionUs[OSD_STATE_COUNT] = { 0 };
static uint32_t osdElementDurationUs[OSD_ITEM_COUNT] = { 0 }; static uint32_t osdElementDurationFractionUs[OSD_ITEM_COUNT] = { 0 };
static uint8_t osdElementGroupMemberships[OSD_ITEM_COUNT];
static uint16_t osdElementGroupTargetFractionUs[OSD_GROUP_COUNT] = { 0 };
static uint16_t osdElementGroupDurationFractionUs[OSD_GROUP_COUNT] = { 0 };
static uint8_t osdElementGroup;
static bool firstPass = true; static bool firstPass = true;
uint8_t osdCurrentElementGroup = 0;
timeUs_t executeTimeUs; timeUs_t executeTimeUs;
osdState_e osdCurrentState = osdState; osdState_e osdCurrentState = osdState;
@ -1473,10 +1462,31 @@ void osdUpdate(timeUs_t currentTimeUs)
if (resumeRefreshAt) { if (resumeRefreshAt) {
osdState = OSD_STATE_TRANSFER; osdState = OSD_STATE_TRANSFER;
} else { } else {
#ifdef USE_SPEC_PREARM_SCREEN
osdState = OSD_STATE_REFRESH_PREARM;
#else
osdState = OSD_STATE_UPDATE_CANVAS; osdState = OSD_STATE_UPDATE_CANVAS;
#endif
} }
break; break;
case OSD_STATE_REFRESH_PREARM:
{
#ifdef USE_SPEC_PREARM_SCREEN
if (!ARMING_FLAG(ARMED) && osdConfig()->osd_show_spec_prearm) {
if (osdDrawSpec(osdDisplayPort)) {
// Rendering is complete
osdState = OSD_STATE_COMMIT;
}
} else
#endif // USE_SPEC_PREARM_SCREEN
{
osdState = OSD_STATE_UPDATE_CANVAS;
}
}
break;
case OSD_STATE_UPDATE_CANVAS: case OSD_STATE_UPDATE_CANVAS:
// Hide OSD when OSDSW mode is active // Hide OSD when OSDSW mode is active
if (IS_RC_MODE_ACTIVE(BOXOSD)) { if (IS_RC_MODE_ACTIVE(BOXOSD)) {
@ -1508,66 +1518,23 @@ void osdUpdate(timeUs_t currentTimeUs)
osdSyncBlink(); osdSyncBlink();
osdState = OSD_STATE_GROUP_ELEMENTS; osdState = OSD_STATE_UPDATE_ELEMENTS;
break; break;
case OSD_STATE_GROUP_ELEMENTS:
{
uint8_t elementGroup;
uint8_t activeElements = osdGetActiveElementCount();
// Reset groupings
for (elementGroup = 0; elementGroup < OSD_GROUP_COUNT; elementGroup++) {
if (osdElementGroupDurationFractionUs[elementGroup] > (OSD_ELEMENT_RENDER_TARGET << OSD_EXEC_TIME_SHIFT)) {
osdElementGroupDurationFractionUs[elementGroup] = 0;
}
osdElementGroupTargetFractionUs[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 ((osdElementGroupTargetFractionUs[elementGroup] == 0) ||
(osdElementGroupTargetFractionUs[elementGroup] + (osdElementDurationUs[curElement]) <= (OSD_ELEMENT_RENDER_TARGET << OSD_EXEC_TIME_SHIFT)) ||
(elementGroup == (OSD_GROUP_COUNT - 1))) {
osdElementGroupTargetFractionUs[elementGroup] += osdElementDurationUs[curElement];
// If group membership changes, reset the stats for the group
if (osdElementGroupMemberships[curElement] != elementGroup) {
osdElementGroupDurationFractionUs[elementGroup] = osdElementGroupTargetFractionUs[elementGroup] + (OSD_ELEMENT_RENDER_GROUP_MARGIN << OSD_EXEC_TIME_SHIFT);
}
osdElementGroupMemberships[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: case OSD_STATE_UPDATE_ELEMENTS:
{ {
osdCurrentElementGroup = osdElementGroup;
bool moreElements = true; bool moreElements = true;
do { for (int rendered = 0; moreElements; rendered++) {
timeUs_t startElementTime = micros();
uint8_t osdCurrentElement = osdGetActiveElement(); uint8_t osdCurrentElement = osdGetActiveElement();
// This element should be rendered in the next group timeUs_t startElementTime = micros();
if (osdElementGroupMemberships[osdCurrentElement] != osdElementGroup) {
osdElementGroup++; timeUs_t anticipatedEndUs = startElementTime + (osdElementDurationFractionUs[osdCurrentElement] >> OSD_EXEC_TIME_SHIFT);
if ((rendered > 0) && cmpTimeUs(anticipatedEndUs, currentTimeUs) > OSD_ELEMENT_RENDER_TARGET) {
// There isn't time to render the next element
break; break;
} }
@ -1575,23 +1542,18 @@ void osdUpdate(timeUs_t currentTimeUs)
executeTimeUs = micros() - startElementTime; executeTimeUs = micros() - startElementTime;
if (executeTimeUs > (osdElementDurationUs[osdCurrentElement] >> OSD_EXEC_TIME_SHIFT)) { if (executeTimeUs > (osdElementDurationFractionUs[osdCurrentElement] >> OSD_EXEC_TIME_SHIFT)) {
osdElementDurationUs[osdCurrentElement] = executeTimeUs << OSD_EXEC_TIME_SHIFT; osdElementDurationFractionUs[osdCurrentElement] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
} else if (osdElementDurationUs[osdCurrentElement] > 0) { } else if (osdElementDurationFractionUs[osdCurrentElement] > 0) {
// Slowly decay the max time // Slowly decay the max time
osdElementDurationUs[osdCurrentElement]--; osdElementDurationFractionUs[osdCurrentElement]--;
} }
} while (moreElements); };
if (moreElements) { if (moreElements) {
// There are more elements to draw // There are more elements to draw
break; break;
} }
#ifdef USE_SPEC_PREARM_SCREEN
osdDrawSpec(osdDisplayPort);
#endif // USE_SPEC_PREARM_SCREEN
osdElementGroup = 0;
osdState = OSD_STATE_COMMIT; osdState = OSD_STATE_COMMIT;
} }
@ -1636,15 +1598,6 @@ void osdUpdate(timeUs_t currentTimeUs)
// On the first pass no element groups will have been formed, so all elements will have been // On the first pass no element groups will have been formed, so all elements will have been
// rendered which is unrepresentative, so ignore // rendered which is unrepresentative, so ignore
if (!firstPass) { if (!firstPass) {
if (osdCurrentState == OSD_STATE_UPDATE_ELEMENTS) {
if (executeTimeUs > (osdElementGroupDurationFractionUs[osdCurrentElementGroup] >> OSD_EXEC_TIME_SHIFT)) {
osdElementGroupDurationFractionUs[osdCurrentElementGroup] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
} else if (osdElementGroupDurationFractionUs[osdCurrentElementGroup] > 0) {
// Slowly decay the max time
osdElementGroupDurationFractionUs[osdCurrentElementGroup]--;
}
}
if (executeTimeUs > (osdStateDurationFractionUs[osdCurrentState] >> OSD_EXEC_TIME_SHIFT)) { if (executeTimeUs > (osdStateDurationFractionUs[osdCurrentState] >> OSD_EXEC_TIME_SHIFT)) {
osdStateDurationFractionUs[osdCurrentState] = executeTimeUs << OSD_EXEC_TIME_SHIFT; osdStateDurationFractionUs[osdCurrentState] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
} else if (osdStateDurationFractionUs[osdCurrentState] > 0) { } else if (osdStateDurationFractionUs[osdCurrentState] > 0) {
@ -1654,14 +1607,10 @@ void osdUpdate(timeUs_t currentTimeUs)
} }
} }
if (osdState == OSD_STATE_UPDATE_ELEMENTS) { if (osdState == OSD_STATE_IDLE) {
schedulerSetNextStateTime((osdElementGroupDurationFractionUs[osdElementGroup] >> OSD_EXEC_TIME_SHIFT) + OSD_ELEMENT_RENDER_GROUP_MARGIN); schedulerSetNextStateTime((osdStateDurationFractionUs[OSD_STATE_CHECK] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);
} else { } else {
if (osdState == OSD_STATE_IDLE) { schedulerSetNextStateTime((osdStateDurationFractionUs[osdState] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);
schedulerSetNextStateTime((osdStateDurationFractionUs[OSD_STATE_CHECK] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);
} else {
schedulerSetNextStateTime((osdStateDurationFractionUs[osdState] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);
}
} }
} }

View file

@ -286,15 +286,20 @@ static int getEscRpmFreq(int i)
static void renderOsdEscRpmOrFreq(getEscRpmOrFreqFnPtr escFnPtr, osdElementParms_t *element) static void renderOsdEscRpmOrFreq(getEscRpmOrFreqFnPtr escFnPtr, osdElementParms_t *element)
{ {
static uint8_t motor = 0;
int x = element->elemPosX; int x = element->elemPosX;
int y = element->elemPosY; int y = element->elemPosY;
for (int i=0; i < getMotorCount(); i++) { char rpmStr[6];
char rpmStr[6]; const int rpm = MIN((*escFnPtr)(motor),99999);
const int rpm = MIN((*escFnPtr)(i),99999); tfp_sprintf(rpmStr, "%d", rpm);
const int len = tfp_sprintf(rpmStr, "%d", rpm); osdDisplayWrite(element, x, y + motor, DISPLAYPORT_SEVERITY_NORMAL, rpmStr);
rpmStr[len] = '\0';
osdDisplayWrite(element, x, y + i, DISPLAYPORT_SEVERITY_NORMAL, rpmStr); if (++motor == getMotorCount()) {
motor = 0;
} else {
element->rendered = false;
} }
element->drawElement = false; element->drawElement = false;
} }
#endif #endif
@ -708,6 +713,7 @@ static void osdElementAntiGravity(osdElementParms_t *element)
static void osdElementArtificialHorizon(osdElementParms_t *element) static void osdElementArtificialHorizon(osdElementParms_t *element)
{ {
static int x = -4;
// Get pitch and roll limits in tenths of degrees // Get pitch and roll limits in tenths of degrees
const int maxPitch = osdConfig()->ahMaxPitch * 10; const int maxPitch = osdConfig()->ahMaxPitch * 10;
const int maxRoll = osdConfig()->ahMaxRoll * 10; const int maxRoll = osdConfig()->ahMaxRoll * 10;
@ -721,11 +727,18 @@ static void osdElementArtificialHorizon(osdElementParms_t *element)
} }
pitchAngle -= 41; // 41 = 4 * AH_SYMBOL_COUNT + 5 pitchAngle -= 41; // 41 = 4 * AH_SYMBOL_COUNT + 5
for (int x = -4; x <= 4; x++) { const int y = ((-rollAngle * x) / 64) - pitchAngle;
const int y = ((-rollAngle * x) / 64) - pitchAngle; if (y >= 0 && y <= 81) {
if (y >= 0 && y <= 81) { osdDisplayWriteChar(element, element->elemPosX + x, element->elemPosY + (y / AH_SYMBOL_COUNT), DISPLAYPORT_SEVERITY_NORMAL, (SYM_AH_BAR9_0 + (y % AH_SYMBOL_COUNT)));
osdDisplayWriteChar(element, element->elemPosX + x, element->elemPosY + (y / AH_SYMBOL_COUNT), DISPLAYPORT_SEVERITY_NORMAL, (SYM_AH_BAR9_0 + (y % AH_SYMBOL_COUNT))); }
}
if (x == 4) {
// Rendering is complete, so prepare to start again
x = -4;
} else {
// Rendering not yet complete
element->rendered = false;
x++;
} }
element->drawElement = false; // element already drawn element->drawElement = false; // element already drawn
@ -794,24 +807,43 @@ static void osdElementCoreTemperature(osdElementParms_t *element)
static void osdBackgroundCameraFrame(osdElementParms_t *element) static void osdBackgroundCameraFrame(osdElementParms_t *element)
{ {
static enum {TOP, MIDDLE, BOTTOM} renderPhase = TOP;
const uint8_t xpos = element->elemPosX; const uint8_t xpos = element->elemPosX;
const uint8_t ypos = element->elemPosY; const uint8_t ypos = element->elemPosY;
const uint8_t width = constrain(osdConfig()->camera_frame_width, OSD_CAMERA_FRAME_MIN_WIDTH, OSD_CAMERA_FRAME_MAX_WIDTH); const uint8_t width = constrain(osdConfig()->camera_frame_width, OSD_CAMERA_FRAME_MIN_WIDTH, OSD_CAMERA_FRAME_MAX_WIDTH);
const uint8_t height = constrain(osdConfig()->camera_frame_height, OSD_CAMERA_FRAME_MIN_HEIGHT, OSD_CAMERA_FRAME_MAX_HEIGHT); const uint8_t height = constrain(osdConfig()->camera_frame_height, OSD_CAMERA_FRAME_MIN_HEIGHT, OSD_CAMERA_FRAME_MAX_HEIGHT);
element->buff[0] = SYM_STICK_OVERLAY_CENTER; if (renderPhase != BOTTOM) {
for (int i = 1; i < (width - 1); i++) { // Rendering not yet complete
element->buff[i] = SYM_STICK_OVERLAY_HORIZONTAL; element->rendered = false;
} }
element->buff[width - 1] = SYM_STICK_OVERLAY_CENTER;
element->buff[width] = 0; // string terminator
osdDisplayWrite(element, xpos, ypos, DISPLAYPORT_SEVERITY_NORMAL, element->buff); if (renderPhase == MIDDLE) {
for (int i = 1; i < (height - 1); i++) { static uint8_t i = 1;
osdDisplayWriteChar(element, xpos, ypos + i, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL); osdDisplayWriteChar(element, xpos, ypos + i, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL);
osdDisplayWriteChar(element, xpos + width - 1, ypos + i, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL); osdDisplayWriteChar(element, xpos + width - 1, ypos + i, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL);
if (++i == height) {
i = 1;
renderPhase = BOTTOM;
}
} else {
element->buff[0] = SYM_STICK_OVERLAY_CENTER;
for (uint8_t i = 1; i < (width - 1); i++) {
element->buff[i] = SYM_STICK_OVERLAY_HORIZONTAL;
}
element->buff[width - 1] = SYM_STICK_OVERLAY_CENTER;
element->buff[width] = 0; // string terminator
if (renderPhase == TOP) {
osdDisplayWrite(element, xpos, ypos, DISPLAYPORT_SEVERITY_NORMAL, element->buff);
renderPhase = MIDDLE;
} else {
osdDisplayWrite(element, xpos, ypos + height - 1, DISPLAYPORT_SEVERITY_NORMAL, element->buff);
renderPhase = TOP;
}
} }
osdDisplayWrite(element, xpos, ypos + height - 1, DISPLAYPORT_SEVERITY_NORMAL, element->buff);
element->drawElement = false; // element already drawn element->drawElement = false; // element already drawn
} }
@ -1184,17 +1216,32 @@ static void osdElementGpsLapTimeBest3(osdElementParms_t *element)
static void osdBackgroundHorizonSidebars(osdElementParms_t *element) static void osdBackgroundHorizonSidebars(osdElementParms_t *element)
{ {
static bool renderLevel = false;
static int8_t y = -AH_SIDEBAR_HEIGHT_POS;
// Draw AH sides // Draw AH sides
const int8_t hudwidth = AH_SIDEBAR_WIDTH_POS; const int8_t hudwidth = AH_SIDEBAR_WIDTH_POS;
const int8_t hudheight = AH_SIDEBAR_HEIGHT_POS; const int8_t hudheight = AH_SIDEBAR_HEIGHT_POS;
for (int y = -hudheight; y <= hudheight; y++) {
if (renderLevel) {
// AH level indicators
osdDisplayWriteChar(element, element->elemPosX - hudwidth + 1, element->elemPosY, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_LEFT);
osdDisplayWriteChar(element, element->elemPosX + hudwidth - 1, element->elemPosY, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_RIGHT);
renderLevel = false;
} else {
osdDisplayWriteChar(element, element->elemPosX - hudwidth, element->elemPosY + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_DECORATION); osdDisplayWriteChar(element, element->elemPosX - hudwidth, element->elemPosY + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_DECORATION);
osdDisplayWriteChar(element, element->elemPosX + hudwidth, element->elemPosY + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_DECORATION); osdDisplayWriteChar(element, element->elemPosX + hudwidth, element->elemPosY + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_DECORATION);
}
// AH level indicators if (y == hudheight) {
osdDisplayWriteChar(element, element->elemPosX - hudwidth + 1, element->elemPosY, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_LEFT); // Rendering is complete, so prepare to start again
osdDisplayWriteChar(element, element->elemPosX + hudwidth - 1, element->elemPosY, DISPLAYPORT_SEVERITY_NORMAL, SYM_AH_RIGHT); y = -hudheight;
// On next pass render the level markers
renderLevel = true;
} else {
y++;
}
// Rendering not yet complete
element->rendered = false;
}
element->drawElement = false; // element already drawn element->drawElement = false; // element already drawn
} }
@ -1444,19 +1491,24 @@ static void osdElementPower(osdElementParms_t *element)
static void osdElementRcChannels(osdElementParms_t *element) static void osdElementRcChannels(osdElementParms_t *element)
{ {
static uint8_t channel = 0;
const uint8_t xpos = element->elemPosX; const uint8_t xpos = element->elemPosX;
const uint8_t ypos = element->elemPosY; const uint8_t ypos = element->elemPosY;
for (int i = 0; i < OSD_RCCHANNELS_COUNT; i++) { if (osdConfig()->rcChannels[channel] >= 0) {
if (osdConfig()->rcChannels[i] >= 0) { // Translate (1000, 2000) to (-1000, 1000)
// Translate (1000, 2000) to (-1000, 1000) int data = scaleRange(rcData[osdConfig()->rcChannels[channel]], PWM_RANGE_MIN, PWM_RANGE_MAX, -1000, 1000);
int data = scaleRange(rcData[osdConfig()->rcChannels[i]], PWM_RANGE_MIN, PWM_RANGE_MAX, -1000, 1000); // Opt for the simplest formatting for now.
// Opt for the simplest formatting for now. // Decimal notation can be added when tfp_sprintf supports float among fancy options.
// Decimal notation can be added when tfp_sprintf supports float among fancy options. char fmtbuf[6];
char fmtbuf[6]; tfp_sprintf(fmtbuf, "%5d", data);
tfp_sprintf(fmtbuf, "%5d", data); osdDisplayWrite(element, xpos, ypos + channel, DISPLAYPORT_SEVERITY_NORMAL, fmtbuf);
osdDisplayWrite(element, xpos, ypos + i, DISPLAYPORT_SEVERITY_NORMAL, fmtbuf); }
}
if (++channel == OSD_RCCHANNELS_COUNT) {
channel = 0;
} else {
element->rendered = false;
} }
element->drawElement = false; // element already drawn element->drawElement = false; // element already drawn
@ -1531,21 +1583,37 @@ static void osdElementRsnr(osdElementParms_t *element)
#ifdef USE_OSD_STICK_OVERLAY #ifdef USE_OSD_STICK_OVERLAY
static void osdBackgroundStickOverlay(osdElementParms_t *element) static void osdBackgroundStickOverlay(osdElementParms_t *element)
{ {
static enum {VERT, HORZ} renderPhase = VERT;
const uint8_t xpos = element->elemPosX; const uint8_t xpos = element->elemPosX;
const uint8_t ypos = element->elemPosY; const uint8_t ypos = element->elemPosY;
// Draw the axis first if (renderPhase == VERT) {
for (unsigned x = 0; x < OSD_STICK_OVERLAY_WIDTH; x++) { static uint8_t y = 0;
for (unsigned y = 0; y < OSD_STICK_OVERLAY_HEIGHT; y++) { osdDisplayWriteChar(element, xpos + ((OSD_STICK_OVERLAY_WIDTH - 1) / 2), ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL);
// draw the axes, vertical and horizonal
if ((x == ((OSD_STICK_OVERLAY_WIDTH - 1) / 2)) && (y == (OSD_STICK_OVERLAY_HEIGHT - 1) / 2)) { y++;
osdDisplayWriteChar(element, xpos + x, ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_CENTER);
} else if (x == ((OSD_STICK_OVERLAY_WIDTH - 1) / 2)) { if (y == (OSD_STICK_OVERLAY_HEIGHT - 1) / 2) {
osdDisplayWriteChar(element, xpos + x, ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL); // Skip over horizontal
} else if (y == ((OSD_STICK_OVERLAY_HEIGHT - 1) / 2)) { y++;
osdDisplayWriteChar(element, xpos + x, ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_HORIZONTAL);
}
} }
if (y == OSD_STICK_OVERLAY_HEIGHT) {
y = 0;
renderPhase = HORZ;
}
element->rendered = false;
} else {
for (uint8_t i = 0; i < OSD_STICK_OVERLAY_WIDTH; i++) {
element->buff[i] = SYM_STICK_OVERLAY_HORIZONTAL;
}
element->buff[((OSD_STICK_OVERLAY_WIDTH - 1) / 2)] = SYM_STICK_OVERLAY_CENTER;
element->buff[OSD_STICK_OVERLAY_WIDTH] = 0; // string terminator
osdDisplayWrite(element, xpos, ypos + ((OSD_STICK_OVERLAY_HEIGHT - 1) / 2), DISPLAYPORT_SEVERITY_NORMAL, element->buff);
renderPhase = VERT;
} }
element->drawElement = false; // element already drawn element->drawElement = false; // element already drawn
@ -2016,14 +2084,14 @@ void osdAddActiveElements(void)
#endif #endif
} }
static void osdDrawSingleElement(displayPort_t *osdDisplayPort, uint8_t item) static bool osdDrawSingleElement(displayPort_t *osdDisplayPort, uint8_t item)
{ {
if (!osdElementDrawFunction[item]) { if (!osdElementDrawFunction[item]) {
// Element has no drawing function // Element has no drawing function
return; return true;
} }
if (!osdDisplayPort->useDeviceBlink && BLINK(item)) { if (!osdDisplayPort->useDeviceBlink && BLINK(item)) {
return; return true;
} }
uint8_t elemPosX = OSD_X(osdElementConfig()->item_pos[item]); uint8_t elemPosX = OSD_X(osdElementConfig()->item_pos[item]);
@ -2038,6 +2106,7 @@ static void osdDrawSingleElement(displayPort_t *osdDisplayPort, uint8_t item)
element.buff = (char *)&buff; element.buff = (char *)&buff;
element.osdDisplayPort = osdDisplayPort; element.osdDisplayPort = osdDisplayPort;
element.drawElement = true; element.drawElement = true;
element.rendered = true;
element.attr = DISPLAYPORT_SEVERITY_NORMAL; element.attr = DISPLAYPORT_SEVERITY_NORMAL;
// Call the element drawing function // Call the element drawing function
@ -2049,13 +2118,15 @@ static void osdDrawSingleElement(displayPort_t *osdDisplayPort, uint8_t item)
osdDisplayWrite(&element, elemPosX, elemPosY, element.attr, buff); osdDisplayWrite(&element, elemPosX, elemPosY, element.attr, buff);
} }
} }
return element.rendered;
} }
static void osdDrawSingleElementBackground(displayPort_t *osdDisplayPort, uint8_t item) static bool osdDrawSingleElementBackground(displayPort_t *osdDisplayPort, uint8_t item)
{ {
if (!osdElementBackgroundFunction[item]) { if (!osdElementBackgroundFunction[item]) {
// Element has no background drawing function // Element has no background drawing function
return; return true;
} }
uint8_t elemPosX = OSD_X(osdElementConfig()->item_pos[item]); uint8_t elemPosX = OSD_X(osdElementConfig()->item_pos[item]);
@ -2069,6 +2140,7 @@ static void osdDrawSingleElementBackground(displayPort_t *osdDisplayPort, uint8_
element.type = OSD_TYPE(osdElementConfig()->item_pos[item]); element.type = OSD_TYPE(osdElementConfig()->item_pos[item]);
element.buff = (char *)&buff; element.buff = (char *)&buff;
element.osdDisplayPort = osdDisplayPort; element.osdDisplayPort = osdDisplayPort;
element.rendered = true;
element.drawElement = true; element.drawElement = true;
// Call the element background drawing function // Call the element background drawing function
@ -2076,6 +2148,8 @@ static void osdDrawSingleElementBackground(displayPort_t *osdDisplayPort, uint8_
if (element.drawElement) { if (element.drawElement) {
osdDisplayWrite(&element, elemPosX, elemPosY, DISPLAYPORT_SEVERITY_NORMAL, buff); osdDisplayWrite(&element, elemPosX, elemPosY, DISPLAYPORT_SEVERITY_NORMAL, buff);
} }
return element.rendered;
} }
static uint8_t activeElement = 0; static uint8_t activeElement = 0;
@ -2094,81 +2168,127 @@ uint8_t osdGetActiveElementCount(void)
bool osdDrawNextActiveElement(displayPort_t *osdDisplayPort, timeUs_t currentTimeUs) bool osdDrawNextActiveElement(displayPort_t *osdDisplayPort, timeUs_t currentTimeUs)
{ {
UNUSED(currentTimeUs); UNUSED(currentTimeUs);
static bool backgroundRendered = false;
bool retval = true; bool retval = true;
if (activeElement >= activeOsdElementCount) { if (activeElement >= activeOsdElementCount) {
return false; return false;
} }
if (!backgroundLayerSupported) { if (!backgroundLayerSupported && !backgroundRendered) {
// If the background layer isn't supported then we // If the background layer isn't supported then we
// have to draw the element's static layer as well. // have to draw the element's static layer as well.
osdDrawSingleElementBackground(osdDisplayPort, activeOsdElementArray[activeElement]); backgroundRendered = osdDrawSingleElementBackground(osdDisplayPort, activeOsdElementArray[activeElement]);
return retval;
} }
osdDrawSingleElement(osdDisplayPort, activeOsdElementArray[activeElement]); // Only advance to the next element if rendering is complete
if (osdDrawSingleElement(osdDisplayPort, activeOsdElementArray[activeElement])) {
if (++activeElement >= activeOsdElementCount) { // Prepare to render the background of the next element
activeElement = 0; backgroundRendered = false;
retval = false; if (++activeElement >= activeOsdElementCount) {
activeElement = 0;
retval = false;
}
} }
return retval; return retval;
} }
#ifdef USE_SPEC_PREARM_SCREEN #ifdef USE_SPEC_PREARM_SCREEN
void osdDrawSpec(displayPort_t *osdDisplayPort) bool osdDrawSpec(displayPort_t *osdDisplayPort)
{ {
if (!ARMING_FLAG(ARMED) && osdConfig()->osd_show_spec_prearm) { static enum {CLR, RPM, POLES, MIXER, THR, MOTOR, BAT, VER} specState = CLR;
const uint8_t midRow = osdDisplayPort->rows / 2; static int currentRow;
const uint8_t midCol = osdDisplayPort->cols / 2;
char buff[OSD_ELEMENT_BUFFER_LENGTH] = ""; const uint8_t midRow = osdDisplayPort->rows / 2;
const uint8_t midCol = osdDisplayPort->cols / 2;
memset(buff,0,strlen(buff)); char buff[OSD_ELEMENT_BUFFER_LENGTH] = "";
int len = 0;
int currentRow = midRow - 3;
int len = 0;
switch (specState) {
default:
case CLR:
displayClearScreen(osdDisplayPort, DISPLAY_CLEAR_NONE);
currentRow = midRow - 3;
specState = RPM;
break;
case RPM:
#ifdef USE_RPM_LIMIT #ifdef USE_RPM_LIMIT
const bool rpmLimitActive = mixerConfig()->rpm_limit > 0 && isMotorProtocolBidirDshot();
if (rpmLimitActive) {
len = tfp_sprintf(buff, "RPM LIMIT ON %d", mixerConfig()->rpm_limit_value);
} else {
len = tfp_sprintf(buff, "%s", "RPM LIMIT OFF");
}
displayWrite(osdDisplayPort, midCol - len/2, currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
if (rpmLimitActive) {
memset(buff,0,strlen(buff));
len = tfp_sprintf(buff, "KV %d POLES %d", motorConfig()->kv, motorConfig()->motorPoleCount);
displayWrite(osdDisplayPort, midCol - len/2, currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
memset(buff,0,strlen(buff));
len = tfp_sprintf(buff, "%d %d %d", mixerConfig()->rpm_limit_p, mixerConfig()->rpm_limit_i, mixerConfig()->rpm_limit_d);
displayWrite(osdDisplayPort, midCol - len/2, currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
} else
#endif // #USE_RPM_LIMIT
{ {
memset(buff,0,strlen(buff)); const bool rpmLimitActive = mixerConfig()->rpm_limit > 0 && isMotorProtocolBidirDshot();
len = tfp_sprintf(buff, "THR LIMIT %s", lookupTableThrottleLimitType[currentControlRateProfile->throttle_limit_type]); if (rpmLimitActive) {
if (currentControlRateProfile->throttle_limit_type != THROTTLE_LIMIT_TYPE_OFF) { len = tfp_sprintf(buff, "RPM LIMIT ON %d", mixerConfig()->rpm_limit_value);
len = tfp_sprintf(buff, "%s %d", buff, currentControlRateProfile->throttle_limit_percent); } else {
len = tfp_sprintf(buff, "%s", "RPM LIMIT OFF");
} }
displayWrite(osdDisplayPort, midCol - len/2, currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff); displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
if (rpmLimitActive) {
specState = POLES;
} else {
specState = THR;
}
}
break;
case POLES:
len = tfp_sprintf(buff, "KV %d POLES %d", motorConfig()->kv, motorConfig()->motorPoleCount);
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
specState = MIXER;
break;
case MIXER:
len = tfp_sprintf(buff, "%d %d %d", mixerConfig()->rpm_limit_p, mixerConfig()->rpm_limit_i, mixerConfig()->rpm_limit_d);
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
specState = THR;
break;
case THR:
#endif // #USE_RPM_LIMIT
len = tfp_sprintf(buff, "THR LIMIT %s", lookupTableThrottleLimitType[currentControlRateProfile->throttle_limit_type]);
if (currentControlRateProfile->throttle_limit_type != THROTTLE_LIMIT_TYPE_OFF) {
len = tfp_sprintf(buff, "%s %d", buff, currentControlRateProfile->throttle_limit_percent);
}
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
specState = MOTOR;
break;
case MOTOR:
len = tfp_sprintf(buff, "MOTOR LIMIT %d", currentPidProfile->motor_output_limit);
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
specState = BAT;
break;
case BAT:
{
const float batteryVoltage = getBatteryVoltage() / 100.0f;
len = osdPrintFloat(buff, osdGetBatterySymbol(getBatteryAverageCellVoltage()), batteryVoltage, "", 2, true, SYM_VOLT);
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
} }
memset(buff,0,strlen(buff)); specState = VER;
len = tfp_sprintf(buff, "MOTOR LIMIT %d", currentPidProfile->motor_output_limit); break;
displayWrite(osdDisplayPort, midCol - len/2, currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
memset(buff,0,strlen(buff));
const float batteryVoltage = getBatteryVoltage() / 100.0f;
len = osdPrintFloat(buff, osdGetBatterySymbol(getBatteryAverageCellVoltage()), batteryVoltage, "", 2, true, SYM_VOLT);
displayWrite(osdDisplayPort, midCol - len/2, currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
case VER:
len = strlen(FC_VERSION_STRING); len = strlen(FC_VERSION_STRING);
displayWrite(osdDisplayPort, midCol - len/2, currentRow++, DISPLAYPORT_SEVERITY_NORMAL, FC_VERSION_STRING); displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, FC_VERSION_STRING);
specState = CLR;
return true;
} }
return false;
} }
#endif // USE_SPEC_PREARM_SCREEN #endif // USE_SPEC_PREARM_SCREEN
@ -2178,7 +2298,7 @@ void osdDrawActiveElementsBackground(displayPort_t *osdDisplayPort)
displayLayerSelect(osdDisplayPort, DISPLAYPORT_LAYER_BACKGROUND); displayLayerSelect(osdDisplayPort, DISPLAYPORT_LAYER_BACKGROUND);
displayClearScreen(osdDisplayPort, DISPLAY_CLEAR_WAIT); displayClearScreen(osdDisplayPort, DISPLAY_CLEAR_WAIT);
for (unsigned i = 0; i < activeOsdElementCount; i++) { for (unsigned i = 0; i < activeOsdElementCount; i++) {
osdDrawSingleElementBackground(osdDisplayPort, activeOsdElementArray[i]); while (!osdDrawSingleElementBackground(osdDisplayPort, activeOsdElementArray[i]));
} }
displayLayerSelect(osdDisplayPort, DISPLAYPORT_LAYER_FOREGROUND); displayLayerSelect(osdDisplayPort, DISPLAYPORT_LAYER_FOREGROUND);
} }

View file

@ -39,6 +39,7 @@ typedef struct osdElementParms_s {
char *buff; char *buff;
displayPort_t *osdDisplayPort; displayPort_t *osdDisplayPort;
bool drawElement; bool drawElement;
bool rendered;
uint8_t attr; uint8_t attr;
} osdElementParms_t; } osdElementParms_t;
@ -65,5 +66,5 @@ void osdResetAlarms(void);
void osdUpdateAlarms(void); void osdUpdateAlarms(void);
bool osdElementsNeedAccelerometer(void); bool osdElementsNeedAccelerometer(void);
#ifdef USE_SPEC_PREARM_SCREEN #ifdef USE_SPEC_PREARM_SCREEN
void osdDrawSpec(displayPort_t *osdDisplayPort); bool osdDrawSpec(displayPort_t *osdDisplayPort);
#endif // USE_SPEC_PREARM_SCREEN #endif // USE_SPEC_PREARM_SCREEN