2 * This file is part of INAV Project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
6 * You can obtain one at http://mozilla.org/MPL/2.0/.
8 * Alternatively, the contents of this file may be used under the terms
9 * of the GNU General Public License Version 3, as described below:
11 * This file is free software: you may copy, redistribute and/or modify
12 * it under the terms of the GNU General Public License as published by the
13 * Free Software Foundation, either version 3 of the License, or (at your
14 * option) any later version.
16 * This file is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19 * Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see http://www.gnu.org/licenses/.
31 FILE_COMPILE_FOR_SPEED
33 #if defined(USE_OSD) && defined(USE_MSP_OSD)
35 #include "common/utils.h"
36 #include "common/printf.h"
37 #include "common/time.h"
38 #include "common/bitarray.h"
42 #include "drivers/display.h"
43 #include "drivers/display_font_metadata.h"
44 #include "drivers/osd_symbols.h"
46 #include "fc/rc_modes.h"
50 #include "msp/msp_protocol.h"
51 #include "msp/msp_serial.h"
53 #include "displayport_msp_osd.h"
55 #include "displayport_msp_bf_compat.h"
57 #define FONT_VERSION 3
59 #define MSP_HEARTBEAT 0
61 #define MSP_CLEAR_SCREEN 2
62 #define MSP_WRITE_STRING 3
63 #define MSP_DRAW_SCREEN 4
64 #define MSP_SET_OPTIONS 5
66 typedef enum { // defines are from hdzero code
69 HD_3016
, // Special HDZERO mode that just sends the centre 30x16 of the 50x18 canvas to the VRX
70 HD_6022
// added to support DJI wtfos 60x22 grid
73 #define DRAW_FREQ_DENOM 4 // 60Hz
74 #define TX_BUFFER_SIZE 1024
75 #define VTX_TIMEOUT 1000 // 1 second timer
77 static mspProcessCommandFnPtr mspProcessCommand
;
78 static mspPort_t mspPort
;
79 static displayPort_t mspOsdDisplayPort
;
80 static bool vtxSeen
, vtxActive
, vtxReset
;
81 static timeMs_t vtxHeartbeat
;
90 #define HDZERO_COLS 50
91 #define HDZERO_ROWS 18
93 #define AVATAR_COLS 54
94 #define AVATAR_ROWS 20
99 // set COLS and ROWS to largest size available
100 #define COLS DJI_COLS
101 #define ROWS DJI_ROWS
104 #define SCREENSIZE (ROWS*COLS)
106 static uint8_t currentOsdMode
; // HDZero screen mode can change across layouts
108 static uint8_t screen
[SCREENSIZE
];
109 static BITARRAY_DECLARE(fontPage
, SCREENSIZE
); // font page for each character on the screen
110 static BITARRAY_DECLARE(dirty
, SCREENSIZE
); // change status for each character on the screen
111 static bool screenCleared
;
112 static uint8_t screenRows
, screenCols
;
113 static videoSystem_e osdVideoSystem
;
115 extern uint8_t cliMode
;
117 static void checkVtxPresent(void)
119 if (vtxActive
&& (millis()-vtxHeartbeat
) > VTX_TIMEOUT
) {
124 static int output(displayPort_t
*displayPort
, uint8_t cmd
, uint8_t *subcmd
, int len
)
131 if (!cliMode
&& vtxActive
) {
132 sent
= mspSerialPushPort(cmd
, subcmd
, len
, &mspPort
, MSP_V1
);
138 static uint8_t determineHDZeroOsdMode(void)
144 // Check if all visible widgets are in the center 30x16 chars of the canvas.
145 int activeLayout
= osdGetActiveLayout(NULL
);
146 osd_items_e index
= 0;
148 index
= osdIncElementIndex(index
);
149 uint16_t pos
= osdLayoutsConfig()->item_pos
[activeLayout
][index
];
150 if (OSD_VISIBLE(pos
)) {
151 uint8_t elemPosX
= OSD_X(pos
);
152 uint8_t elemPosY
= OSD_Y(pos
);
153 if (!osdItemIsFixed(index
) && (elemPosX
< 10 || elemPosX
> 39 || elemPosY
== 0 || elemPosY
== 17)) {
162 static int setDisplayMode(displayPort_t
*displayPort
)
164 if (osdVideoSystem
== VIDEO_SYSTEM_HDZERO
) {
165 currentOsdMode
= determineHDZeroOsdMode(); // Can change between layouts
168 uint8_t subcmd
[] = { MSP_SET_OPTIONS
, 0, currentOsdMode
}; // Font selection, mode (SD/HD)
169 return output(displayPort
, MSP_DISPLAYPORT
, subcmd
, sizeof(subcmd
));
172 static void init(void)
174 memset(screen
, SYM_BLANK
, sizeof(screen
));
175 BITARRAY_CLR_ALL(fontPage
);
176 BITARRAY_CLR_ALL(dirty
);
179 static int clearScreen(displayPort_t
*displayPort
)
181 uint8_t subcmd
[] = { MSP_CLEAR_SCREEN
};
183 if (!cmsInMenu
&& IS_RC_MODE_ACTIVE(BOXOSD
)) { // OSD is off
184 output(displayPort
, MSP_DISPLAYPORT
, subcmd
, sizeof(subcmd
));
185 subcmd
[0] = MSP_DRAW_SCREEN
;
190 setDisplayMode(displayPort
);
191 screenCleared
= true;
194 return output(displayPort
, MSP_DISPLAYPORT
, subcmd
, sizeof(subcmd
));
197 static bool readChar(displayPort_t
*displayPort
, uint8_t col
, uint8_t row
, uint16_t *c
, textAttributes_t
*attr
)
201 uint16_t pos
= (row
* COLS
) + col
;
202 if (pos
>= SCREENSIZE
) {
207 if (bitArrayGet(fontPage
, pos
)) {
212 *attr
= TEXT_ATTRIBUTES_NONE
;
218 static int setChar(const uint16_t pos
, const uint16_t c
)
220 if (pos
< SCREENSIZE
) {
221 uint8_t ch
= c
& 0xFF;
222 bool page
= (c
>> 8);
223 if (screen
[pos
] != ch
|| bitArrayGet(fontPage
, pos
) != page
) {
225 (page
) ? bitArraySet(fontPage
, pos
) : bitArrayClr(fontPage
, pos
);
226 bitArraySet(dirty
, pos
);
232 static int writeChar(displayPort_t
*displayPort
, uint8_t col
, uint8_t row
, uint16_t c
, textAttributes_t attr
)
237 return setChar((row
* COLS
) + col
, c
);
240 static int writeString(displayPort_t
*displayPort
, uint8_t col
, uint8_t row
, const char *string
, textAttributes_t attr
)
245 uint16_t pos
= (row
* COLS
) + col
;
247 setChar(pos
++, *string
++);
253 * Write only changed characters to the VTX
255 static int drawScreen(displayPort_t
*displayPort
) // 250Hz
257 static uint8_t counter
= 0;
259 if ((!cmsInMenu
&& IS_RC_MODE_ACTIVE(BOXOSD
)) || (counter
++ % DRAW_FREQ_DENOM
)) {
264 uint8_t subcmd
[COLS
+ 4];
265 uint8_t updateCount
= 0;
266 subcmd
[0] = MSP_WRITE_STRING
;
268 int next
= BITARRAY_FIND_FIRST_SET(dirty
, 0);
270 // Look for sequential dirty characters on the same line for the same font page
272 uint8_t row
= pos
/ COLS
;
273 uint8_t col
= pos
% COLS
;
274 int endOfLine
= row
* COLS
+ screenCols
;
275 bool page
= bitArrayGet(fontPage
, pos
);
279 bitArrayClr(dirty
, pos
);
280 subcmd
[len
++] = (osdVideoSystem
== VIDEO_SYSTEM_BFCOMPAT
) ? get_bf_character(screen
[pos
++], page
): screen
[pos
++];
282 if (bitArrayGet(dirty
, pos
)) {
285 } while (next
== pos
&& next
< endOfLine
&& bitArrayGet(fontPage
, next
) == page
);
289 subcmd
[3] = (osdVideoSystem
== VIDEO_SYSTEM_BFCOMPAT
) ? 0 : page
;
290 output(displayPort
, MSP_DISPLAYPORT
, subcmd
, len
);
292 next
= BITARRAY_FIND_FIRST_SET(dirty
, pos
);
295 if (updateCount
> 0 || screenCleared
) {
297 screenCleared
= false;
300 subcmd
[0] = MSP_DRAW_SCREEN
;
301 output(displayPort
, MSP_DISPLAYPORT
, subcmd
, 1);
305 clearScreen(displayPort
);
312 static void resync(displayPort_t
*displayPort
)
314 displayPort
->rows
= screenRows
;
315 displayPort
->cols
= screenCols
;
318 static int screenSize(const displayPort_t
*displayPort
)
320 return (displayPort
->rows
* displayPort
->cols
);
323 static uint32_t txBytesFree(const displayPort_t
*displayPort
)
326 return mspSerialTxBytesFree();
329 static bool getFontMetadata(displayFontMetadata_t
*metadata
, const displayPort_t
*displayPort
)
332 metadata
->charCount
= 512;
333 metadata
->version
= FONT_VERSION
;
337 static textAttributes_t
supportedTextAttributes(const displayPort_t
*displayPort
)
340 return TEXT_ATTRIBUTES_NONE
;
343 static bool isTransferInProgress(const displayPort_t
*displayPort
)
349 static bool isReady(displayPort_t
*displayPort
)
355 static int heartbeat(displayPort_t
*displayPort
)
357 uint8_t subcmd
[] = { MSP_HEARTBEAT
};
358 return output(displayPort
, MSP_DISPLAYPORT
, subcmd
, sizeof(subcmd
));
361 static int grab(displayPort_t
*displayPort
)
363 return heartbeat(displayPort
);
366 static int release(displayPort_t
*displayPort
)
368 uint8_t subcmd
[] = { MSP_RELEASE
};
369 return output(displayPort
, MSP_DISPLAYPORT
, subcmd
, sizeof(subcmd
));
372 static const displayPortVTable_t mspOsdVTable
= {
375 .clearScreen
= clearScreen
,
376 .drawScreen
= drawScreen
,
377 .screenSize
= screenSize
,
378 .writeString
= writeString
,
379 .writeChar
= writeChar
,
380 .readChar
= readChar
,
381 .isTransferInProgress
= isTransferInProgress
,
382 .heartbeat
= heartbeat
,
384 .txBytesFree
= txBytesFree
,
385 .supportedTextAttributes
= supportedTextAttributes
,
386 .getFontMetadata
= getFontMetadata
,
390 bool mspOsdSerialInit(void)
392 static volatile uint8_t txBuffer
[TX_BUFFER_SIZE
];
393 memset(&mspPort
, 0, sizeof(mspPort_t
));
395 serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_MSP_OSD
);
397 serialPort_t
*port
= openSerialPort(portConfig
->identifier
, FUNCTION_MSP_OSD
, NULL
, NULL
,
398 baudRates
[portConfig
->peripheral_baudrateIndex
], MODE_RXTX
, SERIAL_NOT_INVERTED
);
401 // Use a bigger TX buffer size to accommodate the configuration menus
402 port
->txBuffer
= txBuffer
;
403 port
->txBufferSize
= TX_BUFFER_SIZE
;
404 port
->txBufferTail
= 0;
405 port
->txBufferHead
= 0;
407 resetMspPort(&mspPort
, port
);
416 displayPort_t
* mspOsdDisplayPortInit(const videoSystem_e videoSystem
)
418 if (mspOsdSerialInit()) {
419 switch(videoSystem
) {
420 case VIDEO_SYSTEM_AUTO
:
421 case VIDEO_SYSTEM_BFCOMPAT
:
422 case VIDEO_SYSTEM_PAL
:
423 currentOsdMode
= SD_3016
;
424 screenRows
= PAL_ROWS
;
425 screenCols
= PAL_COLS
;
427 case VIDEO_SYSTEM_NTSC
:
428 currentOsdMode
= SD_3016
;
429 screenRows
= NTSC_ROWS
;
430 screenCols
= NTSC_COLS
;
432 case VIDEO_SYSTEM_HDZERO
:
433 currentOsdMode
= HD_5018
;
434 screenRows
= HDZERO_ROWS
;
435 screenCols
= HDZERO_COLS
;
437 case VIDEO_SYSTEM_DJIWTF
:
438 currentOsdMode
= HD_6022
;
439 screenRows
= DJI_ROWS
;
440 screenCols
= DJI_COLS
;
446 osdVideoSystem
= videoSystem
;
448 displayInit(&mspOsdDisplayPort
, &mspOsdVTable
);
450 return &mspOsdDisplayPort
;
456 * Intercept MSP processor.
457 * VTX sends an MSP command every 125ms or so.
458 * VTX will have be marked as not ready if no commands received within VTX_TIMEOUT ms.
460 static mspResult_e
processMspCommand(mspPacket_t
*cmd
, mspPacket_t
*reply
, mspPostProcessFnPtr
*mspPostProcessFn
)
462 if ((vtxSeen
&& !vtxActive
) || (cmd
->cmd
== MSP_EEPROM_WRITE
)) {
466 vtxSeen
= vtxActive
= true;
467 vtxHeartbeat
= millis();
469 // Process MSP command
470 return mspProcessCommand(cmd
, reply
, mspPostProcessFn
);
473 void mspOsdSerialProcess(mspProcessCommandFnPtr mspProcessCommandFn
)
476 mspProcessCommand
= mspProcessCommandFn
;
477 mspSerialProcessOnePort(&mspPort
, MSP_SKIP_NON_MSP_DATA
, processMspCommand
);
481 #endif // USE_MSP_OSD