diff --git a/Makefile b/Makefile index fdc04f36c..417fe68d8 100644 --- a/Makefile +++ b/Makefile @@ -606,6 +606,7 @@ HIGHEND_SRC = \ telemetry/ltm.c \ telemetry/mavlink.c \ sensors/esc_sensor.c \ + io/vtx_smartaudio.c SPEED_OPTIMISED_SRC := "" SIZE_OPTIMISED_SRC := "" @@ -709,7 +710,8 @@ SIZE_OPTIMISED_SRC := $(SIZE_OPTIMISED_SRC) \ cms/cms_menu_ledstrip.c \ cms/cms_menu_misc.c \ cms/cms_menu_osd.c \ - cms/cms_menu_vtx.c + cms/cms_menu_vtx.c \ + io/vtx_smartaudio.c endif #F3 ifeq ($(TARGET),$(filter $(TARGET),$(F4_TARGETS))) diff --git a/src/main/cms/cms.c b/src/main/cms/cms.c index 8ec5c3c3c..33b01b35d 100644 --- a/src/main/cms/cms.c +++ b/src/main/cms/cms.c @@ -230,13 +230,28 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; + case OME_Submenu: case OME_Funcall: if (IS_PRINTVALUE(p)) { - cnt = displayWrite(pDisplay, RIGHT_MENU_COLUMN(pDisplay), row, ">"); + + int colPos = RIGHT_MENU_COLUMN(pDisplay); + + if ((p->type == OME_Submenu) && p->func && (p->flags & OPTSTRING)) { + + // Special case of sub menu entry with optional value display. + + char *str = ((CMSMenuOptFuncPtr)p->func)(); + cnt = displayWrite(pDisplay, colPos, row, str); + colPos += strlen(str); + } + + cnt += displayWrite(pDisplay, colPos, row, ">"); + CLR_PRINTVALUE(p); } break; + case OME_Bool: if (IS_PRINTVALUE(p) && p->data) { if (*((uint8_t *)(p->data))) { @@ -247,7 +262,8 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; - case OME_TAB: { + + case OME_TAB: if (IS_PRINTVALUE(p)) { OSD_TAB_t *ptr = p->data; //cnt = displayWrite(pDisplay, RIGHT_MENU_COLUMN(pDisplay) - 5, row, (char *)ptr->names[*ptr->val]); @@ -255,7 +271,7 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; - } + #ifdef OSD case OME_VISIBLE: if (IS_PRINTVALUE(p) && p->data) { @@ -273,6 +289,7 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) } break; #endif + case OME_UINT8: if (IS_PRINTVALUE(p) && p->data) { OSD_UINT8_t *ptr = p->data; @@ -282,6 +299,7 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; + case OME_INT8: if (IS_PRINTVALUE(p) && p->data) { OSD_INT8_t *ptr = p->data; @@ -291,6 +309,7 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; + case OME_UINT16: if (IS_PRINTVALUE(p) && p->data) { OSD_UINT16_t *ptr = p->data; @@ -300,6 +319,7 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; + case OME_INT16: if (IS_PRINTVALUE(p) && p->data) { OSD_UINT16_t *ptr = p->data; @@ -309,6 +329,7 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; + case OME_FLOAT: if (IS_PRINTVALUE(p) && p->data) { OSD_FLOAT_t *ptr = p->data; @@ -318,6 +339,7 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; + case OME_Label: if (IS_PRINTVALUE(p) && p->data) { // A label with optional string, immediately following text @@ -325,10 +347,12 @@ static int cmsDrawMenuEntry(displayPort_t *pDisplay, OSD_Entry *p, uint8_t row) CLR_PRINTVALUE(p); } break; + case OME_OSD_Exit: case OME_END: case OME_Back: break; + case OME_MENU: // Fall through default: @@ -645,17 +669,34 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) switch (p->type) { case OME_Submenu: - case OME_Funcall: + if (key == KEY_RIGHT) { + cmsMenuChange(pDisplay, p->data); + res = BUTTON_PAUSE; + } + break; + + case OME_Funcall:; + long retval; + if (p->func && key == KEY_RIGHT) { + retval = p->func(pDisplay, p->data); + if (retval == MENU_CHAIN_BACK) + cmsMenuBack(pDisplay); + res = BUTTON_PAUSE; + } + break; + case OME_OSD_Exit: if (p->func && key == KEY_RIGHT) { p->func(pDisplay, p->data); res = BUTTON_PAUSE; } break; + case OME_Back: cmsMenuBack(pDisplay); res = BUTTON_PAUSE; break; + case OME_Bool: if (p->data) { uint8_t *val = p->data; @@ -666,6 +707,7 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) SET_PRINTVALUE(p); } break; + #ifdef OSD case OME_VISIBLE: if (p->data) { @@ -682,6 +724,7 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) } break; #endif + case OME_UINT8: case OME_FLOAT: if (p->data) { @@ -700,6 +743,7 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) } } break; + case OME_TAB: if (p->type == OME_TAB) { OSD_TAB_t *ptr = p->data; @@ -717,6 +761,7 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) SET_PRINTVALUE(p); } break; + case OME_INT8: if (p->data) { OSD_INT8_t *ptr = p->data; @@ -734,6 +779,7 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) } } break; + case OME_UINT16: if (p->data) { OSD_UINT16_t *ptr = p->data; @@ -751,6 +797,7 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) } } break; + case OME_INT16: if (p->data) { OSD_INT16_t *ptr = p->data; @@ -768,11 +815,14 @@ STATIC_UNIT_TESTED uint16_t cmsHandleKey(displayPort_t *pDisplay, uint8_t key) } } break; + case OME_String: break; + case OME_Label: case OME_END: break; + case OME_MENU: // Shouldn't happen break; diff --git a/src/main/cms/cms_menu_builtin.c b/src/main/cms/cms_menu_builtin.c index 50a85a4c0..a0dc6b19e 100644 --- a/src/main/cms/cms_menu_builtin.c +++ b/src/main/cms/cms_menu_builtin.c @@ -45,6 +45,9 @@ #include "cms/cms_menu_ledstrip.h" #include "cms/cms_menu_misc.h" +// User supplied menus + +#include "io/vtx_smartaudio_cms.h" // Info @@ -93,6 +96,9 @@ static OSD_Entry menuFeaturesEntries[] = #if defined(VTX) || defined(USE_RTC6705) {"VTX", OME_Submenu, cmsMenuChange, &cmsx_menuVtx, 0}, #endif // VTX || USE_RTC6705 +#if defined(VTX_SMARTAUDIO) + {"VTX", OME_Submenu, cmsMenuChange, &cmsx_menuVtxSmartAudio, 0}, +#endif #ifdef LED_STRIP {"LED STRIP", OME_Submenu, cmsMenuChange, &cmsx_menuLedstrip, 0}, #endif // LED_STRIP diff --git a/src/main/cms/cms_menu_imu.c b/src/main/cms/cms_menu_imu.c index f95560a2c..26cce4558 100644 --- a/src/main/cms/cms_menu_imu.c +++ b/src/main/cms/cms_menu_imu.c @@ -362,14 +362,13 @@ static OSD_Entry cmsx_menuImuEntries[] = {"PID PROF", OME_UINT8, cmsx_profileIndexOnChange, &(OSD_UINT8_t){ &tmpProfileIndex, 1, MAX_PROFILE_COUNT, 1}, 0}, {"PID", OME_Submenu, cmsMenuChange, &cmsx_menuPid, 0}, - {"OTHER PP", OME_Submenu, cmsMenuChange, &cmsx_menuProfileOther, 0}, + {"MISC PP", OME_Submenu, cmsMenuChange, &cmsx_menuProfileOther, 0}, + {"FILT PP", OME_Submenu, cmsMenuChange, &cmsx_menuFilterPerProfile, 0}, {"RATE PROF", OME_UINT8, cmsx_rateProfileIndexOnChange, &(OSD_UINT8_t){ &tmpRateProfileIndex, 1, MAX_RATEPROFILES, 1}, 0}, {"RATE", OME_Submenu, cmsMenuChange, &cmsx_menuRateProfile, 0}, - {"FLT PP", OME_Submenu, cmsMenuChange, &cmsx_menuFilterPerProfile, 0}, - - {"FLT GLB", OME_Submenu, cmsMenuChange, &cmsx_menuFilterGlobal, 0}, + {"FILT GLB", OME_Submenu, cmsMenuChange, &cmsx_menuFilterGlobal, 0}, {"BACK", OME_Back, NULL, NULL, 0}, {NULL, OME_END, NULL, NULL, 0} diff --git a/src/main/cms/cms_types.h b/src/main/cms/cms_types.h index 427e1d38e..4bea82e62 100644 --- a/src/main/cms/cms_types.h +++ b/src/main/cms/cms_types.h @@ -66,6 +66,7 @@ typedef struct #define PRINT_VALUE 0x01 // Value has been changed, need to redraw #define PRINT_LABEL 0x02 // Text label should be printed #define DYNAMIC 0x04 // Value should be updated dynamically +#define OPTSTRING 0x08 // (Temporary) Flag for OME_Submenu, indicating func should be called to get a string to display. #define IS_PRINTVALUE(p) ((p)->flags & PRINT_VALUE) #define SET_PRINTVALUE(p) { (p)->flags |= PRINT_VALUE; } @@ -77,9 +78,11 @@ typedef struct #define IS_DYNAMIC(p) ((p)->flags & DYNAMIC) - typedef long (*CMSMenuFuncPtr)(void); +// Special return value(s) for function chaining by CMSMenuFuncPtr +#define MENU_CHAIN_BACK (-1) // Causes automatic cmsMenuBack + /* onExit function is called with self: (1) Pointer to an OSD_Entry when cmsMenuBack() was called. @@ -153,3 +156,7 @@ typedef struct { char *val; } OSD_String_t; + +// This is a function used in the func member if the type is OME_Submenu. + +typedef char * (*CMSMenuOptFuncPtr)(void); diff --git a/src/main/config/config_master.h b/src/main/config/config_master.h index e2e51979d..cbe9a26c0 100644 --- a/src/main/config/config_master.h +++ b/src/main/config/config_master.h @@ -198,7 +198,7 @@ typedef struct master_s { uint8_t transponderData[6]; #endif -#ifdef USE_RTC6705 +#if defined(USE_RTC6705) uint8_t vtx_channel; uint8_t vtx_power; #endif diff --git a/src/main/drivers/max7456.c b/src/main/drivers/max7456.c index 3e4d541d8..d1d1a7188 100755 --- a/src/main/drivers/max7456.c +++ b/src/main/drivers/max7456.c @@ -37,9 +37,8 @@ #include "max7456.h" #include "max7456_symbols.h" - - //MAX7456 opcodes +//XXX These are not opcodes but reg addrs. Substitute with MAX7456ADD_xxx. #define DMM_REG 0x04 #define DMAH_REG 0x05 #define DMAL_REG 0x06 @@ -52,15 +51,21 @@ #define MAX7456_RESET 0x02 #define VERTICAL_SYNC_NEXT_VSYNC 0x04 #define OSD_ENABLE 0x08 + #define SYNC_MODE_AUTO 0x00 #define SYNC_MODE_INTERNAL 0x30 #define SYNC_MODE_EXTERNAL 0x20 + #define VIDEO_MODE_PAL 0x40 #define VIDEO_MODE_NTSC 0x00 +#define VIDEO_MODE_MASK 0x40 +#define VIDEO_MODE_IS_PAL(val) (((val) & VIDEO_MODE_MASK) == VIDEO_MODE_PAL) +#define VIDEO_MODE_IS_NTSC(val) (((val) & VIDEO_MODE_MASK) == VIDEO_MODE_NTSC) + +#define VIDEO_SIGNAL_DEBOUNCE_MS 100 // Time to wait for input to stabilize // video mode register 1 bits - // duty cycle is on_off #define BLINK_DUTY_CYCLE_50_50 0x00 #define BLINK_DUTY_CYCLE_33_66 0x01 @@ -85,9 +90,33 @@ #define BACKGROUND_MODE_GRAY 0x40 -//MAX7456 commands +// STAT register bits + +#define STAT_PAL 0x01 +#define STAT_NTSC 0x02 +#define STAT_LOS 0x04 +#define STAT_NVR_BUSY 0x20 + +#define STAT_IS_PAL(val) ((val) & STAT_PAL) +#define STAT_IS_NTSC(val) ((val) & STAT_NTSC) +#define STAT_IS_LOS(val) ((val) & STAT_LOS) + +#define VIN_IS_PAL(val) (!STAT_IS_LOS(val) && STAT_IS_PAL(val)) +#define VIN_IS_NTSC(val) (!STAT_IS_LOS(val) && STAT_IS_NTSC(val)) + +// Kluege warning! +// There are occasions that NTSC is not detected even with !LOS (AB7456 specific?) +// When this happens, lower 3 bits of STAT register is read as zero. +// To cope with this case, this macro defines !LOS && !PAL as NTSC. +// Should be compatible with MAX7456 and non-problematic case. + +#define VIN_IS_NTSC_alt(val) (!STAT_IS_LOS(val) && !STAT_IS_PAL(val)) + +// DMM special bits #define CLEAR_DISPLAY 0x04 #define CLEAR_DISPLAY_VERT 0x06 + +// Special address for terminating incremental write #define END_STRING 0xff @@ -125,7 +154,6 @@ #define NVM_RAM_SIZE 54 #define WRITE_NVR 0xA0 -#define STATUS_REG_NVR_BUSY 0x20 /** Line multiples, for convenience & one less op at runtime **/ #define LINE 30 @@ -147,12 +175,6 @@ #define LINE16 450 - - - - - - //on shared SPI buss we want to change clock for OSD chip and restore for other devices #ifdef MAX7456_SPI_CLK #define ENABLE_MAX7456 {spiSetDivisor(MAX7456_SPI_INSTANCE, MAX7456_SPI_CLK);IOLo(max7456CsPin);} @@ -315,8 +337,12 @@ uint8_t max7456GetRowsCount(void) return (videoSignalReg & VIDEO_MODE_PAL) ? VIDEO_LINES_PAL : VIDEO_LINES_NTSC; } +#if 0 +// XXX Remove this comment, too. //because MAX7456 need some time to detect video system etc. we need to wait for a while to initialize it at startup //and in case of restart we need to reinitialize chip +#endif + void max7456ReInit(void) { uint8_t maxScreenRows; @@ -324,9 +350,13 @@ void max7456ReInit(void) uint16_t x; static bool firstInit = true; +#if 0 +// XXX We don't have to wait for the cam any more. +// XXX Remove this when everything is stable. //do not init MAX before camera power up correctly if (millis() < 1500) return; +#endif ENABLE_MAX7456; @@ -334,15 +364,22 @@ void max7456ReInit(void) case VIDEO_SYSTEM_PAL: videoSignalReg = VIDEO_MODE_PAL | OSD_ENABLE; break; + case VIDEO_SYSTEM_NTSC: videoSignalReg = VIDEO_MODE_NTSC | OSD_ENABLE; break; + case VIDEO_SYSTEM_AUTO: srdata = max7456Send(MAX7456ADD_STAT, 0x00); - if ((0x02 & srdata) == 0x02) + + if (VIN_IS_NTSC(srdata)) { videoSignalReg = VIDEO_MODE_NTSC | OSD_ENABLE; - else + } else if (VIN_IS_PAL(srdata)) { videoSignalReg = VIDEO_MODE_PAL | OSD_ENABLE; + } else { + // No valid input signal, fallback to default (XXX NTSC for now) + videoSignalReg = VIDEO_MODE_NTSC | OSD_ENABLE; + } break; } @@ -436,22 +473,60 @@ bool max7456DmaInProgres(void) } #endif +#include "build/debug.h" + void max7456DrawScreen(void) { - uint8_t check; + uint8_t stallCheck; + uint8_t videoSense; + static uint32_t videoDetectTimeMs = 0; static uint16_t pos = 0; int k = 0, buff_len=0; if (!max7456Lock && !fontIsLoading) { - //-----------------detect MAX7456 fail, or initialize it at startup when it is ready-------- + + // Detect MAX7456 fail, or initialize it at startup when it is ready + max7456Lock = true; ENABLE_MAX7456; - check = max7456Send(VM0_REG | 0x80, 0x00); + stallCheck = max7456Send(VM0_REG | 0x80, 0x00); DISABLE_MAX7456; - if ( check != videoSignalReg) + if (stallCheck != videoSignalReg) { max7456ReInit(); + } else if (videoSignalCfg == VIDEO_SYSTEM_AUTO) { + + // Adjust output format based on the current input format. + + ENABLE_MAX7456; + videoSense = max7456Send(MAX7456ADD_STAT, 0x00); + DISABLE_MAX7456; + + debug[0] = videoSignalReg & VIDEO_MODE_MASK; + debug[1] = videoSense & 0x7; + debug[3] = max7456GetRowsCount(); + + if (videoSense & STAT_LOS) { + videoDetectTimeMs = 0; + } else { + // There is a case that NTSC is not detected for some reason (AB7456 specific?) + // Here we force NTSC detection if not LOS and not PAL. + if ((VIN_IS_PAL(videoSense) && VIDEO_MODE_IS_NTSC(videoSignalReg)) + || (VIN_IS_NTSC_alt(videoSense) && VIDEO_MODE_IS_PAL(videoSignalReg))) { + if (videoDetectTimeMs) { + if (millis() - videoDetectTimeMs > VIDEO_SIGNAL_DEBOUNCE_MS) { + max7456ReInit(); + debug[2]++; + } + } else { + // Wait for signal to stabilize + videoDetectTimeMs = millis(); + } + } + } + } + //------------ end of (re)init------------------------------------- for (k=0; k< MAX_CHARS2UPDATE; k++) { @@ -545,12 +620,11 @@ void max7456WriteNvm(uint8_t char_address, const uint8_t *font_data) max7456Send(MAX7456ADD_CMM, WRITE_NVR); // wait until bit 5 in the status register returns to 0 (12ms) - while ((max7456Send(MAX7456ADD_STAT, 0x00) & STATUS_REG_NVR_BUSY) != 0x00); + while ((max7456Send(MAX7456ADD_STAT, 0x00) & STAT_NVR_BUSY) != 0x00); DISABLE_MAX7456; max7456Lock = false; } - #endif diff --git a/src/main/drivers/serial.h b/src/main/drivers/serial.h index 5d337a729..bfad7e2aa 100644 --- a/src/main/drivers/serial.h +++ b/src/main/drivers/serial.h @@ -31,7 +31,17 @@ typedef enum portOptions_t { SERIAL_PARITY_NO = 0 << 2, SERIAL_PARITY_EVEN = 1 << 2, SERIAL_UNIDIR = 0 << 3, - SERIAL_BIDIR = 1 << 3 + SERIAL_BIDIR = 1 << 3, + + /* + * Note on SERIAL_BIDIR_PP + * With SERIAL_BIDIR_PP, the very first start bit of back-to-back bytes + * is lost and the first data byte will be lost by a framing error. + * To ensure the first start bit to be sent, prepend a zero byte (0x00) + * to actual data bytes. + */ + SERIAL_BIDIR_OD = 0 << 4, + SERIAL_BIDIR_PP = 1 << 4 } portOptions_t; typedef void (*serialReceiveCallbackPtr)(uint16_t data); // used by serial drivers to return frames to app diff --git a/src/main/drivers/serial_uart_stm32f10x.c b/src/main/drivers/serial_uart_stm32f10x.c index 599087f5d..493146644 100644 --- a/src/main/drivers/serial_uart_stm32f10x.c +++ b/src/main/drivers/serial_uart_stm32f10x.c @@ -123,7 +123,7 @@ uartPort_t *serialUART1(uint32_t baudRate, portMode_t mode, portOptions_t option // UART1_RX PA10 if (options & SERIAL_BIDIR) { IOInit(IOGetByTag(IO_TAG(PA9)), OWNER_SERIAL_TX, 1); - IOConfigGPIO(IOGetByTag(IO_TAG(PA9)), IOCFG_AF_OD); + IOConfigGPIO(IOGetByTag(IO_TAG(PA9)), (options & SERIAL_BIDIR_PP) ? IOCFG_AF_PP : IOCFG_AF_OD); } else { if (mode & MODE_TX) { IOInit(IOGetByTag(IO_TAG(PA9)), OWNER_SERIAL_TX, 1); @@ -194,7 +194,7 @@ uartPort_t *serialUART2(uint32_t baudRate, portMode_t mode, portOptions_t option // UART2_RX PA3 if (options & SERIAL_BIDIR) { IOInit(IOGetByTag(IO_TAG(PA2)), OWNER_SERIAL_TX, 2); - IOConfigGPIO(IOGetByTag(IO_TAG(PA2)), IOCFG_AF_OD); + IOConfigGPIO(IOGetByTag(IO_TAG(PA2)), (options & SERIAL_BIDIR_PP) ? IOCFG_AF_PP : IOCFG_AF_OD); } else { if (mode & MODE_TX) { IOInit(IOGetByTag(IO_TAG(PA2)), OWNER_SERIAL_TX, 2); @@ -256,7 +256,7 @@ uartPort_t *serialUART3(uint32_t baudRate, portMode_t mode, portOptions_t option if (options & SERIAL_BIDIR) { IOInit(IOGetByTag(IO_TAG(UART3_TX_PIN)), OWNER_SERIAL_TX, 3); - IOConfigGPIO(IOGetByTag(IO_TAG(UART3_TX_PIN)), IOCFG_AF_OD); + IOConfigGPIO(IOGetByTag(IO_TAG(UART3_TX_PIN)), (options & SERIAL_BIDIR_PP) ? IOCFG_AF_PP : IOCFG_AF_OD); } else { if (mode & MODE_TX) { IOInit(IOGetByTag(IO_TAG(UART3_TX_PIN)), OWNER_SERIAL_TX, 3); diff --git a/src/main/drivers/serial_uart_stm32f30x.c b/src/main/drivers/serial_uart_stm32f30x.c index 7b9464990..51f256038 100644 --- a/src/main/drivers/serial_uart_stm32f30x.c +++ b/src/main/drivers/serial_uart_stm32f30x.c @@ -117,8 +117,8 @@ void serialUARTInit(IO_t tx, IO_t rx, portMode_t mode, portOptions_t options, ui { if (options & SERIAL_BIDIR) { ioConfig_t ioCfg = IO_CONFIG(GPIO_Mode_AF, GPIO_Speed_50MHz, - (options & SERIAL_INVERTED) ? GPIO_OType_PP : GPIO_OType_OD, - (options & SERIAL_INVERTED) ? GPIO_PuPd_DOWN : GPIO_PuPd_UP + ((options & SERIAL_INVERTED) || (options & SERIAL_BIDIR_PP)) ? GPIO_OType_PP : GPIO_OType_OD, + ((options & SERIAL_INVERTED) || (options & SERIAL_BIDIR_PP)) ? GPIO_PuPd_DOWN : GPIO_PuPd_UP ); IOInit(tx, OWNER_SERIAL_TX, index); diff --git a/src/main/drivers/serial_uart_stm32f4xx.c b/src/main/drivers/serial_uart_stm32f4xx.c index 79d3e18ad..ccfedb397 100644 --- a/src/main/drivers/serial_uart_stm32f4xx.c +++ b/src/main/drivers/serial_uart_stm32f4xx.c @@ -340,7 +340,10 @@ uartPort_t *serialUART(UARTDevice device, uint32_t baudRate, portMode_t mode, po if (options & SERIAL_BIDIR) { IOInit(tx, OWNER_SERIAL_TX, RESOURCE_INDEX(device)); - IOConfigGPIOAF(tx, IOCFG_AF_OD, uart->af); + if (options & SERIAL_BIDIR_PP) + IOConfigGPIOAF(tx, IOCFG_AF_PP, uart->af); + else + IOConfigGPIOAF(tx, IOCFG_AF_OD, uart->af); } else { if (mode & MODE_TX) { diff --git a/src/main/fc/fc_tasks.c b/src/main/fc/fc_tasks.c index 0d63281d2..1678353ca 100644 --- a/src/main/fc/fc_tasks.c +++ b/src/main/fc/fc_tasks.c @@ -51,6 +51,7 @@ #include "io/serial.h" #include "io/serial_cli.h" #include "io/transponder_ir.h" +#include "io/vtx_smartaudio.h" #include "msp/msp_serial.h" @@ -203,6 +204,19 @@ static void taskTelemetry(timeUs_t currentTimeUs) } #endif +#ifdef VTX_CONTROL +// Everything that listens to VTX devices +void taskVtxControl(uint32_t currentTime) +{ + if (ARMING_FLAG(ARMED)) + return; + +#ifdef VTX_SMARTAUDIO + smartAudioProcess(currentTime); +#endif +} +#endif + void fcTasksInit(void) { schedulerInit(); @@ -281,6 +295,11 @@ void fcTasksInit(void) #ifdef STACK_CHECK setTaskEnabled(TASK_STACK_CHECK, true); #endif +#ifdef VTX_CONTROL +#ifdef VTX_SMARTAUDIO + setTaskEnabled(TASK_VTXCTRL, true); +#endif +#endif } cfTask_t cfTasks[TASK_COUNT] = { @@ -467,4 +486,13 @@ cfTask_t cfTasks[TASK_COUNT] = { .staticPriority = TASK_PRIORITY_IDLE, }, #endif + +#ifdef VTX_CONTROL + [TASK_VTXCTRL] = { + .taskName = "VTXCTRL", + .taskFunc = taskVtxControl, + .desiredPeriod = TASK_PERIOD_HZ(5), // 5Hz @200msec + .staticPriority = TASK_PRIORITY_IDLE, + }, +#endif }; diff --git a/src/main/io/serial.h b/src/main/io/serial.h index 02f00d823..c8c9a6667 100644 --- a/src/main/io/serial.h +++ b/src/main/io/serial.h @@ -37,7 +37,8 @@ typedef enum { FUNCTION_BLACKBOX = (1 << 7), // 128 FUNCTION_TELEMETRY_MAVLINK = (1 << 9), // 512 - FUNCTION_ESC_SENSOR = (1 << 10) // 1024 + FUNCTION_ESC_SENSOR = (1 << 10),// 1024 + FUNCTION_VTX_CONTROL = (1 << 11),// 2048 } serialPortFunction_e; typedef enum { diff --git a/src/main/io/serial_cli.c b/src/main/io/serial_cli.c index be7408bfc..f831d8281 100755 --- a/src/main/io/serial_cli.c +++ b/src/main/io/serial_cli.c @@ -739,7 +739,7 @@ const clivalue_t valueTable[] = { #ifdef LED_STRIP { "ledstrip_visual_beeper", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, &ledStripConfig()->ledstrip_visual_beeper, .config.lookup = { TABLE_OFF_ON } }, #endif -#ifdef USE_RTC6705 +#if defined(USE_RTC6705) { "vtx_channel", VAR_UINT8 | MASTER_VALUE, &masterConfig.vtx_channel, .config.minmax = { 0, 39 } }, { "vtx_power", VAR_UINT8 | MASTER_VALUE, &masterConfig.vtx_power, .config.minmax = { 0, 1 } }, #endif diff --git a/src/main/io/vtx_smartaudio.c b/src/main/io/vtx_smartaudio.c new file mode 100644 index 000000000..2fbe94743 --- /dev/null +++ b/src/main/io/vtx_smartaudio.c @@ -0,0 +1,1293 @@ +/* + * This file is part of Cleanflight. + * + * Cleanflight is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cleanflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Cleanflight. If not, see . + */ + +/* Created by jflyper */ + +#include +#include +#include + +#include "platform.h" + +#ifdef VTX_SMARTAUDIO + +#include "cms/cms.h" +#include "cms/cms_types.h" + +#include "string.h" +#include "common/printf.h" +#include "common/utils.h" +#include "drivers/system.h" +#include "drivers/serial.h" +#include "io/serial.h" +#include "io/vtx_smartaudio.h" + +#include "fc/rc_controls.h" +#include "fc/runtime_config.h" + +#include "flight/pid.h" +#include "config/config_master.h" + +#include "build/build_config.h" + +//#define SMARTAUDIO_DPRINTF +//#define SMARTAUDIO_DEBUG_MONITOR + +#ifdef SMARTAUDIO_DPRINTF + +#ifdef OMNIBUSF4 +#define DPRINTF_SERIAL_PORT SERIAL_PORT_USART3 +#else +#define DPRINTF_SERIAL_PORT SERIAL_PORT_USART1 +#endif + +serialPort_t *debugSerialPort = NULL; +#define dprintf(x) if (debugSerialPort) printf x +#else +#define dprintf(x) +#endif + +#include "build/debug.h" + +#ifdef CMS +static void saUpdateStatusString(void); // Forward +#endif + +static serialPort_t *smartAudioSerialPort = NULL; + +// SmartAudio command and response codes +enum { + SA_CMD_NONE = 0x00, + SA_CMD_GET_SETTINGS = 0x01, + SA_CMD_SET_POWER, + SA_CMD_SET_CHAN, + SA_CMD_SET_FREQ, + SA_CMD_SET_MODE, + SA_CMD_GET_SETTINGS_V2 = 0x09 // Response only +} smartAudioCommand_e; + +// This is not a good design; can't distinguish command from response this way. +#define SACMD(cmd) (((cmd) << 1) | 1) + +// opmode flags, GET side +#define SA_MODE_GET_FREQ_BY_FREQ 1 +#define SA_MODE_GET_PITMODE 2 +#define SA_MODE_GET_IN_RANGE_PITMODE 4 +#define SA_MODE_GET_OUT_RANGE_PITMODE 8 +#define SA_MODE_GET_UNLOCK 16 +#define SA_MODE_GET_DEFERRED_FREQ 32 + +#define SA_IS_PITMODE(n) ((n) & SA_MODE_GET_PITMODE) +#define SA_IS_PIRMODE(n) (((n) & SA_MODE_GET_PITMODE) && ((n) & SA_MODE_GET_IN_RANGE_PITMODE)) +#define SA_IS_PORMODE(n) (((n) & SA_MODE_GET_PITMODE) && ((n) & SA_MODE_GET_OUT_RANGE_PITMODE)) + +// opmode flags, SET side +#define SA_MODE_SET_IN_RANGE_PITMODE 1 // Immediate +#define SA_MODE_SET_OUT_RANGE_PITMODE 2 // Immediate +#define SA_MODE_CLR_PITMODE 4 // Immediate +#define SA_MODE_SET_UNLOCK 8 +#define SA_MODE_SET_LOCK 0 // ~UNLOCK +#define SA_MODE_SET_DEFERRED_FREQ 16 + +// SetFrequency flags, for pit mode frequency manipulation +#define SA_FREQ_GETPIT (1 << 14) +#define SA_FREQ_SETPIT (1 << 15) +#define SA_FREQ_MASK (~(SA_FREQ_GETPIT|SA_FREQ_SETPIT)) + +// Statistical counters, for user side trouble shooting. + +typedef struct smartAudioStat_s { + uint16_t pktsent; + uint16_t pktrcvd; + uint16_t badpre; + uint16_t badlen; + uint16_t crc; + uint16_t ooopresp; + uint16_t badcode; +} smartAudioStat_t; + +static smartAudioStat_t saStat = { + .pktsent = 0, + .pktrcvd = 0, + .badpre = 0, + .badlen = 0, + .crc = 0, + .ooopresp = 0, + .badcode = 0, +}; + +// The band/chan to frequency table +// XXX Should really be consolidated among different vtx drivers +static const uint16_t saFreqTable[5][8] = +{ + { 5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725 }, // Boacam A + { 5733, 5752, 5771, 5790, 5809, 5828, 5847, 5866 }, // Boscam B + { 5705, 5685, 5665, 5645, 5885, 5905, 5925, 5945 }, // Boscam E + { 5740, 5760, 5780, 5800, 5820, 5840, 5860, 5880 }, // FatShark + { 5658, 5695, 5732, 5769, 5806, 5843, 5880, 5917 }, // RaceBand +}; +// XXX Should frequencies not usable in locked state be detected? + +typedef struct saPowerTable_s { + int rfpower; + int16_t valueV1; + int16_t valueV2; +} saPowerTable_t; + +static saPowerTable_t saPowerTable[] = { + { 25, 7, 0 }, + { 200, 16, 1 }, + { 500, 25, 2 }, + { 800, 40, 3 }, +}; + +// Last received device ('hard') states + +typedef struct smartAudioDevice_s { + int8_t version; + int8_t chan; + int8_t power; + int8_t mode; + uint16_t freq; + uint16_t orfreq; +} smartAudioDevice_t; + +static smartAudioDevice_t saDevice = { + .version = 0, + .chan = -1, + .power = -1, + .mode = 0, + .freq = 0, + .orfreq = 0, +}; + +static smartAudioDevice_t saDevicePrev = { + .version = 0, +}; + +// XXX Possible compliance problem here. Need LOCK/UNLOCK menu? +static uint8_t saLockMode = SA_MODE_SET_UNLOCK; // saCms variable? + +// XXX Should be configurable by user? +static bool saDeferred = true; // saCms variable? + +// Receive frame reassembly buffer +#define SA_MAX_RCVLEN 11 +static uint8_t sa_rbuf[SA_MAX_RCVLEN+4]; // XXX delete 4 byte guard + +// +// CRC8 computations +// + +#define POLYGEN 0xd5 + +static uint8_t CRC8(const uint8_t *data, const int8_t len) +{ + uint8_t crc = 0; /* start with 0 so first byte can be 'xored' in */ + uint8_t currByte; + + for (int i = 0 ; i < len ; i++) { + currByte = data[i]; + + crc ^= currByte; /* XOR-in the next input byte */ + + for (int i = 0; i < 8; i++) { + if ((crc & 0x80) != 0) { + crc = (uint8_t)((crc << 1) ^ POLYGEN); + } else { + crc <<= 1; + } + } + } + return crc; +} + + +static void saPrintSettings(void) +{ +#ifdef SMARTAUDIO_DPRINTF + dprintf(("Current status: version: %d\r\n", saDevice.version)); + dprintf((" mode(0x%x): fmode=%s", saDevice.mode, (saDevice.mode & 1) ? "freq" : "chan")); + dprintf((" pit=%s ", (saDevice.mode & 2) ? "on " : "off")); + dprintf((" inb=%s", (saDevice.mode & 4) ? "on " : "off")); + dprintf((" outb=%s", (saDevice.mode & 8) ? "on " : "off")); + dprintf((" lock=%s", (saDevice.mode & 16) ? "unlocked" : "locked")); + dprintf((" deferred=%s\r\n", (saDevice.mode & 32) ? "on" : "off")); + dprintf((" chan: %d ", saDevice.chan)); + dprintf(("freq: %d ", saDevice.freq)); + dprintf(("power: %d ", saDevice.power)); + dprintf(("pitfreq: %d ", saDevice.orfreq)); + dprintf(("\r\n")); +#endif +} + +static int saDacToPowerIndex(int dac) +{ + int idx; + + for (idx = 0 ; idx < 4 ; idx++) { + if (saPowerTable[idx].valueV1 <= dac) + return(idx); + } + return(3); +} + +// +// Autobauding +// + +#define SMARTBAUD_MIN 4800 +#define SMARTBAUD_MAX 4950 +uint16_t sa_smartbaud = SMARTBAUD_MIN; +static int sa_adjdir = 1; // -1=going down, 1=going up +static int sa_baudstep = 50; + +#define SMARTAUDIO_CMD_TIMEOUT 120 + +static void saAutobaud(void) +{ + if (saStat.pktsent < 10) + // Not enough samples collected + return; + +#if 0 + dprintf(("autobaud: %d rcvd %d/%d (%d)\r\n", + sa_smartbaud, saStat.pktrcvd, saStat.pktsent, ((saStat.pktrcvd * 100) / saStat.pktsent))); +#endif + + if (((saStat.pktrcvd * 100) / saStat.pktsent) >= 70) { + // This is okay + saStat.pktsent = 0; // Should be more moderate? + saStat.pktrcvd = 0; + return; + } + + dprintf(("autobaud: adjusting\r\n")); + + if ((sa_adjdir == 1) && (sa_smartbaud == SMARTBAUD_MAX)) { + sa_adjdir = -1; + dprintf(("autobaud: now going down\r\n")); + } else if ((sa_adjdir == -1 && sa_smartbaud == SMARTBAUD_MIN)) { + sa_adjdir = 1; + dprintf(("autobaud: now going up\r\n")); + } + + sa_smartbaud += sa_baudstep * sa_adjdir; + + dprintf(("autobaud: %d\r\n", sa_smartbaud)); + + smartAudioSerialPort->vTable->serialSetBaudRate(smartAudioSerialPort, sa_smartbaud); + + saStat.pktsent = 0; + saStat.pktrcvd = 0; +} + +// Transport level variables + +static uint32_t sa_lastTransmission = 0; +static uint8_t sa_outstanding = SA_CMD_NONE; // Outstanding command +static uint8_t sa_osbuf[32]; // Outstanding comamnd frame for retransmission +static int sa_oslen; // And associate length + +#ifdef CMS +void saCmsUpdate(void); +#endif + +static void saProcessResponse(uint8_t *buf, int len) +{ + uint8_t resp = buf[0]; + + if (resp == sa_outstanding) { + sa_outstanding = SA_CMD_NONE; + } else if ((resp == SA_CMD_GET_SETTINGS_V2) && (sa_outstanding == SA_CMD_GET_SETTINGS)) { + sa_outstanding = SA_CMD_NONE; + } else { + saStat.ooopresp++; + dprintf(("processResponse: outstanding %d got %d\r\n", sa_outstanding, resp)); + } + + switch(resp) { + case SA_CMD_GET_SETTINGS_V2: // Version 2 Get Settings + case SA_CMD_GET_SETTINGS: // Version 1 Get Settings + if (len < 7) + break; + + saDevice.version = (buf[0] == SA_CMD_GET_SETTINGS) ? 1 : 2; + saDevice.chan = buf[2]; + saDevice.power = buf[3]; + saDevice.mode = buf[4]; + saDevice.freq = (buf[5] << 8)|buf[6]; + +#ifdef SMARTAUDIO_DEBUG_MONITOR + debug[0] = saDevice.version * 100 + saDevice.mode; + debug[1] = saDevice.chan; + debug[2] = saDevice.freq; + debug[3] = saDevice.power; +#endif + break; + + case SA_CMD_SET_POWER: // Set Power + break; + + case SA_CMD_SET_CHAN: // Set Channel + break; + + case SA_CMD_SET_FREQ: // Set Frequency + if (len < 5) + break; + + uint16_t freq = (buf[2] << 8)|buf[3]; + + if (freq & SA_FREQ_GETPIT) { + saDevice.orfreq = freq & SA_FREQ_MASK; + dprintf(("saProcessResponse: GETPIT freq %d\r\n", saDevice.orfreq)); + } else if (freq & SA_FREQ_SETPIT) { + saDevice.orfreq = freq & SA_FREQ_MASK; + dprintf(("saProcessResponse: SETPIT freq %d\r\n", saDevice.orfreq)); + } else { + saDevice.freq = freq; + dprintf(("saProcessResponse: SETFREQ freq %d\r\n", freq)); + } + break; + + case SA_CMD_SET_MODE: // Set Mode + dprintf(("saProcessResponse: SET_MODE 0x%x\r\n", buf[2])); + break; + + default: + saStat.badcode++; + return; + } + + // Debug + if (memcmp(&saDevice, &saDevicePrev, sizeof(smartAudioDevice_t))) + saPrintSettings(); + saDevicePrev = saDevice; + +#ifdef CMS + // Export current device status for CMS + saCmsUpdate(); + saUpdateStatusString(); +#endif +} + +// +// Datalink +// + +static void saReceiveFramer(uint8_t c) +{ + + static enum saFramerState_e { + S_WAITPRE1, // Waiting for preamble 1 (0xAA) + S_WAITPRE2, // Waiting for preamble 2 (0x55) + S_WAITRESP, // Waiting for response code + S_WAITLEN, // Waiting for length + S_DATA, // Receiving data + S_WAITCRC, // Waiting for CRC + } state = S_WAITPRE1; + + static int len; + static int dlen; + + switch(state) { + case S_WAITPRE1: + if (c == 0xAA) { + state = S_WAITPRE2; + } else { + state = S_WAITPRE1; // Don't need this (no change) + } + break; + + case S_WAITPRE2: + if (c == 0x55) { + state = S_WAITRESP; + } else { + saStat.badpre++; + state = S_WAITPRE1; + } + break; + + case S_WAITRESP: + sa_rbuf[0] = c; + state = S_WAITLEN; + break; + + case S_WAITLEN: + sa_rbuf[1] = c; + len = c; + + if (len > SA_MAX_RCVLEN - 2) { + saStat.badlen++; + state = S_WAITPRE1; + } else if (len == 0) { + state = S_WAITCRC; + } else { + dlen = 0; + state = S_DATA; + } + break; + + case S_DATA: + // XXX Should check buffer overflow (-> saerr_overflow) + sa_rbuf[2 + dlen] = c; + if (++dlen == len) { + state = S_WAITCRC; + } + break; + + case S_WAITCRC: + if (CRC8(sa_rbuf, 2 + len) == c) { + // Got a response + saProcessResponse(sa_rbuf, len + 2); + saStat.pktrcvd++; + } else if (sa_rbuf[0] & 1) { + // Command echo + // XXX There is an exceptional case (V2 response) + // XXX Should check crc in the command format? + } else { + saStat.crc++; + } + state = S_WAITPRE1; + break; + } +} + +static void saSendFrame(uint8_t *buf, int len) +{ + int i; + + serialWrite(smartAudioSerialPort, 0x00); // Generate 1st start bit + + for (i = 0 ; i < len ; i++) + serialWrite(smartAudioSerialPort, buf[i]); + + serialWrite(smartAudioSerialPort, 0x00); // XXX Probably don't need this + + sa_lastTransmission = millis(); + saStat.pktsent++; +} + +/* + * Retransmission and command queuing + * + * The transport level support includes retransmission on response timeout + * and command queueing. + * + * Resend buffer: + * The smartaudio returns response for valid command frames in no less + * than 60msec, which we can't wait. So there's a need for a resend buffer. + * + * Command queueing: + * The driver autonomously sends GetSettings command for auto-bauding, + * asynchronous to user initiated commands; commands issued while another + * command is outstanding must be queued for later processing. + * The queueing also handles the case in which multiple commands are + * required to implement a user level command. + */ + +// Retransmission + +static void saResendCmd(void) +{ + saSendFrame(sa_osbuf, sa_oslen); +} + +static void saSendCmd(uint8_t *buf, int len) +{ + int i; + + for (i = 0 ; i < len ; i++) + sa_osbuf[i] = buf[i]; + + sa_oslen = len; + sa_outstanding = (buf[2] >> 1); + + saSendFrame(sa_osbuf, sa_oslen); +} + +// Command queue management + +typedef struct saCmdQueue_s { + uint8_t *buf; + int len; +} saCmdQueue_t; + +#define SA_QSIZE 4 // 1 heartbeat (GetSettings) + 2 commands + 1 slack +static saCmdQueue_t sa_queue[SA_QSIZE]; +static uint8_t sa_qhead = 0; +static uint8_t sa_qtail = 0; + +#ifdef DPRINTF_SMARTAUDIO +static int saQueueLength() +{ + if (sa_qhead >= sa_qtail) { + return sa_qhead - sa_qtail; + } else { + return SA_QSIZE + sa_qhead - sa_qtail; + } +} +#endif + +static bool saQueueEmpty() +{ + return sa_qhead == sa_qtail; +} + +static bool saQueueFull() +{ + return ((sa_qhead + 1) % SA_QSIZE) == sa_qtail; +} + +static void saQueueCmd(uint8_t *buf, int len) +{ + if (saQueueFull()) + return; + + sa_queue[sa_qhead].buf = buf; + sa_queue[sa_qhead].len = len; + sa_qhead = (sa_qhead + 1) % SA_QSIZE; +} + +static void saSendQueue(void) +{ + if (saQueueEmpty()) + return; + + saSendCmd(sa_queue[sa_qtail].buf, sa_queue[sa_qtail].len); + sa_qtail = (sa_qtail + 1) % SA_QSIZE; +} + +// Individual commands + +static void saGetSettings(void) +{ + static uint8_t bufGetSettings[5] = {0xAA, 0x55, SACMD(SA_CMD_GET_SETTINGS), 0x00, 0x9F}; + + saQueueCmd(bufGetSettings, 5); +} + +static void saSetFreq(uint16_t freq) +{ + static uint8_t buf[7] = { 0xAA, 0x55, SACMD(SA_CMD_SET_FREQ), 2 }; + + if (freq & SA_FREQ_GETPIT) { + dprintf(("smartAudioSetFreq: GETPIT\r\n")); + } else if (freq & SA_FREQ_SETPIT) { + dprintf(("smartAudioSetFreq: SETPIT %d\r\n", freq & SA_FREQ_MASK)); + } else { + dprintf(("smartAudioSetFreq: SET %d\r\n", freq)); + } + + buf[4] = (freq >> 8) & 0xff; + buf[5] = freq & 0xff; + buf[6] = CRC8(buf, 6); + + saQueueCmd(buf, 7); +} + +#if 0 +static void saSetPitFreq(uint16_t freq) +{ + saSetFreq(freq | SA_FREQ_SETPIT); +} + +static void saGetPitFreq(void) +{ + saSetFreq(SA_FREQ_GETPIT); +} +#endif + +void saSetBandChan(uint8_t band, uint8_t chan) +{ + static uint8_t buf[6] = { 0xAA, 0x55, SACMD(SA_CMD_SET_CHAN), 1 }; + + buf[4] = band * 8 + chan; + buf[5] = CRC8(buf, 5); + + saQueueCmd(buf, 6); +} + +static void saSetMode(int mode) +{ + static uint8_t buf[6] = { 0xAA, 0x55, SACMD(SA_CMD_SET_MODE), 1 }; + + buf[4] = (mode & 0x3f)|saLockMode; + buf[5] = CRC8(buf, 5); + + saQueueCmd(buf, 6); +} + +void saSetPowerByIndex(uint8_t index) +{ + static uint8_t buf[6] = { 0xAA, 0x55, SACMD(SA_CMD_SET_POWER), 1 }; + + dprintf(("saSetPowerByIndex: index %d\r\n", index)); + + if (saDevice.version == 0) { + // Unknown or yet unknown version. + return; + } + + if (index > 3) + return; + + buf[4] = (saDevice.version == 1) ? saPowerTable[index].valueV1 : saPowerTable[index].valueV2; + buf[5] = CRC8(buf, 5); + saQueueCmd(buf, 6); +} + +bool smartAudioInit() +{ +#ifdef SMARTAUDIO_DPRINTF + // Setup debugSerialPort + + debugSerialPort = openSerialPort(DPRINTF_SERIAL_PORT, FUNCTION_NONE, NULL, 115200, MODE_RXTX, 0); + if (debugSerialPort) { + setPrintfSerialPort(debugSerialPort); + dprintf(("smartAudioInit: OK\r\n")); + } +#endif + + serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_VTX_CONTROL); + if (portConfig) { + smartAudioSerialPort = openSerialPort(portConfig->identifier, FUNCTION_VTX_CONTROL, NULL, 4800, MODE_RXTX, SERIAL_BIDIR|SERIAL_BIDIR_PP); + } + + if (!smartAudioSerialPort) { + return false; + } + + return true; +} + +void smartAudioProcess(uint32_t now) +{ + static bool initialSent = false; + + if (smartAudioSerialPort == NULL) + return; + + while (serialRxBytesWaiting(smartAudioSerialPort) > 0) { + uint8_t c = serialRead(smartAudioSerialPort); + saReceiveFramer((uint16_t)c); + } + + // Re-evaluate baudrate after each frame reception + saAutobaud(); + + if (!initialSent) { + saGetSettings(); + saSetFreq(SA_FREQ_GETPIT); + saSendQueue(); + initialSent = true; + return; + } + + if ((sa_outstanding != SA_CMD_NONE) + && (now - sa_lastTransmission > SMARTAUDIO_CMD_TIMEOUT)) { + // Last command timed out + // dprintf(("process: resending 0x%x\r\n", sa_outstanding)); + // XXX Todo: Resend termination and possible offline transition + saResendCmd(); + } else if (!saQueueEmpty()) { + // Command pending. Send it. + // dprintf(("process: sending queue\r\n")); + saSendQueue(); + } else if (now - sa_lastTransmission >= 1000) { + // Heart beat for autobauding + //dprintf(("process: sending heartbeat\r\n")); + saGetSettings(); + saSendQueue(); + } +} + +#ifdef CMS + +// Interface to CMS + +// Operational Model and RF modes (CMS) + +#define SACMS_OPMODEL_UNDEF 0 // Not known yet +#define SACMS_OPMODEL_FREE 1 // Freestyle model: Power up transmitting +#define SACMS_OPMODEL_RACE 2 // Race model: Power up in pit mode + +uint8_t saCmsOpmodel = SACMS_OPMODEL_UNDEF; + +#define SACMS_TXMODE_NODEF 0 +#define SACMS_TXMODE_PIT_OUTRANGE 1 +#define SACMS_TXMODE_PIT_INRANGE 2 +#define SACMS_TXMODE_ACTIVE 3 + +uint8_t saCmsRFState; // RF state; ACTIVE, PIR, POR XXX Not currently used + +uint8_t saCmsBand = 0; +uint8_t saCmsChan = 0; +uint8_t saCmsPower = 0; + +// Frequency derived from channel table (used for reference in band/chan mode) +uint16_t saCmsFreqRef = 0; + +uint16_t saCmsDeviceFreq = 0; + +uint8_t saCmsDeviceStatus = 0; +uint8_t saCmsPower; +uint8_t saCmsPitFMode; // In-Range or Out-Range +uint8_t saCmsFselMode; // Channel(0) or User defined(1) + +uint16_t saCmsORFreq = 0; // POR frequency +uint16_t saCmsORFreqNew; // POR frequency + +uint16_t saCmsUserFreq = 0; // User defined frequency +uint16_t saCmsUserFreqNew; // User defined frequency + +void saCmsUpdate(void) +{ +// XXX Take care of pit mode update somewhere??? + + if (saCmsOpmodel == SACMS_OPMODEL_UNDEF) { + // This is a first valid response to GET_SETTINGS. + saCmsOpmodel = (saDevice.mode & SA_MODE_GET_PITMODE) ? SACMS_OPMODEL_RACE : SACMS_OPMODEL_FREE; + + saCmsFselMode = (saDevice.mode & SA_MODE_GET_FREQ_BY_FREQ) ? 1 : 0; + + saCmsBand = (saDevice.chan / 8) + 1; + saCmsChan = (saDevice.chan % 8) + 1; + saCmsFreqRef = saFreqTable[saDevice.chan / 8][saDevice.chan % 8]; + + saCmsDeviceFreq = saFreqTable[saDevice.chan / 8][saDevice.chan % 8]; + + if ((saDevice.mode & SA_MODE_GET_PITMODE) == 0) { + saCmsRFState = SACMS_TXMODE_ACTIVE; + } else if (saDevice.mode & SA_MODE_GET_IN_RANGE_PITMODE) { + saCmsRFState = SACMS_TXMODE_PIT_INRANGE; + } else { + saCmsRFState = SACMS_TXMODE_PIT_OUTRANGE; + } + + if (saDevice.version == 2) { + saCmsPower = saDevice.power + 1; // XXX Take care V1 + } else { + saCmsPower = saDacToPowerIndex(saDevice.power) + 1; + } + } + + saUpdateStatusString(); +} + +char saCmsStatusString[31] = "- -- ---- ---"; +// m bc ffff ppp +// 0123456789012 + +static long saCmsConfigOpmodelByGvar(displayPort_t *, const void *self); +static long saCmsConfigPitFModeByGvar(displayPort_t *, const void *self); +static long saCmsConfigBandByGvar(displayPort_t *, const void *self); +static long saCmsConfigChanByGvar(displayPort_t *, const void *self); +static long saCmsConfigPowerByGvar(displayPort_t *, const void *self); + +static void saUpdateStatusString(void) +{ + if (saDevice.version == 0) + return; + +// XXX These should be done somewhere else +if (saCmsDeviceStatus == 0 && saDevice.version != 0) + saCmsDeviceStatus = saDevice.version; +if (saCmsORFreq == 0 && saDevice.orfreq != 0) + saCmsORFreq = saDevice.orfreq; +if (saCmsUserFreq == 0 && saDevice.freq != 0) + saCmsUserFreq = saDevice.freq; + +if (saDevice.mode & SA_MODE_GET_OUT_RANGE_PITMODE) + saCmsPitFMode = 1; +else + saCmsPitFMode = 0; + + saCmsStatusString[0] = "-FP"[(saDevice.mode & SA_MODE_GET_PITMODE) ? SACMS_OPMODEL_RACE : SACMS_OPMODEL_FREE]; + saCmsStatusString[2] = "ABEFR"[saDevice.chan / 8]; + saCmsStatusString[3] = '1' + (saDevice.chan % 8); + + if ((saDevice.mode & SA_MODE_GET_PITMODE) + && (saDevice.mode & SA_MODE_GET_OUT_RANGE_PITMODE)) + tfp_sprintf(&saCmsStatusString[5], "%4d", saDevice.orfreq); + else if (saDevice.mode & SA_MODE_GET_FREQ_BY_FREQ) + tfp_sprintf(&saCmsStatusString[5], "%4d", saDevice.freq); + else + tfp_sprintf(&saCmsStatusString[5], "%4d", + saFreqTable[saDevice.chan / 8][saDevice.chan % 8]); + + saCmsStatusString[9] = ' '; + + if (saDevice.mode & SA_MODE_GET_PITMODE) { + saCmsStatusString[10] = 'P'; + if (saDevice.mode & SA_MODE_GET_IN_RANGE_PITMODE) { + saCmsStatusString[11] = 'I'; + } else { + saCmsStatusString[11] = 'O'; + } + saCmsStatusString[12] = 'R'; + saCmsStatusString[13] = 0; + } else { + tfp_sprintf(&saCmsStatusString[10], "%3d", (saDevice.version == 2) ? saPowerTable[saDevice.power].rfpower : saPowerTable[saDacToPowerIndex(saDevice.power)].rfpower); + } +} + +static long saCmsConfigBandByGvar(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + if (saDevice.version == 0) { + // Bounce back; not online yet + saCmsBand = 0; + return 0; + } + +dprintf(("saCmsConfigBand: band req %d ", saCmsBand)); + + if (saCmsBand == 0) { + // Bouce back, no going back to undef state + saCmsBand = 1; + return 0; + } + + if (!(saCmsOpmodel == SACMS_OPMODEL_FREE && saDeferred)) + saSetBandChan(saCmsBand - 1, saCmsChan - 1); + + saCmsFreqRef = saFreqTable[saCmsBand - 1][saCmsChan - 1]; + + return 0; +} + +static long saCmsConfigChanByGvar(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + if (saDevice.version == 0) { + // Bounce back; not online yet + saCmsChan = 0; + return 0; + } + + if (saCmsChan == 0) { + // Bounce back; no going back to undef state + saCmsChan = 1; + return 0; + } + + if (!(saCmsOpmodel == SACMS_OPMODEL_FREE && saDeferred)) + saSetBandChan(saCmsBand - 1, saCmsChan - 1); + + saCmsFreqRef = saFreqTable[saCmsBand - 1][saCmsChan - 1]; + + return 0; +} + +static long saCmsConfigPowerByGvar(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + if (saDevice.version == 0) { + // Bounce back; not online yet + saCmsPower = 0; + return 0; + } + + if (saCmsPower == 0) { + // Bouce back; no going back to undef state + saCmsPower = 1; + return 0; + } + + saSetPowerByIndex(saCmsPower - 1); + + return 0; +} + +static long saCmsConfigPitFModeByGvar(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + dprintf(("saCmsConfigPitFmodeByGbar: saCmsPitFMode %d\r\n", saCmsPitFMode)); + + if (saCmsPitFMode == 0) { + saSetMode(SA_MODE_SET_IN_RANGE_PITMODE); + } else { + saSetMode(SA_MODE_SET_OUT_RANGE_PITMODE); + } + + return 0; +} + +static long saCmsConfigOpmodelByGvar(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + uint8_t opmodel = saCmsOpmodel; + + dprintf(("saCmsConfigOpmodelByGvar: opmodel %d\r\n", opmodel)); + + if (opmodel == SACMS_OPMODEL_FREE) { + // VTX should power up transmitting. + // Turn off In-Range and Out-Range bits + saSetMode(0); + } else if (opmodel == SACMS_OPMODEL_RACE) { + // VTX should power up in pit mode. + // Default PitFMode is in-range to prevent users without + // out-range receivers from getting blinded. + saCmsPitFMode = 0; + saCmsConfigPitFModeByGvar(pDisp, self); + } else { + // Trying to go back to unknown state; bounce back + saCmsOpmodel = SACMS_OPMODEL_UNDEF + 1; + } + + return 0; +} + +static const char * const saCmsDeviceStatusNames[] = { + "OFFL", + "ONL V1", + "ONL V2", +}; + +static OSD_TAB_t saCmsEntOnline = { &saCmsDeviceStatus, 2, saCmsDeviceStatusNames }; + +static OSD_Entry saCmsMenuStatsEntries[] = { + { "- SA STATS -", OME_Label, NULL, NULL, 0 }, + { "STATUS", OME_TAB, NULL, &saCmsEntOnline, DYNAMIC }, + { "BAUDRATE", OME_UINT16, NULL, &(OSD_UINT16_t){ &sa_smartbaud, 0, 0, 0 }, DYNAMIC }, + { "SENT", OME_UINT16, NULL, &(OSD_UINT16_t){ &saStat.pktsent, 0, 0, 0 }, DYNAMIC }, + { "RCVD", OME_UINT16, NULL, &(OSD_UINT16_t){ &saStat.pktrcvd, 0, 0, 0 }, DYNAMIC }, + { "BADPRE", OME_UINT16, NULL, &(OSD_UINT16_t){ &saStat.badpre, 0, 0, 0 }, DYNAMIC }, + { "BADLEN", OME_UINT16, NULL, &(OSD_UINT16_t){ &saStat.badlen, 0, 0, 0 }, DYNAMIC }, + { "CRCERR", OME_UINT16, NULL, &(OSD_UINT16_t){ &saStat.crc, 0, 0, 0 }, DYNAMIC }, + { "OOOERR", OME_UINT16, NULL, &(OSD_UINT16_t){ &saStat.ooopresp, 0, 0, 0 }, DYNAMIC }, + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +static CMS_Menu saCmsMenuStats = { + .GUARD_text = "XSAST", + .GUARD_type = OME_MENU, + .onEnter = NULL, + .onExit = NULL, + .onGlobalExit = NULL, + .entries = saCmsMenuStatsEntries +}; + +static const char * const saCmsBandNames[] = { + "--------", + "BOSCAM A", + "BOSCAM B", + "BOSCAM E", + "FATSHARK", + "RACEBAND", +}; + +static OSD_TAB_t saCmsEntBand = { &saCmsBand, 5, &saCmsBandNames[0], NULL }; + +static const char * const saCmsChanNames[] = { + "-", "1", "2", "3", "4", "5", "6", "7", "8", +}; + +static OSD_TAB_t saCmsEntChan = { &saCmsChan, 8, &saCmsChanNames[0], NULL }; + +static const char * const saCmsPowerNames[] = { + "---", + "25 ", + "200", + "500", + "800", +}; + +static OSD_TAB_t saCmsEntPower = { &saCmsPower, 4, saCmsPowerNames}; + +static OSD_UINT16_t saCmsEntFreqRef = { &saCmsFreqRef, 5600, 5900, 0 }; + +static const char * const saCmsOpmodelNames[] = { + "----", + "FREE", + "RACE", +}; + +static const char * const saCmsFselModeNames[] = { + "CHAN", + "USER" +}; + +static const char * const saCmsPitFModeNames[] = { + "PIR", + "POR" +}; + +static OSD_TAB_t saCmsEntPitFMode = { &saCmsPitFMode, 1, saCmsPitFModeNames }; + +static long sacms_SetupTopMenu(void); // Forward + +static long saCmsConfigFreqModeByGvar(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + if (saCmsFselMode == 0) { + // CHAN + saSetBandChan(saCmsBand - 1, saCmsChan - 1); + } else { + // USER: User frequency mode is only available in FREE opmodel. + if (saCmsOpmodel == SACMS_OPMODEL_FREE) { + saSetFreq(saCmsUserFreq); + } else { + // Bounce back + saCmsFselMode = 0; + } + } + + sacms_SetupTopMenu(); + + return 0; +} + +static long saCmsCommence(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + if (saCmsOpmodel == SACMS_OPMODEL_RACE) { + if (saCmsPitFMode == 0) + saSetMode(SA_MODE_CLR_PITMODE|SA_MODE_SET_IN_RANGE_PITMODE); + else + saSetMode(SA_MODE_CLR_PITMODE|SA_MODE_SET_OUT_RANGE_PITMODE); + } else { + if (saCmsFselMode == 0) + saSetBandChan(saCmsBand - 1, saCmsChan - 1); + else + saSetFreq(saCmsUserFreq); + } + + return MENU_CHAIN_BACK; +} + +static long saCmsSetPORFreqOnEnter(void) +{ + saCmsORFreqNew = saCmsORFreq; + + return 0; +} + +static long saCmsSetPORFreq(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + saSetFreq(saCmsORFreqNew|SA_FREQ_SETPIT); + + return 0; +} + +static char *saCmsORFreqGetString(void) +{ + static char pbuf[5]; + + tfp_sprintf(pbuf, "%4d", saCmsORFreq); + + return pbuf; +} + +static char *saCmsUserFreqGetString(void) +{ + static char pbuf[5]; + + tfp_sprintf(pbuf, "%4d", saCmsUserFreq); + + return pbuf; +} + +static long saCmsSetUserFreqOnEnter(void) +{ + saCmsUserFreqNew = saCmsUserFreq; + + return 0; +} + +static long saCmsSetUserFreq(displayPort_t *pDisp, const void *self) +{ + UNUSED(pDisp); + UNUSED(self); + + saCmsUserFreq = saCmsUserFreqNew; + saSetFreq(saCmsUserFreq); + + return 0; +} + +static OSD_Entry saCmsMenuPORFreqEntries[] = { + { "- POR FREQ -", OME_Label, NULL, NULL, 0 }, + + { "CUR FREQ", OME_UINT16, NULL, &(OSD_UINT16_t){ &saCmsORFreq, 5000, 5900, 0 }, DYNAMIC }, + { "NEW FREQ", OME_UINT16, NULL, &(OSD_UINT16_t){ &saCmsORFreqNew, 5000, 5900, 1 }, 0 }, + { "SET", OME_Funcall, saCmsSetPORFreq, NULL, 0 }, + + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +static CMS_Menu saCmsMenuPORFreq = +{ + .GUARD_text = "XSAPOR", + .GUARD_type = OME_MENU, + .onEnter = saCmsSetPORFreqOnEnter, + .onExit = NULL, + .onGlobalExit = NULL, + .entries = saCmsMenuPORFreqEntries, +}; + +static OSD_Entry saCmsMenuUserFreqEntries[] = { + { "- USER FREQ -", OME_Label, NULL, NULL, 0 }, + + { "CUR FREQ", OME_UINT16, NULL, &(OSD_UINT16_t){ &saCmsUserFreq, 5000, 5900, 0 }, DYNAMIC }, + { "NEW FREQ", OME_UINT16, NULL, &(OSD_UINT16_t){ &saCmsUserFreqNew, 5000, 5900, 1 }, 0 }, + { "SET", OME_Funcall, saCmsSetUserFreq, NULL, 0 }, + + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +static CMS_Menu saCmsMenuUserFreq = +{ + .GUARD_text = "XSAUFQ", + .GUARD_type = OME_MENU, + .onEnter = saCmsSetUserFreqOnEnter, + .onExit = NULL, + .onGlobalExit = NULL, + .entries = saCmsMenuUserFreqEntries, +}; + +static OSD_TAB_t saCmsEntFselMode = { &saCmsFselMode, 1, saCmsFselModeNames }; + +static OSD_Entry saCmsMenuConfigEntries[] = { + { "- SA CONFIG -", OME_Label, NULL, NULL, 0 }, + + { "OP MODEL", OME_TAB, saCmsConfigOpmodelByGvar, &(OSD_TAB_t){ &saCmsOpmodel, 2, saCmsOpmodelNames }, 0 }, + { "FSEL MODE", OME_TAB, saCmsConfigFreqModeByGvar, &saCmsEntFselMode, 0 }, + { "PIT FMODE", OME_TAB, saCmsConfigPitFModeByGvar, &saCmsEntPitFMode, 0 }, + { "POR FREQ", OME_Submenu, (CMSEntryFuncPtr)saCmsORFreqGetString, &saCmsMenuPORFreq, OPTSTRING }, + { "STATX", OME_Submenu, cmsMenuChange, &saCmsMenuStats, 0 }, + + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +static CMS_Menu saCmsMenuConfig = { + .GUARD_text = "XSACFG", + .GUARD_type = OME_MENU, + .onEnter = NULL, + .onExit = NULL, + .onGlobalExit = NULL, + .entries = saCmsMenuConfigEntries +}; + +static OSD_Entry saCmsMenuCommenceEntries[] = { + { "CONFIRM", OME_Label, NULL, NULL, 0 }, + + { "YES", OME_Funcall, saCmsCommence, NULL, 0 }, + + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +static CMS_Menu saCmsMenuCommence = { + .GUARD_text = "XVTXCOM", + .GUARD_type = OME_MENU, + .onEnter = NULL, + .onExit = NULL, + .onGlobalExit = NULL, + .entries = saCmsMenuCommenceEntries, +}; + +static OSD_Entry saCmsMenuFreqModeEntries[] = { + { "- SMARTAUDIO -", OME_Label, NULL, NULL, 0 }, + + { "", OME_Label, NULL, saCmsStatusString, DYNAMIC }, + { "FREQ", OME_Submenu, (CMSEntryFuncPtr)saCmsUserFreqGetString, &saCmsMenuUserFreq, OPTSTRING }, + { "POWER", OME_TAB, saCmsConfigPowerByGvar, &saCmsEntPower, 0 }, + { "SET", OME_Submenu, cmsMenuChange, &saCmsMenuCommence, 0 }, + { "CONFIG", OME_Submenu, cmsMenuChange, &saCmsMenuConfig, 0 }, + + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +static OSD_Entry saCmsMenuChanModeEntries[] = +{ + { "- SMARTAUDIO -", OME_Label, NULL, NULL, 0 }, + + { "", OME_Label, NULL, saCmsStatusString, DYNAMIC }, + { "BAND", OME_TAB, saCmsConfigBandByGvar, &saCmsEntBand, 0 }, + { "CHAN", OME_TAB, saCmsConfigChanByGvar, &saCmsEntChan, 0 }, + { "(FREQ)", OME_UINT16, NULL, &saCmsEntFreqRef, DYNAMIC }, + { "POWER", OME_TAB, saCmsConfigPowerByGvar, &saCmsEntPower, 0 }, + { "SET", OME_Submenu, cmsMenuChange, &saCmsMenuCommence, 0 }, + { "CONFIG", OME_Submenu, cmsMenuChange, &saCmsMenuConfig, 0 }, + + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +static OSD_Entry saCmsMenuOfflineEntries[] = +{ + { "- VTX SMARTAUDIO -", OME_Label, NULL, NULL, 0 }, + + { "", OME_Label, NULL, saCmsStatusString, DYNAMIC }, + { "STATX", OME_Submenu, cmsMenuChange, &saCmsMenuStats, 0 }, + + { "BACK", OME_Back, NULL, NULL, 0 }, + { NULL, OME_END, NULL, NULL, 0 } +}; + +CMS_Menu cmsx_menuVtxSmartAudio; // Forward + +static long sacms_SetupTopMenu(void) +{ + if (saCmsDeviceStatus) { + if (saCmsFselMode == 0) + cmsx_menuVtxSmartAudio.entries = saCmsMenuChanModeEntries; + else + cmsx_menuVtxSmartAudio.entries = saCmsMenuFreqModeEntries; + } else { + cmsx_menuVtxSmartAudio.entries = saCmsMenuOfflineEntries; + } + + return 0; +} + +CMS_Menu cmsx_menuVtxSmartAudio = { + .GUARD_text = "XVTXSA", + .GUARD_type = OME_MENU, + .onEnter = sacms_SetupTopMenu, + .onExit = NULL, + .onGlobalExit = NULL, + .entries = saCmsMenuOfflineEntries, +}; + +#endif // CMS + +#endif // VTX_SMARTAUDIO diff --git a/src/main/io/vtx_smartaudio.h b/src/main/io/vtx_smartaudio.h new file mode 100644 index 000000000..d11ba621a --- /dev/null +++ b/src/main/io/vtx_smartaudio.h @@ -0,0 +1,35 @@ + +// For generic API use, but here for now + +bool smartAudioInit(); +void smartAudioProcess(uint32_t); + +#if 0 +#ifdef CMS + +uint16_t smartAudioSmartbaud; + +uint16_t saerr_badpre; +uint16_t saerr_badlen; +uint16_t saerr_crcerr; +uint16_t saerr_oooresp; + +uint8_t smartAudioOpModel; +uint8_t smartAudioStatus; +uint8_t smartAudioBand; +uint8_t smartAudioChan; +uint16_t smartAudioFreq; +uint8_t smartAudioPower; +uint8_t smartAudioTxMode; +uint8_t smartAudioPitFMode; +uint16_t smartAudioORFreq; + +long smartAudioConfigureOpModelByGvar(displayPort_t *, const void *self); +long smartAudioConfigurePitFModeByGvar(displayPort_t *, const void *self); +long smartAudioConfigureBandByGvar(displayPort_t *, const void *self); +long smartAudioConfigureChanByGvar(displayPort_t *, const void *self); +long smartAudioConfigurePowerByGvar(displayPort_t *, const void *self); +long smartAudioSetTxModeByGvar(displayPort_t *, const void *self); + +#endif +#endif diff --git a/src/main/io/vtx_smartaudio_cms.h b/src/main/io/vtx_smartaudio_cms.h new file mode 100644 index 000000000..93a72ba1e --- /dev/null +++ b/src/main/io/vtx_smartaudio_cms.h @@ -0,0 +1 @@ +extern CMS_Menu cmsx_menuVtxSmartAudio; diff --git a/src/main/main.c b/src/main/main.c index 78bed43c6..ada641775 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -30,6 +30,7 @@ #include "common/printf.h" #include "cms/cms.h" +#include "cms/cms_types.h" #include "drivers/nvic.h" #include "drivers/sensor.h" @@ -90,6 +91,7 @@ #include "io/osd.h" #include "io/displayport_msp.h" #include "io/vtx.h" +#include "io/vtx_smartaudio.h" #include "scheduler/scheduler.h" @@ -517,6 +519,10 @@ void init(void) baroSetCalibrationCycles(CALIBRATING_BARO_CYCLES); #endif +#ifdef VTX_SMARTAUDIO + smartAudioInit(); +#endif + // start all timers // TODO - not implemented yet timerStart(); diff --git a/src/main/platform.h b/src/main/platform.h index 0f6439cbc..9e2c68e0d 100644 --- a/src/main/platform.h +++ b/src/main/platform.h @@ -73,4 +73,4 @@ #include "target/common.h" #include "target.h" - +#include "target/common_post.h" diff --git a/src/main/scheduler/scheduler.h b/src/main/scheduler/scheduler.h index 133943a3f..06014f01b 100644 --- a/src/main/scheduler/scheduler.h +++ b/src/main/scheduler/scheduler.h @@ -100,6 +100,9 @@ typedef enum { #ifdef CMS TASK_CMS, #endif +#ifdef VTX_CONTROL + TASK_VTXCTRL, +#endif /* Count of real tasks */ TASK_COUNT, diff --git a/src/main/target/OMNIBUS/target.h b/src/main/target/OMNIBUS/target.h index 1071cdd00..e0407debb 100644 --- a/src/main/target/OMNIBUS/target.h +++ b/src/main/target/OMNIBUS/target.h @@ -162,7 +162,7 @@ #define BUTTON_A_PIN PB1 #define BUTTON_B_PIN PB0 -#define AVOID_UART3_FOR_PWM_PPM +//#define AVOID_UART3_FOR_PWM_PPM // Disable this for using UART3 #define SPEKTRUM_BIND // USART3, diff --git a/src/main/target/OMNIBUSF4/target.mk b/src/main/target/OMNIBUSF4/target.mk index f39c26840..db1c65b89 100644 --- a/src/main/target/OMNIBUSF4/target.mk +++ b/src/main/target/OMNIBUSF4/target.mk @@ -8,4 +8,3 @@ TARGET_SRC = \ drivers/barometer_spi_bmp280.c \ drivers/compass_hmc5883l.c \ drivers/max7456.c - diff --git a/src/main/target/common.h b/src/main/target/common.h index ed8d70942..66d17911a 100644 --- a/src/main/target/common.h +++ b/src/main/target/common.h @@ -101,6 +101,8 @@ #define TELEMETRY_MAVLINK #define USE_RX_MSP #define USE_SERIALRX_JETIEXBUS +#define VTX_CONTROL +#define VTX_SMARTAUDIO #else #define SKIP_CLI_COMMAND_HELP #endif diff --git a/src/main/target/common_post.h b/src/main/target/common_post.h new file mode 100644 index 000000000..193124c07 --- /dev/null +++ b/src/main/target/common_post.h @@ -0,0 +1,26 @@ +/* + * This file is part of Cleanflight. + * + * Cleanflight is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cleanflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Cleanflight. If not, see . + */ + +// Touch up configuration + +#pragma once + +// Targets with built-in vtx do not need external vtx +#if defined(VTX) || defined(USE_RTC6705) +# undef VTX_CONTROL +# undef VTX_SMARTAUDIO +#endif