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:
parent
915caae88d
commit
af51e00773
7 changed files with 350 additions and 206 deletions
|
@ -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) {
|
||||
instance->vTable->writeBuf(instance, data, count);
|
||||
} else {
|
||||
for (const uint8_t *p = data; count > 0; count--, p++) {
|
||||
|
||||
while (!serialTxBytesFree(instance)) {
|
||||
// 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++) {
|
||||
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)
|
||||
{
|
||||
if (instance->vTable->beginWrite)
|
||||
|
@ -123,3 +118,14 @@ void serialEndWrite(serialPort_t *instance)
|
|||
if (instance->vTable->endWrite)
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ void serialWrite(serialPort_t *instance, uint8_t ch);
|
|||
uint32_t serialRxBytesWaiting(const serialPort_t *instance);
|
||||
uint32_t serialTxBytesFree(const serialPort_t *instance);
|
||||
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);
|
||||
void serialSetBaudRate(serialPort_t *instance, uint32_t baudRate);
|
||||
void serialSetMode(serialPort_t *instance, portMode_e mode);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
|
@ -34,6 +35,7 @@
|
|||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#include <common/maths.h>
|
||||
#include "common/utils.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[] = {
|
||||
{
|
||||
.serialWrite = uartWrite,
|
||||
|
@ -308,9 +375,9 @@ const struct serialPortVTable uartVTable[] = {
|
|||
.setMode = uartSetMode,
|
||||
.setCtrlLineStateCb = NULL,
|
||||
.setBaudRateCb = NULL,
|
||||
.writeBuf = NULL,
|
||||
.beginWrite = NULL,
|
||||
.endWrite = NULL,
|
||||
.writeBuf = uartWriteBuf,
|
||||
.beginWrite = uartBeginWrite,
|
||||
.endWrite = uartEndWrite,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -317,9 +317,9 @@ static int mspSerialSendFrame(mspPort_t *msp, const uint8_t * hdr, int hdrLen, c
|
|||
|
||||
// Transmit frame
|
||||
serialBeginWrite(msp->port);
|
||||
serialWriteBuf(msp->port, hdr, hdrLen);
|
||||
serialWriteBuf(msp->port, data, dataLen);
|
||||
serialWriteBuf(msp->port, crc, crcLen);
|
||||
serialWriteBufNoFlush(msp->port, hdr, hdrLen);
|
||||
serialWriteBufNoFlush(msp->port, data, dataLen);
|
||||
serialWriteBufNoFlush(msp->port, crc, crcLen);
|
||||
serialEndWrite(msp->port);
|
||||
|
||||
return totalFrameLength;
|
||||
|
|
|
@ -206,13 +206,7 @@ const osd_stats_e osdStatsDisplayOrder[OSD_STAT_COUNT] = {
|
|||
#define OSD_GROUP_COUNT OSD_ITEM_COUNT
|
||||
// Aim to render a group of elements within a target time
|
||||
#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
|
||||
// Decay the estimated max task duration by 1/(1 << OSD_EXEC_TIME_SHIFT) on every invocation
|
||||
#define OSD_EXEC_TIME_SHIFT 8
|
||||
|
@ -1339,8 +1333,8 @@ typedef enum {
|
|||
OSD_STATE_PROCESS_STATS2,
|
||||
OSD_STATE_PROCESS_STATS3,
|
||||
OSD_STATE_UPDATE_ALARMS,
|
||||
OSD_STATE_REFRESH_PREARM,
|
||||
OSD_STATE_UPDATE_CANVAS,
|
||||
OSD_STATE_GROUP_ELEMENTS,
|
||||
OSD_STATE_UPDATE_ELEMENTS,
|
||||
OSD_STATE_UPDATE_HEARTBEAT,
|
||||
OSD_STATE_COMMIT,
|
||||
|
@ -1379,13 +1373,8 @@ bool osdUpdateCheck(timeUs_t currentTimeUs, timeDelta_t currentDeltaTimeUs)
|
|||
void osdUpdate(timeUs_t currentTimeUs)
|
||||
{
|
||||
static uint16_t osdStateDurationFractionUs[OSD_STATE_COUNT] = { 0 };
|
||||
static uint32_t osdElementDurationUs[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 uint32_t osdElementDurationFractionUs[OSD_ITEM_COUNT] = { 0 };
|
||||
static bool firstPass = true;
|
||||
uint8_t osdCurrentElementGroup = 0;
|
||||
timeUs_t executeTimeUs;
|
||||
osdState_e osdCurrentState = osdState;
|
||||
|
||||
|
@ -1473,8 +1462,29 @@ void osdUpdate(timeUs_t currentTimeUs)
|
|||
if (resumeRefreshAt) {
|
||||
osdState = OSD_STATE_TRANSFER;
|
||||
} else {
|
||||
#ifdef USE_SPEC_PREARM_SCREEN
|
||||
osdState = OSD_STATE_REFRESH_PREARM;
|
||||
#else
|
||||
osdState = OSD_STATE_UPDATE_CANVAS;
|
||||
#endif
|
||||
}
|
||||
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:
|
||||
|
@ -1508,66 +1518,23 @@ void osdUpdate(timeUs_t currentTimeUs)
|
|||
|
||||
osdSyncBlink();
|
||||
|
||||
osdState = OSD_STATE_GROUP_ELEMENTS;
|
||||
|
||||
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:
|
||||
{
|
||||
osdCurrentElementGroup = osdElementGroup;
|
||||
bool moreElements = true;
|
||||
|
||||
do {
|
||||
timeUs_t startElementTime = micros();
|
||||
for (int rendered = 0; moreElements; rendered++) {
|
||||
uint8_t osdCurrentElement = osdGetActiveElement();
|
||||
|
||||
// This element should be rendered in the next group
|
||||
if (osdElementGroupMemberships[osdCurrentElement] != osdElementGroup) {
|
||||
osdElementGroup++;
|
||||
timeUs_t startElementTime = micros();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1575,23 +1542,18 @@ void osdUpdate(timeUs_t currentTimeUs)
|
|||
|
||||
executeTimeUs = micros() - startElementTime;
|
||||
|
||||
if (executeTimeUs > (osdElementDurationUs[osdCurrentElement] >> OSD_EXEC_TIME_SHIFT)) {
|
||||
osdElementDurationUs[osdCurrentElement] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
|
||||
} else if (osdElementDurationUs[osdCurrentElement] > 0) {
|
||||
if (executeTimeUs > (osdElementDurationFractionUs[osdCurrentElement] >> OSD_EXEC_TIME_SHIFT)) {
|
||||
osdElementDurationFractionUs[osdCurrentElement] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
|
||||
} else if (osdElementDurationFractionUs[osdCurrentElement] > 0) {
|
||||
// Slowly decay the max time
|
||||
osdElementDurationUs[osdCurrentElement]--;
|
||||
osdElementDurationFractionUs[osdCurrentElement]--;
|
||||
}
|
||||
} while (moreElements);
|
||||
};
|
||||
|
||||
if (moreElements) {
|
||||
// There are more elements to draw
|
||||
break;
|
||||
}
|
||||
#ifdef USE_SPEC_PREARM_SCREEN
|
||||
osdDrawSpec(osdDisplayPort);
|
||||
#endif // USE_SPEC_PREARM_SCREEN
|
||||
|
||||
osdElementGroup = 0;
|
||||
|
||||
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
|
||||
// rendered which is unrepresentative, so ignore
|
||||
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)) {
|
||||
osdStateDurationFractionUs[osdCurrentState] = executeTimeUs << OSD_EXEC_TIME_SHIFT;
|
||||
} else if (osdStateDurationFractionUs[osdCurrentState] > 0) {
|
||||
|
@ -1654,16 +1607,12 @@ void osdUpdate(timeUs_t currentTimeUs)
|
|||
}
|
||||
}
|
||||
|
||||
if (osdState == OSD_STATE_UPDATE_ELEMENTS) {
|
||||
schedulerSetNextStateTime((osdElementGroupDurationFractionUs[osdElementGroup] >> OSD_EXEC_TIME_SHIFT) + OSD_ELEMENT_RENDER_GROUP_MARGIN);
|
||||
} else {
|
||||
if (osdState == OSD_STATE_IDLE) {
|
||||
schedulerSetNextStateTime((osdStateDurationFractionUs[OSD_STATE_CHECK] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);
|
||||
} else {
|
||||
schedulerSetNextStateTime((osdStateDurationFractionUs[osdState] >> OSD_EXEC_TIME_SHIFT) + OSD_TASK_MARGIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void osdSuppressStats(bool flag)
|
||||
{
|
||||
|
|
|
@ -286,15 +286,20 @@ static int getEscRpmFreq(int i)
|
|||
|
||||
static void renderOsdEscRpmOrFreq(getEscRpmOrFreqFnPtr escFnPtr, osdElementParms_t *element)
|
||||
{
|
||||
static uint8_t motor = 0;
|
||||
int x = element->elemPosX;
|
||||
int y = element->elemPosY;
|
||||
for (int i=0; i < getMotorCount(); i++) {
|
||||
char rpmStr[6];
|
||||
const int rpm = MIN((*escFnPtr)(i),99999);
|
||||
const int len = tfp_sprintf(rpmStr, "%d", rpm);
|
||||
rpmStr[len] = '\0';
|
||||
osdDisplayWrite(element, x, y + i, DISPLAYPORT_SEVERITY_NORMAL, rpmStr);
|
||||
const int rpm = MIN((*escFnPtr)(motor),99999);
|
||||
tfp_sprintf(rpmStr, "%d", rpm);
|
||||
osdDisplayWrite(element, x, y + motor, DISPLAYPORT_SEVERITY_NORMAL, rpmStr);
|
||||
|
||||
if (++motor == getMotorCount()) {
|
||||
motor = 0;
|
||||
} else {
|
||||
element->rendered = false;
|
||||
}
|
||||
|
||||
element->drawElement = false;
|
||||
}
|
||||
#endif
|
||||
|
@ -708,6 +713,7 @@ static void osdElementAntiGravity(osdElementParms_t *element)
|
|||
|
||||
static void osdElementArtificialHorizon(osdElementParms_t *element)
|
||||
{
|
||||
static int x = -4;
|
||||
// Get pitch and roll limits in tenths of degrees
|
||||
const int maxPitch = osdConfig()->ahMaxPitch * 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
|
||||
|
||||
for (int x = -4; x <= 4; x++) {
|
||||
const int y = ((-rollAngle * x) / 64) - pitchAngle;
|
||||
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)));
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -794,24 +807,43 @@ static void osdElementCoreTemperature(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 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 height = constrain(osdConfig()->camera_frame_height, OSD_CAMERA_FRAME_MIN_HEIGHT, OSD_CAMERA_FRAME_MAX_HEIGHT);
|
||||
|
||||
if (renderPhase != BOTTOM) {
|
||||
// Rendering not yet complete
|
||||
element->rendered = false;
|
||||
}
|
||||
|
||||
if (renderPhase == MIDDLE) {
|
||||
static uint8_t i = 1;
|
||||
|
||||
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);
|
||||
|
||||
if (++i == height) {
|
||||
i = 1;
|
||||
renderPhase = BOTTOM;
|
||||
}
|
||||
} else {
|
||||
element->buff[0] = SYM_STICK_OVERLAY_CENTER;
|
||||
for (int i = 1; i < (width - 1); i++) {
|
||||
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);
|
||||
for (int i = 1; i < (height - 1); i++) {
|
||||
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);
|
||||
}
|
||||
renderPhase = MIDDLE;
|
||||
} else {
|
||||
osdDisplayWrite(element, xpos, ypos + height - 1, DISPLAYPORT_SEVERITY_NORMAL, element->buff);
|
||||
renderPhase = TOP;
|
||||
}
|
||||
}
|
||||
|
||||
element->drawElement = false; // element already drawn
|
||||
}
|
||||
|
@ -1184,17 +1216,32 @@ static void osdElementGpsLapTimeBest3(osdElementParms_t *element)
|
|||
|
||||
static void osdBackgroundHorizonSidebars(osdElementParms_t *element)
|
||||
{
|
||||
static bool renderLevel = false;
|
||||
static int8_t y = -AH_SIDEBAR_HEIGHT_POS;
|
||||
// Draw AH sides
|
||||
const int8_t hudwidth = AH_SIDEBAR_WIDTH_POS;
|
||||
const int8_t hudheight = AH_SIDEBAR_HEIGHT_POS;
|
||||
for (int y = -hudheight; y <= hudheight; y++) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (y == hudheight) {
|
||||
// Rendering is complete, so prepare to start again
|
||||
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
|
||||
}
|
||||
|
@ -1444,19 +1491,24 @@ static void osdElementPower(osdElementParms_t *element)
|
|||
|
||||
static void osdElementRcChannels(osdElementParms_t *element)
|
||||
{
|
||||
static uint8_t channel = 0;
|
||||
const uint8_t xpos = element->elemPosX;
|
||||
const uint8_t ypos = element->elemPosY;
|
||||
|
||||
for (int i = 0; i < OSD_RCCHANNELS_COUNT; i++) {
|
||||
if (osdConfig()->rcChannels[i] >= 0) {
|
||||
if (osdConfig()->rcChannels[channel] >= 0) {
|
||||
// Translate (1000, 2000) to (-1000, 1000)
|
||||
int data = scaleRange(rcData[osdConfig()->rcChannels[i]], PWM_RANGE_MIN, PWM_RANGE_MAX, -1000, 1000);
|
||||
int data = scaleRange(rcData[osdConfig()->rcChannels[channel]], PWM_RANGE_MIN, PWM_RANGE_MAX, -1000, 1000);
|
||||
// Opt for the simplest formatting for now.
|
||||
// Decimal notation can be added when tfp_sprintf supports float among fancy options.
|
||||
char fmtbuf[6];
|
||||
tfp_sprintf(fmtbuf, "%5d", data);
|
||||
osdDisplayWrite(element, xpos, ypos + i, DISPLAYPORT_SEVERITY_NORMAL, fmtbuf);
|
||||
osdDisplayWrite(element, xpos, ypos + channel, DISPLAYPORT_SEVERITY_NORMAL, fmtbuf);
|
||||
}
|
||||
|
||||
if (++channel == OSD_RCCHANNELS_COUNT) {
|
||||
channel = 0;
|
||||
} else {
|
||||
element->rendered = false;
|
||||
}
|
||||
|
||||
element->drawElement = false; // element already drawn
|
||||
|
@ -1531,21 +1583,37 @@ static void osdElementRsnr(osdElementParms_t *element)
|
|||
#ifdef USE_OSD_STICK_OVERLAY
|
||||
static void osdBackgroundStickOverlay(osdElementParms_t *element)
|
||||
{
|
||||
static enum {VERT, HORZ} renderPhase = VERT;
|
||||
const uint8_t xpos = element->elemPosX;
|
||||
const uint8_t ypos = element->elemPosY;
|
||||
|
||||
// Draw the axis first
|
||||
for (unsigned x = 0; x < OSD_STICK_OVERLAY_WIDTH; x++) {
|
||||
for (unsigned y = 0; y < OSD_STICK_OVERLAY_HEIGHT; y++) {
|
||||
// draw the axes, vertical and horizonal
|
||||
if ((x == ((OSD_STICK_OVERLAY_WIDTH - 1) / 2)) && (y == (OSD_STICK_OVERLAY_HEIGHT - 1) / 2)) {
|
||||
osdDisplayWriteChar(element, xpos + x, ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_CENTER);
|
||||
} else if (x == ((OSD_STICK_OVERLAY_WIDTH - 1) / 2)) {
|
||||
osdDisplayWriteChar(element, xpos + x, ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL);
|
||||
} else if (y == ((OSD_STICK_OVERLAY_HEIGHT - 1) / 2)) {
|
||||
osdDisplayWriteChar(element, xpos + x, ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_HORIZONTAL);
|
||||
if (renderPhase == VERT) {
|
||||
static uint8_t y = 0;
|
||||
osdDisplayWriteChar(element, xpos + ((OSD_STICK_OVERLAY_WIDTH - 1) / 2), ypos + y, DISPLAYPORT_SEVERITY_NORMAL, SYM_STICK_OVERLAY_VERTICAL);
|
||||
|
||||
y++;
|
||||
|
||||
if (y == (OSD_STICK_OVERLAY_HEIGHT - 1) / 2) {
|
||||
// Skip over horizontal
|
||||
y++;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -2016,14 +2084,14 @@ void osdAddActiveElements(void)
|
|||
#endif
|
||||
}
|
||||
|
||||
static void osdDrawSingleElement(displayPort_t *osdDisplayPort, uint8_t item)
|
||||
static bool osdDrawSingleElement(displayPort_t *osdDisplayPort, uint8_t item)
|
||||
{
|
||||
if (!osdElementDrawFunction[item]) {
|
||||
// Element has no drawing function
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
if (!osdDisplayPort->useDeviceBlink && BLINK(item)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
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.osdDisplayPort = osdDisplayPort;
|
||||
element.drawElement = true;
|
||||
element.rendered = true;
|
||||
element.attr = DISPLAYPORT_SEVERITY_NORMAL;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
return element.rendered;
|
||||
}
|
||||
|
||||
static void osdDrawSingleElementBackground(displayPort_t *osdDisplayPort, uint8_t item)
|
||||
static bool osdDrawSingleElementBackground(displayPort_t *osdDisplayPort, uint8_t item)
|
||||
{
|
||||
if (!osdElementBackgroundFunction[item]) {
|
||||
// Element has no background drawing function
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
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.buff = (char *)&buff;
|
||||
element.osdDisplayPort = osdDisplayPort;
|
||||
element.rendered = true;
|
||||
element.drawElement = true;
|
||||
|
||||
// Call the element background drawing function
|
||||
|
@ -2076,6 +2148,8 @@ static void osdDrawSingleElementBackground(displayPort_t *osdDisplayPort, uint8_
|
|||
if (element.drawElement) {
|
||||
osdDisplayWrite(&element, elemPosX, elemPosY, DISPLAYPORT_SEVERITY_NORMAL, buff);
|
||||
}
|
||||
|
||||
return element.rendered;
|
||||
}
|
||||
|
||||
static uint8_t activeElement = 0;
|
||||
|
@ -2094,81 +2168,127 @@ uint8_t osdGetActiveElementCount(void)
|
|||
bool osdDrawNextActiveElement(displayPort_t *osdDisplayPort, timeUs_t currentTimeUs)
|
||||
{
|
||||
UNUSED(currentTimeUs);
|
||||
static bool backgroundRendered = false;
|
||||
bool retval = true;
|
||||
|
||||
if (activeElement >= activeOsdElementCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!backgroundLayerSupported) {
|
||||
if (!backgroundLayerSupported && !backgroundRendered) {
|
||||
// If the background layer isn't supported then we
|
||||
// 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])) {
|
||||
// Prepare to render the background of the next element
|
||||
backgroundRendered = false;
|
||||
if (++activeElement >= activeOsdElementCount) {
|
||||
activeElement = 0;
|
||||
retval = false;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#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;
|
||||
static int currentRow;
|
||||
|
||||
const uint8_t midRow = osdDisplayPort->rows / 2;
|
||||
const uint8_t midCol = osdDisplayPort->cols / 2;
|
||||
|
||||
char buff[OSD_ELEMENT_BUFFER_LENGTH] = "";
|
||||
|
||||
memset(buff,0,strlen(buff));
|
||||
int len = 0;
|
||||
int currentRow = midRow - 3;
|
||||
|
||||
switch (specState) {
|
||||
default:
|
||||
case CLR:
|
||||
displayClearScreen(osdDisplayPort, DISPLAY_CLEAR_NONE);
|
||||
currentRow = midRow - 3;
|
||||
|
||||
specState = RPM;
|
||||
break;
|
||||
|
||||
case RPM:
|
||||
#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);
|
||||
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);
|
||||
specState = POLES;
|
||||
} else {
|
||||
specState = THR;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
memset(buff,0,strlen(buff));
|
||||
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);
|
||||
} else
|
||||
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
|
||||
|
||||
specState = THR;
|
||||
break;
|
||||
|
||||
case THR:
|
||||
#endif // #USE_RPM_LIMIT
|
||||
{
|
||||
memset(buff,0,strlen(buff));
|
||||
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);
|
||||
}
|
||||
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
|
||||
|
||||
memset(buff,0,strlen(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);
|
||||
displayWrite(osdDisplayPort, midCol - (len / 2), currentRow++, DISPLAYPORT_SEVERITY_NORMAL, buff);
|
||||
|
||||
memset(buff,0,strlen(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);
|
||||
|
||||
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, buff);
|
||||
}
|
||||
|
||||
specState = VER;
|
||||
break;
|
||||
|
||||
case VER:
|
||||
len = strlen(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
|
||||
|
||||
|
@ -2178,7 +2298,7 @@ void osdDrawActiveElementsBackground(displayPort_t *osdDisplayPort)
|
|||
displayLayerSelect(osdDisplayPort, DISPLAYPORT_LAYER_BACKGROUND);
|
||||
displayClearScreen(osdDisplayPort, DISPLAY_CLEAR_WAIT);
|
||||
for (unsigned i = 0; i < activeOsdElementCount; i++) {
|
||||
osdDrawSingleElementBackground(osdDisplayPort, activeOsdElementArray[i]);
|
||||
while (!osdDrawSingleElementBackground(osdDisplayPort, activeOsdElementArray[i]));
|
||||
}
|
||||
displayLayerSelect(osdDisplayPort, DISPLAYPORT_LAYER_FOREGROUND);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ typedef struct osdElementParms_s {
|
|||
char *buff;
|
||||
displayPort_t *osdDisplayPort;
|
||||
bool drawElement;
|
||||
bool rendered;
|
||||
uint8_t attr;
|
||||
} osdElementParms_t;
|
||||
|
||||
|
@ -65,5 +66,5 @@ void osdResetAlarms(void);
|
|||
void osdUpdateAlarms(void);
|
||||
bool osdElementsNeedAccelerometer(void);
|
||||
#ifdef USE_SPEC_PREARM_SCREEN
|
||||
void osdDrawSpec(displayPort_t *osdDisplayPort);
|
||||
bool osdDrawSpec(displayPort_t *osdDisplayPort);
|
||||
#endif // USE_SPEC_PREARM_SCREEN
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue