From 3083269f30c44eaac0257e3374df7befe2347fd0 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 27 Aug 2015 14:54:06 +0200 Subject: [PATCH 01/74] Version moved to 2.2.0 --- companion/src/CMakeLists.txt | 4 ++-- radio/src/Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index 3df4ec991..15f5c2b71 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -1,8 +1,8 @@ PROJECT( companion ) SET(C9X_VERSION_MAJOR "2") -SET(C9X_VERSION_MINOR "1") -SET(C9X_VERSION_REVISION "4") +SET(C9X_VERSION_MINOR "2") +SET(C9X_VERSION_REVISION "0") SET(C9X_VERSION_SUFFIX $ENV{OPENTX_VERSION_SUFFIX}) SET(C9X_VERSION ${C9X_VERSION_MAJOR}.${C9X_VERSION_MINOR}.${C9X_VERSION_REVISION}${C9X_VERSION_SUFFIX}) diff --git a/radio/src/Makefile b/radio/src/Makefile index ef81c7a86..35469abcb 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -395,7 +395,7 @@ IMG2LBM = -$(PYTHON) ../util/img2lbm.py BIN2LBM = -$(PYTHON) ../util/bin2lbm.py FONT2PNG = -$(PYTHON) ../util/font2png.py TRANSLATE = -$(PYTHON) ../util/translate.py -VERSION = 2.1.4$(OPENTX_VERSION_SUFFIX) +VERSION = 2.2.0$(OPENTX_VERSION_SUFFIX) THIRDPARTY = thirdparty COOSDIR = $(THIRDPARTY)/CoOS From d0f6c59b8aa8de6143fb436352d6ff9dccc05ef0 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 28 Aug 2015 09:56:44 +0200 Subject: [PATCH 02/74] EEPROM structure changes to allow more switches (9bits instead of 8bits) --- radio/src/gui/Taranis/helpers.cpp | 4 ++ radio/src/gui/Taranis/lcd.cpp | 10 +++- radio/src/gui/Taranis/menu_model_setup.cpp | 1 + radio/src/gui/Taranis/menus.h | 2 +- radio/src/gui/Taranis/widgets.cpp | 2 +- radio/src/myeeprom.h | 67 ++++++++++++---------- radio/src/opentx.h | 2 +- 7 files changed, 54 insertions(+), 34 deletions(-) diff --git a/radio/src/gui/Taranis/helpers.cpp b/radio/src/gui/Taranis/helpers.cpp index d479234f5..e364d394f 100644 --- a/radio/src/gui/Taranis/helpers.cpp +++ b/radio/src/gui/Taranis/helpers.cpp @@ -263,6 +263,10 @@ bool isSwitchAvailable(int swtch, SwitchContext context) } } + if (swtch >= SWSRC_FIRST_SENSOR && swtch <= SWSRC_LAST_SENSOR) { + return isTelemetryFieldAvailable(swtch - SWSRC_FIRST_SENSOR); + } + return true; } diff --git a/radio/src/gui/Taranis/lcd.cpp b/radio/src/gui/Taranis/lcd.cpp index 738726564..93ccce20d 100644 --- a/radio/src/gui/Taranis/lcd.cpp +++ b/radio/src/gui/Taranis/lcd.cpp @@ -712,7 +712,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) else { idx -= MIXSRC_FIRST_TELEM; div_t qr = div(idx, 3); - lcd_putsnAtt(x, y, g_model.telemetrySensors[qr.quot].label, ZLEN(g_model.telemetrySensors[qr.quot].label), ZCHAR|att); + lcd_putsnAtt(x, y, g_model.telemetrySensors[qr.quot].label, TELEM_LABEL_LEN, ZCHAR|att); if (qr.rem) lcd_putcAtt(lcdLastPos, y, qr.rem==2 ? '+' : '-', att); } } @@ -773,9 +773,15 @@ void putsSwitches(coord_t x, coord_t y, int32_t idx, LcdFlags att) else if (idx <= SWSRC_ONE) { lcd_putsiAtt(x, y, STR_VSWITCHES, idx-SWSRC_ON+1+(2*NUM_STICKS), att); } - else { + else if (idx <= SWSRC_LAST_FLIGHT_MODE) { putsStrIdx(x, y, STR_FP, idx-SWSRC_FIRST_FLIGHT_MODE, att); } + else if (idx == SWSRC_TELEMETRY_STREAMING) { + lcd_putsAtt(x, y, "Tele", att); + } + else { + lcd_putsnAtt(x, y, g_model.telemetrySensors[idx-SWSRC_FIRST_SENSOR].label, TELEM_LABEL_LEN, ZCHAR|att); + } } #if defined(FLIGHT_MODES) diff --git a/radio/src/gui/Taranis/menu_model_setup.cpp b/radio/src/gui/Taranis/menu_model_setup.cpp index 2d1970c09..c2c541460 100644 --- a/radio/src/gui/Taranis/menu_model_setup.cpp +++ b/radio/src/gui/Taranis/menu_model_setup.cpp @@ -138,6 +138,7 @@ void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, uint8_t event) TimerData * timer = &g_model.timers[timerIdx]; putsStrIdx(0*FW, y, STR_TIMER, timerIdx+1); putsTimerMode(MODEL_SETUP_2ND_COLUMN, y, timer->mode, m_posHorz==0 ? attr : 0); + putsTimer(MODEL_SETUP_2ND_COLUMN+5*FW-2+5*FWNUM+1, y, timer->start, m_posHorz==1 ? attr : 0, m_posHorz==2 ? attr : 0); if (attr && m_posHorz < 0) drawFilledRect(MODEL_SETUP_2ND_COLUMN-1, y-1, LCD_W-MODEL_SETUP_2ND_COLUMN-MENUS_SCROLLBAR_WIDTH+1, FH+1); if (attr && s_editMode>0) { diff --git a/radio/src/gui/Taranis/menus.h b/radio/src/gui/Taranis/menus.h index ee5fced5f..c47e2d052 100644 --- a/radio/src/gui/Taranis/menus.h +++ b/radio/src/gui/Taranis/menus.h @@ -274,7 +274,7 @@ typedef int select_menu_value_t; select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, uint8_t event); uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, const pm_char *label, LcdFlags attr, uint8_t event); -int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, uint8_t event); +swsrc_t switchMenuItem(coord_t x, coord_t y, swsrc_t value, LcdFlags attr, uint8_t event); #define ON_OFF_MENU_ITEM(value, x, y, label, attr, event) value = onoffMenuItem(value, x, y, label, attr, event) diff --git a/radio/src/gui/Taranis/widgets.cpp b/radio/src/gui/Taranis/widgets.cpp index 9299456fe..b9ed8bd23 100644 --- a/radio/src/gui/Taranis/widgets.cpp +++ b/radio/src/gui/Taranis/widgets.cpp @@ -154,7 +154,7 @@ uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, const pm_char *label, return selectMenuItem(x, y, label, NULL, value, 0, 1, attr, event); } -int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, uint8_t event) +swsrc_t switchMenuItem(coord_t x, coord_t y, swsrc_t value, LcdFlags attr, uint8_t event) { lcd_putsColumnLeft(x, y, STR_SWITCH); putsSwitches(x, y, value, attr); diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index dd86250ec..3ac82b482 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -62,10 +62,10 @@ #define BEEP_VAL ( (g_eeGeneral.warnOpts & WARN_BVAL_BIT) >>3 ) #if defined(PCBTARANIS) - #define EEPROM_VER 217 + #define EEPROM_VER 218 #define FIRST_CONV_EEPROM_VER 216 #elif defined(PCBSKY9X) - #define EEPROM_VER 217 + #define EEPROM_VER 218 #define FIRST_CONV_EEPROM_VER 216 #elif defined(CPUM2560) || defined(CPUM2561) #define EEPROM_VER 217 @@ -116,7 +116,7 @@ #define MAX_FLIGHT_MODES 9 #define MAX_MIXERS 64 #define MAX_EXPOS 64 - #define NUM_LOGICAL_SWITCH 32 // number of logical switches + #define NUM_LOGICAL_SWITCH 64 // number of logical switches #define NUM_CFN 64 // number of functions assigned to switches #define MAX_SCRIPTS 7 #define MAX_INPUTS 32 @@ -628,8 +628,8 @@ enum AdjustGvarFunctionParam { #define CFN_SPARE_TYPE int16_t #endif PACK(typedef struct { - int8_t swtch; - uint8_t func; + int16_t swtch:9; + uint16_t func:7; PACK(union { PACK(struct { char name[LEN_CFN_NAME]; @@ -817,14 +817,15 @@ PACK(typedef struct { #if defined(PCBTARANIS) PACK(typedef struct { - uint32_t srcRaw:10; - uint32_t scale:14; - uint32_t chn:8; - int8_t swtch; - uint16_t flightModes; - int8_t weight; - int8_t carryTrim:6; - uint8_t mode:2; + uint16_t mode:2; + uint16_t scale:14; + uint16_t srcRaw:10; + int16_t carryTrim:6; + uint32_t chn:5; + int32_t swtch:9; + uint32_t flightModes:9; + int32_t weight:8; + int32_t spare:1; char name[LEN_EXPOMIX_NAME]; int8_t offset; CurveRef curve; @@ -944,15 +945,16 @@ PACK(typedef struct { #define SLOW_MAX (25*SLOW_STEP) /* 25 seconds */ #if defined(PCBTARANIS) PACK(typedef struct { - uint8_t destCh; - uint16_t flightModes:9; - uint16_t mltpx:2; // multiplex method: 0 means +=, 1 means *=, 2 means := + int16_t weight:11; // GV1=-1024, -GV1=1023 + uint16_t destCh:5; + uint16_t srcRaw:10; // srcRaw=0 means not used uint16_t carryTrim:1; - uint16_t mixWarn:4; // mixer warning - int16_t weight; - uint32_t srcRaw:10; + uint16_t mixWarn:2; // mixer warning + uint16_t mltpx:2; // multiplex method: 0 means +=, 1 means *=, 2 means := + uint16_t spare:1; int32_t offset:14; - int32_t swtch:8; + int32_t swtch:9; + uint32_t flightModes:9; CurveRef curve; uint8_t delayUp; uint8_t delayDown; @@ -1125,13 +1127,15 @@ enum LogicalSwitchesFunctions { #define MAX_LS_ANDSW SWSRC_LAST typedef int16_t ls_telemetry_value_t; PACK(typedef struct { // Logical Switches data - uint16_t func:6; - int16_t v1:10; + uint8_t func; + int32_t v1:10; + int32_t v3:10; + int32_t andsw:9; // TODO rename to xswtch + uint32_t andswtype:1; // TODO rename to xswtchType (AND / OR) + uint32_t spare:2; // anything else needed? int16_t v2; - int16_t v3; uint8_t delay; uint8_t duration; - int8_t andsw; }) LogicalSwitchData; #else typedef uint8_t ls_telemetry_value_t; @@ -1611,10 +1615,11 @@ PACK(typedef struct { #if defined(CPUARM) PACK(typedef struct { TRIMS_ARRAY; - int8_t swtch; // swtch of phase[0] is not used char name[LEN_FLIGHT_MODE_NAME]; - uint8_t fadeIn; - uint8_t fadeOut; + int16_t swtch:9; // swtch of phase[0] is not used + int16_t spare:7; + uint8_t fadeIn; + uint8_t fadeOut; ROTARY_ENCODER_ARRAY; PHASE_GVARS_DATA; }) FlightModeData; @@ -1754,6 +1759,10 @@ enum SwitchSources { #if defined(CPUARM) SWSRC_FIRST_FLIGHT_MODE, SWSRC_LAST_FLIGHT_MODE = SWSRC_FIRST_FLIGHT_MODE+MAX_FLIGHT_MODES-1, + + SWSRC_TELEMETRY_STREAMING, + SWSRC_FIRST_SENSOR, + SWSRC_LAST_SENSOR = SWSRC_FIRST_SENSOR+MAX_SENSORS-1, #endif SWSRC_COUNT, @@ -1972,8 +1981,8 @@ enum CountDownModes { #if defined(CPUARM) PACK(typedef struct { - int32_t mode:8; // timer trigger source -> off, abs, stk, stk%, sw/!sw, !m_sw/!m_sw - uint32_t start:24; + int32_t mode:9; // timer trigger source -> off, abs, stk, stk%, sw/!sw, !m_sw/!m_sw + uint32_t start:23; int32_t value:24; uint32_t countdownBeep:2; uint32_t minuteBeep:1; diff --git a/radio/src/opentx.h b/radio/src/opentx.h index bcee11321..8badd74a6 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -423,7 +423,7 @@ typedef int32_t rotenc_t; typedef int32_t getvalue_t; typedef uint32_t mixsrc_t; - typedef int8_t swsrc_t; + typedef int32_t swsrc_t; #else #define tmr10ms_t uint16_t extern volatile tmr10ms_t g_tmr10ms; From 0bf2a204e2c7370d1f37efd5e452c143555fd12a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 19 Sep 2015 20:25:39 +0200 Subject: [PATCH 03/74] Fixes #2902 --- radio/src/pulses/pxx_arm.cpp | 77 ++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/radio/src/pulses/pxx_arm.cpp b/radio/src/pulses/pxx_arm.cpp index 0109cbc26..c581a4584 100644 --- a/radio/src/pulses/pxx_arm.cpp +++ b/radio/src/pulses/pxx_arm.cpp @@ -173,7 +173,7 @@ void putPcmHead(unsigned int port) void setupPulsesPXX(unsigned int port) { - uint16_t chan=0, chan_low=0; + uint16_t pulseValue=0, pulseValueLow=0; modulePulsesData[port].pxx.ptr = modulePulsesData[port].pxx.pulses; modulePulsesData[port].pxx.pcmValue = 0 ; @@ -224,56 +224,73 @@ void setupPulsesPXX(unsigned int port) for (int i=0; i<8; i++) { if (flag1 & PXX_SEND_FAILSAFE) { if (g_model.moduleData[port].failsafeMode == FAILSAFE_HOLD) { - chan = (i < sendUpperChannels ? 4095 : 2047); + pulseValue = (i < sendUpperChannels ? 4095 : 2047); } else if (g_model.moduleData[port].failsafeMode == FAILSAFE_NOPULSES) { - chan = (i < sendUpperChannels ? 2048 : 0); + pulseValue = (i < sendUpperChannels ? 2048 : 0); } else { if (i < sendUpperChannels) { - int16_t failsafeValue = g_model.moduleData[port].failsafeChannels[8+g_model.moduleData[port].channelsStart+i]; - if (failsafeValue == FAILSAFE_CHANNEL_HOLD) - chan = 4095; - else if (failsafeValue == FAILSAFE_CHANNEL_NOPULSE) - chan = 2048; - else - chan = limit(2049, PPM_CH_CENTER(8+g_model.moduleData[port].channelsStart+i) - PPM_CENTER + (failsafeValue * 512 / 682) + 3072, 4094); + int channel = 8 + g_model.moduleData[port].channelsStart + i; + int16_t failsafeValue = g_model.moduleData[port].failsafeChannels[channel]; + if (failsafeValue == FAILSAFE_CHANNEL_HOLD) { + pulseValue = 4095; + } + else if (failsafeValue == FAILSAFE_CHANNEL_NOPULSE) { + pulseValue = 2048; + } + else { + failsafeValue += 2*PPM_CH_CENTER(channel) - 2*PPM_CENTER; + pulseValue = limit(2049, (failsafeValue * 512 / 682) + 3072, 4094); + } } else { - int16_t failsafeValue = g_model.moduleData[port].failsafeChannels[g_model.moduleData[port].channelsStart+i]; - if (failsafeValue == FAILSAFE_CHANNEL_HOLD) - chan = 2047; - else if (failsafeValue == FAILSAFE_CHANNEL_NOPULSE) - chan = 0; - else - chan = limit(1, PPM_CH_CENTER(g_model.moduleData[port].channelsStart+i) - PPM_CENTER + (failsafeValue * 512 / 682) + 1024, 2046); + int channel = g_model.moduleData[port].channelsStart + i; + int16_t failsafeValue = g_model.moduleData[port].failsafeChannels[channel]; + if (failsafeValue == FAILSAFE_CHANNEL_HOLD) { + pulseValue = 2047; + } + else if (failsafeValue == FAILSAFE_CHANNEL_NOPULSE) { + pulseValue = 0; + } + else { + failsafeValue += 2*PPM_CH_CENTER(channel) - 2*PPM_CENTER; + pulseValue = limit(1, (failsafeValue * 512 / 682) + 1024, 2046); + } } } } else { - if (i < sendUpperChannels) - chan = limit(2049, PPM_CH_CENTER(8+g_model.moduleData[port].channelsStart+i) - PPM_CENTER + (channelOutputs[8+g_model.moduleData[port].channelsStart+i] * 512 / 682) + 3072, 4094); - else if (i < NUM_CHANNELS(port)) - chan = limit(1, PPM_CH_CENTER(g_model.moduleData[port].channelsStart+i) - PPM_CENTER + (channelOutputs[g_model.moduleData[port].channelsStart+i] * 512 / 682) + 1024, 2046); - else - chan = 1024; + if (i < sendUpperChannels) { + int channel = 8 + g_model.moduleData[port].channelsStart + i; + int value = channelOutputs[channel] + 2*PPM_CH_CENTER(channel) - 2*PPM_CENTER; + pulseValue = limit(2049, (value * 512 / 682) + 3072, 4094); + } + else if (i < NUM_CHANNELS(port)) { + int channel = g_model.moduleData[port].channelsStart + i; + int value = channelOutputs[channel] + 2*PPM_CH_CENTER(channel) - 2*PPM_CENTER; + pulseValue = limit(1, (value * 512 / 682) + 1024, 2046); + } + else { + pulseValue = 1024; + } } if (i & 1) { - putPcmByte(chan_low, port); // Low byte of channel - putPcmByte(((chan_low >> 8) & 0x0F) | (chan << 4), port); // 4 bits each from 2 channels - putPcmByte(chan >> 4, port); // High byte of channel + putPcmByte(pulseValueLow, port); // Low byte of channel + putPcmByte(((pulseValueLow >> 8) & 0x0F) | (pulseValue << 4), port); // 4 bits each from 2 channels + putPcmByte(pulseValue >> 4, port); // High byte of channel } else { - chan_low = chan; + pulseValueLow = pulseValue; } } /* CRC16 */ putPcmByte(0, port); - chan = modulePulsesData[port].pxx.pcmCrc; - putPcmByte(chan>>8, port); - putPcmByte(chan, port); + pulseValue = modulePulsesData[port].pxx.pcmCrc; + putPcmByte(pulseValue>>8, port); + putPcmByte(pulseValue, port); /* Sync */ putPcmHead(port); From 1df45cbfc2802c46d26b94304fc5b680a76c7219 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 22 Sep 2015 22:35:01 +0200 Subject: [PATCH 04/74] EEPROM v217->v218 conversions (on board + Companion) --- companion/src/constants.h | 2 +- companion/src/eeprominterface.cpp | 18 +- .../src/firmwares/opentx/opentxeeprom.cpp | 121 +++++-- .../src/firmwares/opentx/opentxinterface.cpp | 8 +- radio/src/eeprom_conversions.cpp | 341 +++++++++++++++++- radio/src/eeprom_rlc.cpp | 2 +- radio/src/myeeprom.h | 15 +- 7 files changed, 438 insertions(+), 69 deletions(-) diff --git a/companion/src/constants.h b/companion/src/constants.h index 10960074d..958134e07 100644 --- a/companion/src/constants.h +++ b/companion/src/constants.h @@ -25,7 +25,7 @@ enum BoardEnum { #define C9X_MAX_GVARS 9 #define C9X_MAX_ENCODERS 2 #define C9X_NUM_CHNOUT 32 // number of real output channels -#define C9X_NUM_CSW 32 // number of custom switches +#define C9X_NUM_CSW 64 // number of custom switches #define C9X_MAX_CUSTOM_FUNCTIONS 64 // number of functions assigned to switches #define C9X_NUM_MODULES 2 #define NUM_STICKS 4 diff --git a/companion/src/eeprominterface.cpp b/companion/src/eeprominterface.cpp index d9243255e..e619bd4b6 100644 --- a/companion/src/eeprominterface.cpp +++ b/companion/src/eeprominterface.cpp @@ -475,13 +475,6 @@ QString RawSource::toString(const ModelData * model) const QObject::tr("ACC"), QObject::tr("GPS Time"), }; - static const QString logicalSwitches[] = { - QObject::tr("L1"), QObject::tr("L2"), QObject::tr("L3"), QObject::tr("L4"), QObject::tr("L5"), QObject::tr("L6"), QObject::tr("L7"), QObject::tr("L8"), QObject::tr("L9"), QObject::tr("L10"), - QObject::tr("L11"), QObject::tr("L12"), QObject::tr("L13"), QObject::tr("L14"), QObject::tr("L15"), QObject::tr("L16"), QObject::tr("L17"), QObject::tr("L18"), QObject::tr("L19"), QObject::tr("L20"), - QObject::tr("L21"), QObject::tr("L22"), QObject::tr("L23"), QObject::tr("L24"), QObject::tr("L25"), QObject::tr("L26"), QObject::tr("L27"), QObject::tr("L28"), QObject::tr("L29"), QObject::tr("L30"), - QObject::tr("L31"), QObject::tr("L32") - }; - if (index<0) { return QObject::tr("----"); } @@ -508,7 +501,7 @@ QString RawSource::toString(const ModelData * model) const case SOURCE_TYPE_SWITCH: return (IS_TARANIS(GetEepromInterface()->getBoard()) ? CHECK_IN_ARRAY(switchesX9D, index) : CHECK_IN_ARRAY(switches9X, index)); case SOURCE_TYPE_CUSTOM_SWITCH: - return logicalSwitches[index]; + return QObject::tr("L%1").arg(index+1); case SOURCE_TYPE_CYC: return QObject::tr("CYC%1").arg(index+1); case SOURCE_TYPE_PPM: @@ -590,13 +583,6 @@ QString RawSwitch::toString() const SwitchUp('R'), QString::fromUtf8("SR-"), SwitchDn('R'), }; - static const QString logicalSwitches[] = { - QObject::tr("L1"), QObject::tr("L2"), QObject::tr("L3"), QObject::tr("L4"), QObject::tr("L5"), QObject::tr("L6"), QObject::tr("L7"), QObject::tr("L8"), QObject::tr("L9"), QObject::tr("L10"), - QObject::tr("L11"), QObject::tr("L12"), QObject::tr("L13"), QObject::tr("L14"), QObject::tr("L15"), QObject::tr("L16"), QObject::tr("L17"), QObject::tr("L18"), QObject::tr("L19"), QObject::tr("L20"), - QObject::tr("L21"), QObject::tr("L22"), QObject::tr("L23"), QObject::tr("L24"), QObject::tr("L25"), QObject::tr("L26"), QObject::tr("L27"), QObject::tr("L28"), QObject::tr("L29"), QObject::tr("L30"), - QObject::tr("L31"), QObject::tr("L32") - }; - static const QString flightModes[] = { QObject::tr("FM0"), QObject::tr("FM1"), QObject::tr("FM2"), QObject::tr("FM3"), QObject::tr("FM4"), QObject::tr("FM5"), QObject::tr("FM6"), QObject::tr("FM7"), QObject::tr("FM8") }; @@ -634,7 +620,7 @@ QString RawSwitch::toString() const else return CHECK_IN_ARRAY(switches9X, index-1); case SWITCH_TYPE_VIRTUAL: - return CHECK_IN_ARRAY(logicalSwitches, index-1); + return QObject::tr("L%1").arg(index); case SWITCH_TYPE_MULTIPOS_POT: return CHECK_IN_ARRAY(multiposPots, index-1); case SWITCH_TYPE_TRIM: diff --git a/companion/src/firmwares/opentx/opentxeeprom.cpp b/companion/src/firmwares/opentx/opentxeeprom.cpp index 6d34cb994..a62d97025 100644 --- a/companion/src/firmwares/opentx/opentxeeprom.cpp +++ b/companion/src/firmwares/opentx/opentxeeprom.cpp @@ -22,12 +22,13 @@ #define MAX_MIXERS(board, version) (IS_ARM(board) ? 64 : 32) #define MAX_CHANNELS(board, version) (IS_ARM(board) ? 32 : 16) #define MAX_EXPOS(board, version) (IS_ARM(board) ? ((IS_TARANIS(board) && version >= 216) ? 64 : 32) : (IS_DBLRAM(board, version) ? 16 : 14)) -#define MAX_CUSTOM_SWITCHES(board, version) (IS_ARM(board) ? 32 : ((IS_DBLEEPROM(board, version) && version<217) ? 15 : 12)) +#define MAX_LOGICAL_SWITCHES(board, version) (IS_ARM(board) ? (version >= 218 ? 64 : 32) : ((IS_DBLEEPROM(board, version) && version<217) ? 15 : 12)) #define MAX_CUSTOM_FUNCTIONS(board, version) (IS_ARM(board) ? (version >= 216 ? 64 : 32) : (IS_DBLEEPROM(board, version) ? 24 : 16)) #define MAX_CURVES(board, version) (IS_ARM(board) ? ((IS_TARANIS(board) && version >= 216) ? 32 : 16) : 8) #define MAX_GVARS(board, version) ((IS_ARM(board) && version >= 216) ? 9 : 5) #define MAX_SENSORS(board, version) (32) #define NUM_PPM_INPUTS(board, version) ((IS_ARM(board) && version >= 216) ? 16 : 8) +#define ROTENC_COUNT(board, version) (IS_ARM(board) ? ((IS_TARANIS(board) && version >= 218) ? 0 : 1) : (IS_2560(board) ? 2 : 0)) #define IS_AFTER_RELEASE_21_MARCH_2013(board, version) (version >= 214 || (!IS_ARM(board) && version >= 213)) #define IS_AFTER_RELEASE_23_MARCH_2013(board, version) (version >= 214 || (board==BOARD_STOCK && version >= 213)) @@ -100,7 +101,7 @@ class SwitchesConversionTable: public ConversionTable { } } - for (int i=1; i<=MAX_CUSTOM_SWITCHES(board, version); i++) { + for (int i=1; i<=MAX_LOGICAL_SWITCHES(board, version); i++) { addConversion(RawSwitch(SWITCH_TYPE_VIRTUAL, -i), -val+offset); addConversion(RawSwitch(SWITCH_TYPE_VIRTUAL, i), val++); } @@ -125,7 +126,7 @@ class SwitchesConversionTable: public ConversionTable { addConversion(RawSwitch(SWITCH_TYPE_SWITCH, s), val++); } - for (int i=1; i<=MAX_CUSTOM_SWITCHES(board, version); i++) { + for (int i=1; i<=MAX_LOGICAL_SWITCHES(board, version); i++) { addConversion(RawSwitch(SWITCH_TYPE_VIRTUAL, i), val++); } @@ -263,7 +264,7 @@ class SourcesConversionTable: public ConversionTable { addConversion(RawSource(SOURCE_TYPE_SWITCH, i), val++); } } - for (int i=0; i=215)) { // On stock we use 10bits per trim @@ -803,13 +804,21 @@ class FlightModeField: public TransformedField { internalField.Append(new SignedField<16>(trimBase[i])); } } - - internalField.Append(new SwitchField<8>(phase.swtch, board, version)); - if (HAS_LARGE_LCD(board)) - internalField.Append(new ZCharField<10>(phase.name)); - else - internalField.Append(new ZCharField<6>(phase.name)); - + if (IS_ARM(board) && version >= 218) { + if (HAS_LARGE_LCD(board)) + internalField.Append(new ZCharField<10>(phase.name)); + else + internalField.Append(new ZCharField<6>(phase.name)); + internalField.Append(new SwitchField<9>(phase.swtch, board, version)); + internalField.Append(new SpareBitsField<7>()); + } + else { + internalField.Append(new SwitchField<8>(phase.swtch, board, version)); + if (HAS_LARGE_LCD(board)) + internalField.Append(new ZCharField<10>(phase.name)); + else + internalField.Append(new ZCharField<6>(phase.name)); + } if (IS_ARM(board) && version >= 214) { internalField.Append(new UnsignedField<8>(phase.fadeIn)); internalField.Append(new UnsignedField<8>(phase.fadeOut)); @@ -922,7 +931,25 @@ class MixField: public TransformedField { version(version), model(model) { - if (IS_TARANIS(board) && version >= 217) { + if (IS_TARANIS(board) && version >= 218) { + internalField.Append(new SignedField<11>(_weight)); + internalField.Append(new UnsignedField<5>(_destCh)); + internalField.Append(new SourceField<10>(mix.srcRaw, board, version, FLAG_NOTELEMETRY)); + internalField.Append(new UnsignedField<1>((unsigned int &)mix.carryTrim)); + internalField.Append(new UnsignedField<2>(mix.mixWarn)); + internalField.Append(new UnsignedField<2>((unsigned int &)mix.mltpx)); + internalField.Append(new SpareBitsField<1>()); + internalField.Append(new SignedField<14>(_offset)); + internalField.Append(new SwitchField<9>(mix.swtch, board, version)); + internalField.Append(new UnsignedField<9>(mix.flightModes)); + internalField.Append(new CurveReferenceField(mix.curve, board, version)); + internalField.Append(new UnsignedField<8>(mix.delayUp)); + internalField.Append(new UnsignedField<8>(mix.delayDown)); + internalField.Append(new UnsignedField<8>(mix.speedUp)); + internalField.Append(new UnsignedField<8>(mix.speedDown)); + internalField.Append(new ZCharField<8>(mix.name)); + } + else if (IS_TARANIS(board) && version >= 217) { internalField.Append(new UnsignedField<8>(_destCh)); internalField.Append(new UnsignedField<9>(mix.flightModes)); internalField.Append(new UnsignedField<2>((unsigned int &)mix.mltpx)); @@ -1190,7 +1217,21 @@ class InputField: public TransformedField { board(board), version(version) { - if (IS_TARANIS(board) && version >= 217) { + if (IS_TARANIS(board) && version >= 218) { + internalField.Append(new UnsignedField<2>(expo.mode, "Mode")); + internalField.Append(new UnsignedField<14>(expo.scale, "Scale")); + internalField.Append(new SourceField<10>(expo.srcRaw, board, version, 0)); + internalField.Append(new SignedField<6>(expo.carryTrim, "CarryTrim")); + internalField.Append(new UnsignedField<5>(expo.chn, "Channel")); + internalField.Append(new SwitchField<9>(expo.swtch, board, version)); + internalField.Append(new UnsignedField<9>(expo.flightModes)); + internalField.Append(new SignedField<8>(_weight, "Weight")); + internalField.Append(new SpareBitsField<1>()); + internalField.Append(new ZCharField<8>(expo.name)); + internalField.Append(new SignedField<8>(_offset, "Offset")); + internalField.Append(new CurveReferenceField(expo.curve, board, version)); + } + else if (IS_TARANIS(board) && version >= 217) { internalField.Append(new SourceField<10>(expo.srcRaw, board, version, 0)); internalField.Append(new UnsignedField<14>(expo.scale, "Scale")); internalField.Append(new UnsignedField<8>(expo.chn, "Channel")); @@ -1595,7 +1636,7 @@ class AndSwitchesConversionTable: public ConversionTable { addConversion(RawSwitch(SWITCH_TYPE_SWITCH, -s), -val); addConversion(RawSwitch(SWITCH_TYPE_SWITCH, s), val++); } - for (int i=1; i<=MAX_CUSTOM_SWITCHES(board, version); i++) { + for (int i=1; i<=MAX_LOGICAL_SWITCHES(board, version); i++) { addConversion(RawSwitch(SWITCH_TYPE_VIRTUAL, -i), -val); addConversion(RawSwitch(SWITCH_TYPE_VIRTUAL, i), val++); } @@ -1606,7 +1647,7 @@ class AndSwitchesConversionTable: public ConversionTable { addConversion(RawSwitch(SWITCH_TYPE_SWITCH, -s), -val); addConversion(RawSwitch(SWITCH_TYPE_SWITCH, s), val++); } - for (int i=1; i<=MAX_CUSTOM_SWITCHES(board, version); i++) { + for (int i=1; i<=MAX_LOGICAL_SWITCHES(board, version); i++) { addConversion(RawSwitch(SWITCH_TYPE_VIRTUAL, -i), -val); addConversion(RawSwitch(SWITCH_TYPE_VIRTUAL, i), val++); } @@ -1671,7 +1712,16 @@ class LogicalSwitchField: public TransformedField { v2(0), v3(0) { - if (IS_ARM(board) && version >= 217) { + if (IS_ARM(board) && version >= 218) { + internalField.Append(new ConversionField< UnsignedField<8> >(csw.func, &functionsConversionTable, "Function")); + internalField.Append(new SignedField<10>(v1)); + internalField.Append(new SignedField<10>(v3)); + internalField.Append(new ConversionField< SignedField<9> >((int &)csw.andsw, andswitchesConversionTable, "AND switch")); + internalField.Append(new SpareBitsField<1>()); // TODO extra switch mode + internalField.Append(new SpareBitsField<2>()); + internalField.Append(new SignedField<16>(v2)); + } + else if (IS_ARM(board) && version >= 217) { internalField.Append(new ConversionField< UnsignedField<6> >(csw.func, &functionsConversionTable, "Function")); internalField.Append(new SignedField<10>(v1)); internalField.Append(new SignedField<16>(v2)); @@ -1706,7 +1756,7 @@ class LogicalSwitchField: public TransformedField { int scale = (version >= 216 ? 0 : 5); internalField.Append(new ConversionField< UnsignedField<8> >(csw.delay, 0, scale)); internalField.Append(new ConversionField< UnsignedField<8> >(csw.duration, 0, scale)); - if (version >= 214) { + if (version >= 214 && version <= 217) { internalField.Append(new ConversionField< SignedField<8> >((int &)csw.andsw, andswitchesConversionTable, "AND switch")); } } @@ -2029,8 +2079,14 @@ class ArmCustomFunctionField: public TransformedField { { memset(_param, 0, sizeof(_param)); - internalField.Append(new SwitchField<8>(fn.swtch, board, version)); - internalField.Append(new ConversionField< UnsignedField<8> >(_func, &functionsConversionTable, "Function", ::QObject::tr("OpenTX on this board doesn't accept this function"))); + if (version >= 218) { + internalField.Append(new SwitchField<9>(fn.swtch, board, version)); + internalField.Append(new ConversionField< UnsignedField<7> >(_func, &functionsConversionTable, "Function", ::QObject::tr("OpenTX on this board doesn't accept this function"))); + } + else { + internalField.Append(new SwitchField<8>(fn.swtch, board, version)); + internalField.Append(new ConversionField< UnsignedField<8> >(_func, &functionsConversionTable, "Function", ::QObject::tr("OpenTX on this board doesn't accept this function"))); + } if (IS_TARANIS(board) && version >= 216) internalField.Append(new CharField<8>(_param, false)); @@ -2905,8 +2961,21 @@ OpenTxModelData::OpenTxModelData(ModelData & modelData, BoardEnum board, unsigne } for (int i=0; i(modelData.timers[i].mode, board, version, true)); - if (IS_ARM(board) && version >= 217) { + if (IS_ARM(board) && version >= 218) { + internalField.Append(new SwitchField<9>(modelData.timers[i].mode, board, version, true)); + internalField.Append(new UnsignedField<23>(modelData.timers[i].val)); + internalField.Append(new SignedField<24>(modelData.timers[i].pvalue)); + internalField.Append(new UnsignedField<2>(modelData.timers[i].countdownBeep)); + internalField.Append(new BoolField<1>(modelData.timers[i].minuteBeep)); + internalField.Append(new UnsignedField<2>(modelData.timers[i].persistent)); + internalField.Append(new SpareBitsField<3>()); + if (IS_TARANIS(board)) + internalField.Append(new ZCharField<8>(modelData.timers[i].name)); + else + internalField.Append(new ZCharField<3>(modelData.timers[i].name)); + } + else if (IS_ARM(board) && version >= 217) { + internalField.Append(new SwitchField<8>(modelData.timers[i].mode, board, version, true)); internalField.Append(new UnsignedField<24>(modelData.timers[i].val)); internalField.Append(new SignedField<24>(modelData.timers[i].pvalue)); internalField.Append(new UnsignedField<2>(modelData.timers[i].countdownBeep)); @@ -2919,6 +2988,7 @@ OpenTxModelData::OpenTxModelData(ModelData & modelData, BoardEnum board, unsigne internalField.Append(new ZCharField<3>(modelData.timers[i].name)); } else if ((IS_ARM(board) || IS_2560(board)) && version >= 216) { + internalField.Append(new SwitchField<8>(modelData.timers[i].mode, board, version, true)); internalField.Append(new UnsignedField<16>(modelData.timers[i].val)); internalField.Append(new UnsignedField<2>(modelData.timers[i].countdownBeep)); internalField.Append(new BoolField<1>(modelData.timers[i].minuteBeep)); @@ -2927,6 +2997,7 @@ OpenTxModelData::OpenTxModelData(ModelData & modelData, BoardEnum board, unsigne internalField.Append(new SignedField<16>(modelData.timers[i].pvalue)); } else if (afterrelease21March2013) { + internalField.Append(new SwitchField<8>(modelData.timers[i].mode, board, version, true)); internalField.Append(new UnsignedField<12>(modelData.timers[i].val)); internalField.Append(new BoolField<1>((bool &)modelData.timers[i].countdownBeep)); internalField.Append(new BoolField<1>(modelData.timers[i].minuteBeep)); @@ -2940,6 +3011,7 @@ OpenTxModelData::OpenTxModelData(ModelData & modelData, BoardEnum board, unsigne } } else { + internalField.Append(new SwitchField<8>(modelData.timers[i].mode, board, version, true)); internalField.Append(new UnsignedField<16>(modelData.timers[i].val)); if (HAS_PERSISTENT_TIMERS(board)) { internalField.Append(new UnsignedField<1>(modelData.timers[i].persistent)); @@ -2999,7 +3071,7 @@ OpenTxModelData::OpenTxModelData(ModelData & modelData, BoardEnum board, unsigne for (int i=0; i(modelData.moduleData[0].ppmFrameLength)); diff --git a/companion/src/firmwares/opentx/opentxinterface.cpp b/companion/src/firmwares/opentx/opentxinterface.cpp index e74feb378..16b5d6f78 100644 --- a/companion/src/firmwares/opentx/opentxinterface.cpp +++ b/companion/src/firmwares/opentx/opentxinterface.cpp @@ -403,7 +403,7 @@ int OpenTxEepromInterface::save(uint8_t *eeprom, RadioData &radioData, uint32_t case BOARD_SKY9X: case BOARD_AR9X: case BOARD_9XRPRO: - version = 217; + version = 218; break; case BOARD_GRUVIN9X: case BOARD_MEGA2560: @@ -638,7 +638,7 @@ int OpenTxFirmware::getCapability(const Capability capability) return id.contains("nooverridech") ? 0 : 1; case LogicalSwitches: if (IS_ARM(board)) - return 32; + return 64; else return 12; case CustomAndSwitches: @@ -954,10 +954,10 @@ EepromLoadErrors OpenTxEepromInterface::checkVersion(unsigned int version) // M128 revert because too much RAM used! case 216: // A lot of things (first github release) - return OLD_VERSION; - break; case 217: // 3 logical switches removed on M128 / gruvin9x boards + return OLD_VERSION; + case 218: break; default: return NOT_OPENTX; diff --git a/radio/src/eeprom_conversions.cpp b/radio/src/eeprom_conversions.cpp index 19ccfb0c9..5d7fafbc6 100644 --- a/radio/src/eeprom_conversions.cpp +++ b/radio/src/eeprom_conversions.cpp @@ -109,7 +109,7 @@ enum Mix216Sources { MIXSRC216_SWA, MIXSRC216_SWB, MIXSRC216_SWC, - MIXSRC216_LAST_LOGICAL_SWITCH = MIXSRC216_FIRST_LOGICAL_SWITCH+NUM_LOGICAL_SWITCH-1, + MIXSRC216_LAST_LOGICAL_SWITCH = MIXSRC216_FIRST_LOGICAL_SWITCH+32-1, MIXSRC216_FIRST_TRAINER, MIXSRC216_LAST_TRAINER = MIXSRC216_FIRST_TRAINER+NUM_TRAINER-1, @@ -236,6 +236,19 @@ PACK(typedef struct { CurveRef curve; uint8_t spare; }) ExpoData_v216; +PACK(typedef struct { + uint32_t srcRaw:10; + uint32_t scale:14; + uint32_t chn:8; + int8_t swtch; + uint16_t flightModes; + int8_t weight; + int8_t carryTrim:6; + uint8_t mode:2; + char name[LEN_EXPOMIX_NAME]; + int8_t offset; + CurveRef curve; +}) ExpoData_v217; #else PACK(typedef struct { uint8_t mode; // 0=end, 1=pos, 2=neg, 3=both @@ -257,6 +270,7 @@ PACK(typedef struct { char name[LEN_EXPOMIX_NAME]; int8_t curveParam; }) ExpoData_v216; +typedef ExpoData_v216 ExpoData_v217; #endif #if defined(PCBTARANIS) @@ -333,6 +347,23 @@ PACK(typedef struct { char name[LEN_EXPOMIX_NAME]; uint8_t spare3; }) MixData_v216; +PACK(typedef struct { + uint8_t destCh; + uint16_t flightModes:9; + uint16_t mltpx:2; // multiplex method: 0 means +=, 1 means *=, 2 means := + uint16_t carryTrim:1; + uint16_t mixWarn:4; // mixer warning + int16_t weight; + uint32_t srcRaw:10; + int32_t offset:14; + int32_t swtch:8; + CurveRef curve; + uint8_t delayUp; + uint8_t delayDown; + uint8_t speedUp; + uint8_t speedDown; + char name[LEN_EXPOMIX_NAME]; +}) MixData_v217; #else PACK(typedef struct { uint8_t destCh; @@ -375,6 +406,7 @@ PACK(typedef struct { int16_t offset; char name[LEN_EXPOMIX_NAME]; }) MixData_v216; +typedef MixData MixData_v217; #endif PACK(typedef struct { @@ -397,14 +429,25 @@ PACK(typedef struct { uint16_t value; }) TimerData_v216; +PACK(typedef struct { + int32_t mode:8; // timer trigger source -> off, abs, stk, stk%, sw/!sw, !m_sw/!m_sw + uint32_t start:24; + int32_t value:24; + uint32_t countdownBeep:2; + uint32_t minuteBeep:1; + uint32_t persistent:2; + uint32_t spare:3; + char name[LEN_TIMER_NAME]; +}) TimerData_v217; + PACK(typedef struct { int16_t trim[4]; int8_t swtch; // swtch of phase[0] is not used char name[LEN_FLIGHT_MODE_NAME]; uint8_t fadeIn; uint8_t fadeOut; - ROTARY_ENCODER_ARRAY; - gvar_t gvars[5]; + int16_t rotaryEncoders[1]; + gvar_t gvars[9]; }) FlightModeData_v215; PACK(typedef struct { @@ -426,6 +469,16 @@ PACK(typedef struct { // Logical Switches data int8_t andsw; }) LogicalSwitchData_v216; +PACK(typedef struct { // Logical Switches data + uint16_t func:6; + int16_t v1:10; + int16_t v2; + int16_t v3; + uint8_t delay; + uint8_t duration; + int8_t andsw; +}) LogicalSwitchData_v217; + #if defined(PCBTARANIS) PACK(typedef struct { int8_t swtch; @@ -441,6 +494,28 @@ PACK(typedef struct { uint8_t mode:2; uint8_t active:6; }) CustomFunctionData_v215; +PACK(typedef struct { + int8_t swtch; + uint8_t func; + PACK(union { + PACK(struct { + char name[8]; + }) play; + + PACK(struct { + int16_t val; + uint8_t mode; + uint8_t param; + int32_t spare2; + }) all; + + PACK(struct { + int32_t val1; + int32_t val2; + }) clear; + }); + uint8_t active; +}) CustomFunctionData_v216; #else PACK(typedef struct { int8_t swtch; @@ -456,6 +531,28 @@ PACK(typedef struct { uint8_t mode:2; uint8_t active:6; }) CustomFunctionData_v215; +PACK(typedef struct { + int8_t swtch; + uint8_t func; + PACK(union { + PACK(struct { + char name[6]; + }) play; + + PACK(struct { + int16_t val; + uint8_t mode; + uint8_t param; + int16_t spare2; + }) all; + + PACK(struct { + int32_t val1; + int16_t val2; + }) clear; + }); + uint8_t active; +}) CustomFunctionData_v216; #endif PACK(typedef struct { @@ -592,10 +689,10 @@ PACK(typedef struct { CURVDATA curves[MAX_CURVES]; int8_t points[NUM_POINTS]; - LogicalSwitchData_v216 logicalSw[NUM_LOGICAL_SWITCH]; - CustomFunctionData customFn[NUM_CFN]; + LogicalSwitchData_v216 logicalSw[32]; + CustomFunctionData_v216 customFn[NUM_CFN]; SwashRingData_v215 swashR; - FlightModeData flightModeData[MAX_FLIGHT_MODES]; + FlightModeData_v215 flightModeData[MAX_FLIGHT_MODES]; uint8_t thrTraceSrc; @@ -610,6 +707,49 @@ PACK(typedef struct { }) ModelData_v216; +PACK(typedef struct { + ModelHeader header; + TimerData_v217 timers[MAX_TIMERS]; + ARM_FIELD(uint8_t telemetryProtocol:3) + uint8_t thrTrim:1; // Enable Throttle Trim + ARM_FIELD(uint8_t noGlobalFunctions:1) + ARM_FIELD(uint8_t displayTrims:2) + ARM_FIELD(uint8_t ignoreSensorIds:1) + int8_t trimInc:3; // Trim Increments + uint8_t disableThrottleWarning:1; + ARM_FIELD(uint8_t displayChecklist:1) + uint8_t extendedLimits:1; + uint8_t extendedTrims:1; + uint8_t throttleReversed:1; + BeepANACenter beepANACenter; + MixData_v217 mixData[MAX_MIXERS]; + LimitData limitData[NUM_CHNOUT]; + ExpoData_v217 expoData[MAX_EXPOS]; + + CURVDATA curves[MAX_CURVES]; + int8_t points[NUM_POINTS]; + + LogicalSwitchData_v217 logicalSw[32]; + CustomFunctionData_v216 customFn[NUM_CFN]; + SwashRingData swashR; + FlightModeData_v215 flightModeData[MAX_FLIGHT_MODES]; + + uint8_t thrTraceSrc; + + swarnstate_t switchWarningState; + swarnenable_t switchWarningEnable; + + MODEL_GVARS_DATA + + TELEMETRY_DATA + + MODELDATA_EXTRA + + TelemetrySensor telemetrySensors[MAX_SENSORS]; + + TARANIS_REV9E_FIELD(uint8_t topLcdTimer) +}) ModelData_v217; + #if defined(PCBTARANIS) #define NUM_POTS_215 4 #else @@ -700,7 +840,7 @@ void ConvertGeneralSettings_215_to_216(EEGeneral &settings) settings.hapticStrength = 0; // Haptic strength reset } -void ConvertGeneralSettings_216_to_217(EEGeneral &settings) +void ConvertGeneralSettings_216_to_217(EEGeneral & settings) { settings.version = 217; #if defined(PCBTARANIS) @@ -709,6 +849,11 @@ void ConvertGeneralSettings_216_to_217(EEGeneral &settings) #endif } +void ConvertGeneralSettings_217_to_218(EEGeneral & settings) +{ + settings.version = 218; +} + int ConvertTelemetrySource_215_to_216(int source) { // TELEM_TX_TIME and 5 spare added @@ -845,6 +990,19 @@ int ConvertSwitch_216_to_217(int swtch) } #endif +int ConvertSwitch_217_to_218(int swtch) +{ + // 32 additional logical switches + + if (swtch < 0) + return -ConvertSwitch_217_to_218(-swtch); + + if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH+32) + return swtch+32; + + return swtch; +} + int ConvertSource_216_to_217(int source) { #if defined(PCBTARANIS) && defined(REV9E) @@ -890,8 +1048,8 @@ void ConvertModel_216_to_217(ModelData &model) ModelData_v216 oldModel; memcpy(&oldModel, &model, sizeof(oldModel)); - ModelData &newModel = model; - memset(&newModel, 0, sizeof(ModelData)); + ModelData_v217 & newModel = (ModelData_v217 &)model; + memset(&newModel, 0, sizeof(ModelData_v217)); char name[LEN_MODEL_NAME+1]; zchar2str(name, oldModel.header.name, LEN_MODEL_NAME); @@ -904,7 +1062,7 @@ void ConvertModel_216_to_217(ModelData &model) #endif for (uint8_t i=0; i<2; i++) { - TimerData & timer = newModel.timers[i]; + TimerData_v217 & timer = newModel.timers[i]; if (oldModel.timers[i].mode >= TMRMODE_COUNT) timer.mode = TMRMODE_COUNT + ConvertSwitch_216_to_217(oldModel.timers[i].mode - TMRMODE_COUNT + 1) - 1; else @@ -981,8 +1139,8 @@ void ConvertModel_216_to_217(ModelData &model) } memcpy(newModel.curves, oldModel.curves, sizeof(newModel.curves)); memcpy(newModel.points, oldModel.points, sizeof(newModel.points)); - for (int i=0; i= TMRMODE_COUNT) + newModel.timers[i].mode = TMRMODE_COUNT + ConvertSwitch_217_to_218(oldModel.timers[i].mode - TMRMODE_COUNT + 1) - 1; + else + newModel.timers[i].mode = ConvertSwitch_217_to_218(oldModel.timers[i].mode); + if (oldModel.timers[i].mode) + TRACE("timer mode %d => %d", oldModel.timers[i].mode, newModel.timers[i].mode); + newModel.timers[i].start = oldModel.timers[i].start; + newModel.timers[i].value = oldModel.timers[i].value; + newModel.timers[i].countdownBeep = oldModel.timers[i].countdownBeep; + newModel.timers[i].minuteBeep = oldModel.timers[i].minuteBeep; + newModel.timers[i].persistent = oldModel.timers[i].persistent; + memcpy(newModel.timers[i].name, oldModel.timers[i].name, sizeof(newModel.timers[i].name)); + } + newModel.telemetryProtocol = oldModel.telemetryProtocol; + newModel.thrTrim = oldModel.thrTrim; + newModel.noGlobalFunctions = oldModel.noGlobalFunctions; + newModel.displayTrims = oldModel.displayTrims; + newModel.ignoreSensorIds = oldModel.ignoreSensorIds; + newModel.trimInc = oldModel.trimInc; + newModel.disableThrottleWarning = oldModel.disableThrottleWarning; + newModel.displayChecklist = oldModel.displayChecklist; + newModel.extendedLimits = oldModel.extendedLimits; + newModel.extendedTrims = oldModel.extendedTrims; + newModel.throttleReversed = oldModel.throttleReversed; + newModel.beepANACenter = oldModel.beepANACenter; + for (int i=0; i Date: Sun, 4 Oct 2015 14:48:00 +0200 Subject: [PATCH 05/74] Horus drivers --- radio/src/targets/Horus/adc_driver.cpp | 118 + radio/src/targets/Horus/audio_driver.cpp | 172 + radio/src/targets/Horus/board_horus.cpp | 264 ++ radio/src/targets/Horus/board_horus.h | 310 ++ radio/src/targets/Horus/delays.c | 57 + radio/src/targets/Horus/diskio.cpp | 296 ++ radio/src/targets/Horus/hal.h | 260 ++ radio/src/targets/Horus/haptic_driver.cpp | 75 + radio/src/targets/Horus/i2c_driver.cpp | 116 + radio/src/targets/Horus/keys_driver.cpp | 242 ++ radio/src/targets/Horus/lcd_driver.cpp | 529 +++ radio/src/targets/Horus/led_driver.cpp | 67 + radio/src/targets/Horus/pulses_driver.cpp | 321 ++ radio/src/targets/Horus/pwr_driver.c | 119 + radio/src/targets/Horus/sdio_sd.c | 3174 ++++++++++++++++++ radio/src/targets/Horus/sdio_sd.h | 410 +++ radio/src/targets/Horus/sdram_driver.c | 445 +++ radio/src/targets/Horus/serial2_driver.cpp | 166 + radio/src/targets/Horus/stm32f4_flash.ld | 170 + radio/src/targets/Horus/system_stm32f4xx.c | 569 ++++ radio/src/targets/Horus/telemetry_driver.cpp | 137 + radio/src/targets/Horus/trainer_driver.cpp | 171 + radio/src/targets/Horus/usb_bsp.c | 149 + radio/src/targets/Horus/usb_conf.h | 171 + radio/src/targets/Horus/usb_driver.c | 85 + radio/src/targets/Horus/usbd_cdc.cpp | 222 ++ radio/src/targets/Horus/usbd_conf.h | 71 + radio/src/targets/Horus/usbd_desc.c | 242 ++ radio/src/targets/Horus/usbd_desc.h | 120 + radio/src/targets/Horus/usbd_hid_joystick.c | 479 +++ radio/src/targets/Horus/usbd_storage_msd.cpp | 811 +++++ radio/src/targets/Horus/usbd_usr.cpp | 128 + 32 files changed, 10666 insertions(+) create mode 100644 radio/src/targets/Horus/adc_driver.cpp create mode 100644 radio/src/targets/Horus/audio_driver.cpp create mode 100644 radio/src/targets/Horus/board_horus.cpp create mode 100644 radio/src/targets/Horus/board_horus.h create mode 100644 radio/src/targets/Horus/delays.c create mode 100644 radio/src/targets/Horus/diskio.cpp create mode 100644 radio/src/targets/Horus/hal.h create mode 100644 radio/src/targets/Horus/haptic_driver.cpp create mode 100644 radio/src/targets/Horus/i2c_driver.cpp create mode 100644 radio/src/targets/Horus/keys_driver.cpp create mode 100644 radio/src/targets/Horus/lcd_driver.cpp create mode 100644 radio/src/targets/Horus/led_driver.cpp create mode 100644 radio/src/targets/Horus/pulses_driver.cpp create mode 100644 radio/src/targets/Horus/pwr_driver.c create mode 100644 radio/src/targets/Horus/sdio_sd.c create mode 100644 radio/src/targets/Horus/sdio_sd.h create mode 100755 radio/src/targets/Horus/sdram_driver.c create mode 100644 radio/src/targets/Horus/serial2_driver.cpp create mode 100644 radio/src/targets/Horus/stm32f4_flash.ld create mode 100644 radio/src/targets/Horus/system_stm32f4xx.c create mode 100644 radio/src/targets/Horus/telemetry_driver.cpp create mode 100644 radio/src/targets/Horus/trainer_driver.cpp create mode 100644 radio/src/targets/Horus/usb_bsp.c create mode 100644 radio/src/targets/Horus/usb_conf.h create mode 100644 radio/src/targets/Horus/usb_driver.c create mode 100644 radio/src/targets/Horus/usbd_cdc.cpp create mode 100644 radio/src/targets/Horus/usbd_conf.h create mode 100644 radio/src/targets/Horus/usbd_desc.c create mode 100644 radio/src/targets/Horus/usbd_desc.h create mode 100644 radio/src/targets/Horus/usbd_hid_joystick.c create mode 100644 radio/src/targets/Horus/usbd_storage_msd.cpp create mode 100644 radio/src/targets/Horus/usbd_usr.cpp diff --git a/radio/src/targets/Horus/adc_driver.cpp b/radio/src/targets/Horus/adc_driver.cpp new file mode 100644 index 000000000..e6f46ff9e --- /dev/null +++ b/radio/src/targets/Horus/adc_driver.cpp @@ -0,0 +1,118 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define STICK_LV 3 +#define STICK_LH 2 +#define STICK_RV 0 +#define STICK_RH 1 +#define POT_L 13 +#define POT_R 5 +#define SLIDE_L 9 +#define SLIDE_R 8 +#define SWITCHES1 11 +#define SWITCHES2 7 +#define SWITCHES3 10 +#define SWITCHES4 6 +#define BATTERY 14 + +// Sample time should exceed 1uS +#define SAMPTIME 2 // sample time = 28 cycles + +uint16_t Analog_values[NUMBER_ANALOG] __DMA; + +#define NUMBER_ANALOG_ADC1 13 + +#if 0 +const int8_t ana_direction[NUMBER_ANALOG] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; +#endif + +void adcInit() +{ + // Enable clocks + RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; + RCC->AHB1ENR |= ADC_RCC_AHB1Periph_GPIO | RCC_AHB1ENR_DMA2EN; + + configure_pins(ADC_GPIO_PIN_STICK_LV | ADC_GPIO_PIN_STICK_LH | ADC_GPIO_PIN_STICK_RV | ADC_GPIO_PIN_STICK_RH | + ADC_GPIO_PIN_POT_R1 | ADC_GPIO_SWITCHES_PIN_R3 | ADC_GPIO_SWITCHES_PIN_R4, PIN_ANALOG | PIN_PORTA); + + configure_pins(ADC_GPIO_PIN_POT_R2 | ADC_GPIO_PIN_POT_L2, PIN_ANALOG | PIN_PORTB); + + configure_pins(ADC_GPIO_PIN_POT_L1 | ADC_GPIO_SWITCHES_PIN_L3 | ADC_GPIO_SWITCHES_PIN_L4 | ADC_GPIO_PIN_BATT, PIN_ANALOG | PIN_PORTC); + + ADC1->CR1 = ADC_CR1_SCAN; + ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_DMA | ADC_CR2_DDS; + ADC1->SQR1 = (BATTERY<<0) + ((NUMBER_ANALOG_ADC1-1) << 20); // bits 23:20 = number of conversions + ADC1->SQR2 = (SLIDE_L<<0) + (SLIDE_R<<5) + (SWITCHES1<<10) + (SWITCHES2<<15) + (SWITCHES3<<20) + (SWITCHES4<<25); // conversions 7 to 12 + ADC1->SQR3 = (STICK_LH<<0) + (STICK_LV<<5) + (STICK_RV<<10) + (STICK_RH<<15) + (POT_L<<20) + (POT_R<<25); // conversions 1 to 6 + ADC1->SMPR1 = SAMPTIME + (SAMPTIME<<3) + (SAMPTIME<<6) + (SAMPTIME<<9) + (SAMPTIME<<12) + (SAMPTIME<<15) + (SAMPTIME<<18) + (SAMPTIME<<21) + (SAMPTIME<<24); + ADC1->SMPR2 = SAMPTIME + (SAMPTIME<<3) + (SAMPTIME<<6) + (SAMPTIME<<9) + (SAMPTIME<<12) + (SAMPTIME<<15) + (SAMPTIME<<18) + (SAMPTIME<<21) + (SAMPTIME<<24) + (SAMPTIME<<27) ; + + ADC->CCR = 0 ; //ADC_CCR_ADCPRE_0 ; // Clock div 2 + + DMA2_Stream0->CR = DMA_SxCR_PL | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC; + DMA2_Stream0->PAR = CONVERT_PTR_UINT(&ADC1->DR); + DMA2_Stream0->M0AR = CONVERT_PTR_UINT(Analog_values); + DMA2_Stream0->NDTR = NUMBER_ANALOG_ADC1; + DMA2_Stream0->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ; +} + +void adcRead() +{ + DMA2_Stream0->CR &= ~DMA_SxCR_EN ; // Disable DMA + ADC1->SR &= ~(uint32_t) ( ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR ) ; + DMA2->LIFCR = DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 |DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0 ; // Write ones to clear bits + DMA2_Stream0->CR |= DMA_SxCR_EN ; // Enable DMA + ADC1->CR2 |= (uint32_t)ADC_CR2_SWSTART ; + for (unsigned int i=0; i<10000; i++) { + if (DMA2->LISR & DMA_LISR_TCIF0) { + break; + } + } + DMA2_Stream0->CR &= ~DMA_SxCR_EN ; // Disable DMA +} + +// TODO +void adcStop() +{ +} + +uint16_t getAnalogValue(uint32_t value) +{ + // return Analog_values[ana_mapping[value]]; + return 0; // Analog_values[value]; +} diff --git a/radio/src/targets/Horus/audio_driver.cpp b/radio/src/targets/Horus/audio_driver.cpp new file mode 100644 index 000000000..1bb7b39ac --- /dev/null +++ b/radio/src/targets/Horus/audio_driver.cpp @@ -0,0 +1,172 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +const int8_t volumeScale[VOLUME_LEVEL_MAX+1] = +{ + 0, 1, 2, 3, 5, 9, 13, 17, 22, 27, 33, 40, + 64, 82, 96, 105, 112, 117, 120, 122, 124, 125, 126, 127 +}; + +#if !defined(SIMU) +bool dacIdle = true; + +void setSampleRate(uint32_t frequency) +{ + register uint32_t timer = (PERI1_FREQUENCY * TIMER_MULT_APB1) / frequency - 1 ; // MCK/8 and 100 000 Hz + + TIM6->CR1 &= ~TIM_CR1_CEN ; + TIM6->CNT = 0 ; + TIM6->ARR = limit(2, timer, 65535) ; + TIM6->CR1 |= TIM_CR1_CEN ; +} + +// Start TIMER6 at 100000Hz, used for DAC trigger +void dacTimerInit() +{ + // Now for timer 6 + RCC->APB1ENR |= RCC_APB1ENR_TIM6EN ; // Enable clock + + TIM6->PSC = 0 ; // Max speed + TIM6->ARR = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 100000 - 1 ; // 10 uS, 100 kHz + TIM6->CR2 = 0 ; + TIM6->CR2 = 0x20 ; + TIM6->CR1 = TIM_CR1_CEN ; +} + +// Configure DAC0 (or DAC1 for REVA) +// Not sure why PB14 has not be allocated to the DAC, although it is an EXTRA function +// So maybe it is automatically done +void dacInit() +{ + dacTimerInit(); + + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN ; // Enable portA clock + configure_pins( GPIO_Pin_4, PIN_ANALOG | PIN_PORTA ) ; + RCC->APB1ENR |= RCC_APB1ENR_DACEN ; // Enable clock + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN ; // Enable DMA1 clock + + // Chan 7, 16-bit wide, Medium priority, memory increments + DMA1_Stream5->CR &= ~DMA_SxCR_EN ; // Disable DMA + DMA1->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5 ; // Write ones to clear bits + DMA1_Stream5->CR = DMA_SxCR_CHSEL_0 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_2 | DMA_SxCR_PL_0 | + DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_CIRC ; + DMA1_Stream5->PAR = CONVERT_PTR_UINT(&DAC->DHR12R1); + // DMA1_Stream5->M0AR = CONVERT_PTR_UINT(Sine_values); + DMA1_Stream5->FCR = 0x05 ; //DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ; + // DMA1_Stream5->NDTR = 100 ; + + DAC->DHR12R1 = 2010 ; + DAC->SR = DAC_SR_DMAUDR1 ; // Write 1 to clear flag + DAC->CR = DAC_CR_TEN1 | DAC_CR_EN1 ; // Enable DAC + NVIC_EnableIRQ(TIM6_DAC_IRQn); // TODO needed? + NVIC_SetPriority(TIM6_DAC_IRQn, 7); + NVIC_EnableIRQ(DMA1_Stream5_IRQn); + NVIC_SetPriority(DMA1_Stream5_IRQn, 7); +} + +bool dacQueue(AudioBuffer *buffer) +{ + if (dacIdle) { + dacIdle = false; + DMA1_Stream5->CR &= ~DMA_SxCR_EN ; // Disable DMA channel + DMA1->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5 ; // Write ones to clear bits + DMA1_Stream5->M0AR = CONVERT_PTR_UINT(buffer->data); + DMA1_Stream5->NDTR = buffer->size; + DMA1_Stream5->CR |= DMA_SxCR_EN | DMA_SxCR_TCIE ; // Enable DMA channel and interrupt + DAC->SR = DAC_SR_DMAUDR1 ; // Write 1 to clear flag + DAC->CR |= DAC_CR_EN1 | DAC_CR_DMAEN1 ; // Enable DAC + return true; + } + else { + return false; + } +} + +void dacStart() +{ + DMA1->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5 ; // Write ones to clear bits + DMA1_Stream5->CR |= DMA_SxCR_CIRC | DMA_SxCR_EN ; // Enable DMA channel + DAC->SR = DAC_SR_DMAUDR1 ; // Write 1 to clear flag + DAC->CR |= DAC_CR_EN1 | DAC_CR_DMAEN1 ; // Enable DAC +} + +void dacStop() +{ + DMA1_Stream5->CR &= ~DMA_SxCR_CIRC ; +} + +// Sound routines +void audioInit() +{ + dacInit(); +} + +void audioEnd() +{ + DAC->CR = 0 ; + TIM6->CR1 = 0 ; + // Also need to turn off any possible interrupts + NVIC_DisableIRQ(TIM6_DAC_IRQn) ; + NVIC_DisableIRQ(DMA1_Stream5_IRQn) ; +} + +extern "C" void TIM6_DAC_IRQHandler() +{ + DAC->CR &= ~DAC_CR_DMAEN1 ; // Stop DMA requests + DAC->SR = DAC_SR_DMAUDR1 ; // Write 1 to clear flag +} + +extern "C" void DMA1_Stream5_IRQHandler() +{ + DMA1_Stream5->CR &= ~DMA_SxCR_TCIE ; // Stop interrupt + DMA1->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5 ; // Write ones to clear flags + DMA1_Stream5->CR &= ~DMA_SxCR_EN ; // Disable DMA channel + + AudioBuffer *nextBuffer = audioQueue.getNextFilledBuffer(); + if (nextBuffer) { + DMA1_Stream5->M0AR = CONVERT_PTR_UINT(nextBuffer->data); + DMA1_Stream5->NDTR = nextBuffer->size; + DMA1->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5 ; // Write ones to clear bits + DMA1_Stream5->CR |= DMA_SxCR_EN | DMA_SxCR_TCIE ; // Enable DMA channel + DAC->SR = DAC_SR_DMAUDR1; // Write 1 to clear flag + } + else { + dacIdle = true; + } +} +#endif // #if !defined(SIMU) diff --git a/radio/src/targets/Horus/board_horus.cpp b/radio/src/targets/Horus/board_horus.cpp new file mode 100644 index 000000000..0e0fe3a35 --- /dev/null +++ b/radio/src/targets/Horus/board_horus.cpp @@ -0,0 +1,264 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#if defined(__cplusplus) && !defined(SIMU) +extern "C" { +#endif +#include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_dcd_int.h" +#include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_bsp.h" +#if defined(__cplusplus) && !defined(SIMU) +} +#endif + +void watchdogInit(unsigned int duration) +{ + IWDG->KR = 0x5555 ; // Unlock registers + IWDG->PR = 3 ; // Divide by 32 => 1kHz clock + IWDG->KR = 0x5555 ; // Unlock registers + IWDG->RLR = duration ; // 1.5 seconds nominal + IWDG->KR = 0xAAAA ; // reload + IWDG->KR = 0xCCCC ; // start +} + +// Start TIMER7 at 2000000Hz +void init2MhzTimer() +{ + // Now for timer 7 + RCC->APB1ENR |= RCC_APB1ENR_TIM7EN ; // Enable clock + + TIM7->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 2000000 - 1 ; // 0.5 uS, 2 MHz + TIM7->ARR = 65535; + TIM7->CR2 = 0; + TIM7->CR1 = TIM_CR1_CEN; +} + +// Starts TIMER at 200Hz, 5mS period +void init5msTimer() +{ + // Timer14 + RCC->APB1ENR |= RCC_APB1ENR_TIM14EN ; // Enable clock + TIM14->ARR = 4999 ; // 5mS + TIM14->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 1000000 - 1 ; // 1uS from 30MHz + TIM14->CCER = 0 ; + TIM14->CCMR1 = 0 ; + TIM14->EGR = 0 ; + TIM14->CR1 = 5 ; + TIM14->DIER |= 1 ; + NVIC_EnableIRQ(TIM8_TRG_COM_TIM14_IRQn) ; + NVIC_SetPriority(TIM8_TRG_COM_TIM14_IRQn, 7); +} + +void stop5msTimer( void ) +{ + TIM14->CR1 = 0 ; // stop timer + NVIC_DisableIRQ(TIM8_TRG_COM_TIM14_IRQn) ; + RCC->APB1ENR &= ~RCC_APB1ENR_TIM14EN ; // Disable clock +} + +// TODO use the same than board_sky9x.cpp +void interrupt5ms() +{ + static uint32_t pre_scale ; // Used to get 10 Hz counter + + AUDIO_HEARTBEAT(); + +#if defined(HAPTIC) + HAPTIC_HEARTBEAT(); +#endif + + if ( ++pre_scale >= 2 ) { + pre_scale = 0 ; + per10ms(); + } + + checkRotaryEncoder(); +} + +#if !defined(SIMU) +extern "C" void TIM8_TRG_COM_TIM14_IRQHandler() +{ + TIM14->SR &= ~TIM_SR_UIF ; + interrupt5ms() ; +} + +void pinCheck(GPIO_TypeDef * gpio, uint32_t pin, uint32_t RCC_AHB1Periph) +{ + GPIO_InitTypeDef GPIO_InitStruct; + + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph, ENABLE); + + GPIO_InitStruct.GPIO_Pin = pin; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(gpio, &GPIO_InitStruct); + + for (int i=0; i<10; i++) { + GPIO_SetBits(gpio, pin); + delay_01us(10); + GPIO_ResetBits(gpio, pin); + delay_01us(10); + } +} + +void boardInit() +{ + pwrInit(); + ledInit(); + delaysInit(); + + ledRed(); + + + if (0) { + // pinCheck(SERIAL_GPIO, SERIAL_GPIO_PIN_TX, SERIAL_RCC_AHB1Periph_GPIO); + // pinCheck(EEPROM_GPIO, EEPROM_GPIO_PIN_SCK, RCC_AHB1Periph_GPIOB); + // pinCheck(EEPROM_GPIO, EEPROM_GPIO_PIN_MISO, RCC_AHB1Periph_GPIOB); + // pinCheck(EEPROM_GPIO, EEPROM_GPIO_PIN_MOSI, RCC_AHB1Periph_GPIOB); + } + + serial2Init(0, 0); // default serial mode (None if DEBUG not defined) + + ledBlue(); + + __enable_irq(); + + TRACE("Horus started :)"); + + keysInit(); + // adcInit(); + lcdInit(); + audioInit(); + i2cInit(); + init2MhzTimer(); + init5msTimer(); + usbInit(); + hapticInit(); + + // rotencInit(); + // bt_open(); + +#if defined(DEBUG) + DBGMCU_APB1PeriphConfig(DBGMCU_IWDG_STOP|DBGMCU_TIM1_STOP|DBGMCU_TIM2_STOP|DBGMCU_TIM3_STOP|DBGMCU_TIM4_STOP|DBGMCU_TIM5_STOP|DBGMCU_TIM6_STOP|DBGMCU_TIM7_STOP|DBGMCU_TIM8_STOP|DBGMCU_TIM9_STOP|DBGMCU_TIM10_STOP|DBGMCU_TIM11_STOP|DBGMCU_TIM12_STOP|DBGMCU_TIM13_STOP|DBGMCU_TIM14_STOP, ENABLE); +#endif +} +#endif + +#if defined(USB_JOYSTICK) && !defined(SIMU) +extern USB_OTG_CORE_HANDLE USB_OTG_dev; + +/* + Prepare and send new USB data packet + + The format of HID_Buffer is defined by + USB endpoint description can be found in + file usb_hid_joystick.c, variable HID_JOYSTICK_ReportDesc +*/ +void usbJoystickUpdate(void) +{ + static uint8_t HID_Buffer[HID_IN_PACKET]; + + //buttons + HID_Buffer[0] = 0; //buttons + for (int i = 0; i < 8; ++i) { + if ( channelOutputs[i+8] > 0 ) { + HID_Buffer[0] |= (1 << i); + } + } + + //analog values + //uint8_t * p = HID_Buffer + 1; + for (int i = 0; i < 8; ++i) { + int16_t value = channelOutputs[i] / 8; + if ( value > 127 ) value = 127; + else if ( value < -127 ) value = -127; + HID_Buffer[i+1] = static_cast(value); + } + + USBD_HID_SendReport (&USB_OTG_dev, HID_Buffer, HID_IN_PACKET ); +} + +#endif //#if defined(USB_JOYSTICK) && defined(PCBTARANIS) && !defined(SIMU) + + +uint8_t currentTrainerMode = 0xff; + +void checkTrainerSettings() +{ + uint8_t requiredTrainerMode = g_model.trainerMode; + if (requiredTrainerMode != currentTrainerMode) { + switch (currentTrainerMode) { + case TRAINER_MODE_MASTER: + stop_trainer_capture(); + break; + case TRAINER_MODE_SLAVE: + stop_trainer_ppm(); + break; + } + + currentTrainerMode = requiredTrainerMode; + switch (requiredTrainerMode) { + case TRAINER_MODE_SLAVE: + init_trainer_ppm(); + break; + default: + // master is default + init_trainer_capture(); + break; + } + } +} + +// TODO +void eeDirty(uint8_t msk) { } +uint8_t s_eeDirtyMsk; +tmr10ms_t s_eeDirtyTime10ms; +void eeCheck(bool) { } +ModelHeader modelHeaders[MAX_MODELS]; +void eeLoadModelHeaders() { } +void eeLoadModelHeader(uint8_t id, ModelHeader *header) { } +bool eeCopyModel(uint8_t dst, uint8_t src) { } +void eeSwapModels(uint8_t id1, uint8_t id2) { } +void eeDeleteModel(uint8_t idx) { } +uint8_t eeFindEmptyModel(uint8_t id, bool down) { return 0; }; +void eeLoadModel(uint8_t id) { } +bool eeModelExists(uint8_t id) { return true; } +const pm_char * eeBackupModel(uint8_t i_fileSrc) { return NULL; } +const pm_char * eeRestoreModel(uint8_t i_fileDst, char *model_name) { return NULL; } +void selectModel(uint8_t sub) { } diff --git a/radio/src/targets/Horus/board_horus.h b/radio/src/targets/Horus/board_horus.h new file mode 100644 index 000000000..150b4a22f --- /dev/null +++ b/radio/src/targets/Horus/board_horus.h @@ -0,0 +1,310 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#ifndef _BOARD_FLAMENCO_H_ +#define _BOARD_FLAMENCO_H_ + +#include "stddef.h" + +#if defined(__cplusplus) && !defined(SIMU) +extern "C" { +#endif + +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_rcc.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_gpio.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_spi.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_i2c.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_rtc.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_pwr.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_dma.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_usart.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_flash.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_sdio.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_dbgmcu.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_ltdc.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_fmc.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_tim.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_dma2d.h" +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/misc.h" + +#if !defined(SIMU) + #include "usbd_cdc_core.h" + #include "usbd_msc_core.h" + #include "usbd_hid_core.h" + #include "usbd_usr.h" + #include "usbd_desc.h" + #include "usb_conf.h" + #include "usbd_conf.h" +#endif + +#include "hal.h" + +#if defined(__cplusplus) && !defined(SIMU) +} +#endif + +#define FLASHSIZE 0x80000 +#define BOOTLOADER_SIZE 0x8000 +#define FIRMWARE_ADDRESS 0x08000000 + +#define PERI1_FREQUENCY 45000000 +#define PERI2_FREQUENCY 90000000 +#define TIMER_MULT_APB1 4 +#define TIMER_MULT_APB2 2 + +#define PIN_MODE_MASK 0x0003 +#define PIN_INPUT 0x0000 +#define PIN_OUTPUT 0x0001 +#define PIN_PERIPHERAL 0x0002 +#define PIN_ANALOG 0x0003 +#define PIN_PULL_MASK 0x000C +#define PIN_PULLUP 0x0004 +#define PIN_NO_PULLUP 0x0000 +#define PIN_PULLDOWN 0x0008 +#define PIN_NO_PULLDOWN 0x0000 +#define PIN_PERI_MASK 0x00F0 +#define PIN_PUSHPULL 0x0000 +#define PIN_ODRAIN 0x8000 +#define PIN_PORT_MASK 0x0700 +#define PIN_SPEED_MASK 0x6000 +#define PIN_PORTA 0x0000 +#define PIN_PORTB 0x0100 +#define PIN_PORTC 0x0200 +#define PIN_PORTD 0x0300 +#define PIN_PORTE 0x0400 +#define PIN_PORTF 0x0500 +#define PIN_PER_1 0x0010 +#define PIN_PER_2 0x0020 +#define PIN_PER_3 0x0030 +#define PIN_PER_5 0x0050 +#define PIN_PER_6 0x0060 +#define PIN_PER_8 0x0080 +#define PIN_OS25 0x2000 +#define PIN_OS50 0x4000 +#define PIN_OS100 0x6000 + +void configure_pins( uint32_t pins, uint16_t config ); + +#define strcpy_P strcpy +#define strcat_P strcat + +extern uint16_t sessionTimer; + +#define SLAVE_MODE() (g_model.trainerMode == TRAINER_MODE_SLAVE) + +#ifdef __cplusplus +extern "C" { +#endif +void delaysInit(void); +void delay_01us(uint16_t nb); +#ifdef __cplusplus +} +#endif + +// SD driver +#if !defined(SIMU) || defined(SIMU_DISKIO) + uint32_t sdIsHC(void); + uint32_t sdGetSpeed(void); + #define SD_IS_HC() (sdIsHC()) + #define SD_GET_SPEED() (sdGetSpeed()) + #define SD_GET_FREE_BLOCKNR() (sdGetFreeSectors()) +#else + #define SD_IS_HC() (0) + #define SD_GET_SPEED() (0) +#endif + +#if defined(SIMU) + #define sdInit() + #define sdDone() +#else + void sdInit(void); + void sdDone(void); + #define sdPoll10ms() + #define sdMountPoll() + uint32_t sdMounted(void); + #define SD_CARD_PRESENT() (~SD_PRESENT_GPIO->IDR & SD_PRESENT_GPIO_PIN) +#endif + +// Flash Write driver +#define FLASH_PAGESIZE 256 +void unlockFlash(void); +void lockFlash(void); +void writeFlash(uint32_t * address, uint32_t * buffer); +uint32_t isFirmwareStart(const void * buffer); +uint32_t isBootloaderStart(const void * buffer); + +// SDRAM driver +#define SDRAM_BANK_ADDR ((uint32_t)0xD0000000) +void SDRAM_Init(void); +void SDRAM_WriteBuffer(uint32_t* pBuffer, uint32_t uwWriteAddress, uint32_t uwBufferSize); +void SDRAM_ReadBuffer(uint32_t* pBuffer, uint32_t uwReadAddress, uint32_t uwBufferSize); + +// Pulses driver +#define EXTERNAL_MODULE_ON() GPIO_SetBits(EXTMODULE_GPIO_PWR, EXTMODULE_GPIO_PIN_PWR) +#define EXTERNAL_MODULE_OFF() GPIO_ResetBits(EXTMODULE_GPIO_PWR, EXTMODULE_GPIO_PIN_PWR) +void init_no_pulses(uint32_t port); +void disable_no_pulses(uint32_t port); +void init_ppm( uint32_t module_index ); +void disable_ppm( uint32_t module_index ); +void set_external_ppm_parameters(uint32_t idleTime, uint32_t delay, uint32_t positive); +void init_pxx( uint32_t module_index ); +void disable_pxx( uint32_t module_index ); +void init_dsm2( uint32_t module_index ); +void disable_dsm2( uint32_t module_index ); + +// Trainer driver +void init_trainer_ppm(void); +void stop_trainer_ppm(void); +void init_trainer_capture(void); +void stop_trainer_capture(void); +void init_cppm_on_heartbeat_capture(void); +void stop_cppm_on_heartbeat_capture(void); +void init_sbus_on_heartbeat_capture(void); +void stop_sbus_on_heartbeat_capture(void); +void set_trainer_ppm_parameters(uint32_t idleTime, uint32_t delay, uint32_t positive); + +// Keys driver +void keysInit(void); +uint32_t readKeys(void); +uint32_t readTrims(void); +#define TRIMS_PRESSED() (readTrims()) +#define KEYS_PRESSED() (readKeys()) +#define DBLKEYS_PRESSED_RGT_LFT(i) ((in & ((2<KR = 0xAAAA +#define WAS_RESET_BY_WATCHDOG() (RCC->CSR & (RCC_CSR_WDGRSTF | RCC_CSR_WWDGRSTF)) +#define WAS_RESET_BY_SOFTWARE() (RCC->CSR & RCC_CSR_SFTRSTF) +#endif + +// ADC driver +void adcInit(void); +void adcRead(void); +inline uint16_t getAnalogValue(uint32_t value); + +#if defined(REV3) + #define BATT_SCALE 120 +#else + #define BATT_SCALE 150 +#endif + +#if defined(__cplusplus) && !defined(SIMU) +extern "C" { +#endif + +// Power driver +void pwrInit(void); +uint32_t pwrCheck(void); +void pwrOn(void); +void pwrOff(void); +uint32_t pwrPressed(void); +uint32_t pwrPressedDuration(void); +#define UNEXPECTED_SHUTDOWN() (g_eeGeneral.unexpectedShutdown) + +// Led driver +void ledInit(void); +void ledOff(void); +void ledRed(void); +void ledBlue(void); + +// LCD driver + +// Backlight driver +#define setBacklight(xx) +#define backlightEnable() +#define backlightDisable() +#define isBacklightEnable() true + +// USB driver +int usbPlugged(void); +void usbInit(void); +void usbDeInit(void); +void usbSerialPutc(uint8_t c); + +#if defined(__cplusplus) && !defined(SIMU) +} +#endif + +// Audio driver +void audioInit(void); +void audioEnd(void); +void dacStart(void); +void dacStop(void); +void setSampleRate(uint32_t frequency); + +// I2C driver +void i2cInit(void); +#define VOLUME_LEVEL_MAX 23 +#define VOLUME_LEVEL_DEF 12 +extern const int8_t volumeScale[]; +void setVolume(uint8_t volume); + +// Telemetry driver +void telemetryPortInit(uint32_t baudrate); +void sportSendBuffer(uint8_t *buffer, uint32_t count); + +// Haptic driver +void hapticInit(void); +void hapticOff(void); +#define HAPTIC_OFF() hapticOff() +void hapticOn(uint32_t pwmPercent); + +// Second serial port driver +#define DEBUG_BAUDRATE 115200 +void serial2Init(unsigned int mode, unsigned int protocol); +void serial2Putc(char c); +#define serial2TelemetryInit(protocol) serial2Init(UART_MODE_TELEMETRY, protocol) +void serial2SbusInit(void); +void serial2Stop(void); + +#define USART_FLAG_ERRORS (USART_FLAG_ORE | USART_FLAG_NE | USART_FLAG_FE | USART_FLAG_PE) + +#if defined(USB_JOYSTICK) && !defined(SIMU) +void usbJoystickUpdate(void); +#endif + +void checkTrainerSettings(void); + +#endif diff --git a/radio/src/targets/Horus/delays.c b/radio/src/targets/Horus/delays.c new file mode 100644 index 000000000..e0eb013c8 --- /dev/null +++ b/radio/src/targets/Horus/delays.c @@ -0,0 +1,57 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "board_horus.h" + +void delaysInit(void) +{ + // Timer13 + RCC->APB1ENR |= RCC_APB1ENR_TIM13EN; // Enable clock + TIM13->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 10000000 - 1; // 0.1uS + TIM13->CCER = 0; + TIM13->CCMR1 = 0; + TIM13->CR1 = 0x02; + TIM13->DIER = 0; +} + +void delay_01us(uint16_t nb) +{ + TIM13->EGR = 1; + TIM13->CNT = 0; + TIM13->CR1 = 0x03; + while (TIM13->CNT < nb); + TIM13->CR1 = 0x02; +} diff --git a/radio/src/targets/Horus/diskio.cpp b/radio/src/targets/Horus/diskio.cpp new file mode 100644 index 000000000..3da1d09c4 --- /dev/null +++ b/radio/src/targets/Horus/diskio.cpp @@ -0,0 +1,296 @@ +/*-----------------------------------------------------------------------*/ +/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2007 */ +/*-----------------------------------------------------------------------*/ +/* This is a stub disk I/O module that acts as front end of the existing */ +/* disk I/O modules and attach it to FatFs module with common interface. */ +/*-----------------------------------------------------------------------*/ + +#include "diskio.h" +#include // memcpy +#include "stm32f4xx.h" +#include "sdio_sd.h" + +#define BLOCK_SIZE 512 /* Block Size in Bytes */ + +/*-----------------------------------------------------------------------*/ +/* Lock / unlock functions */ +/*-----------------------------------------------------------------------*/ +#if !defined(BOOT) +static OS_MutexID ioMutex; +volatile int mutexCheck = 0; + +int ff_cre_syncobj (BYTE vol, _SYNC_t *mutex) +{ + *mutex = ioMutex; + return 1; +} + +int ff_req_grant (_SYNC_t mutex) +{ + return CoEnterMutexSection(mutex) == E_OK; +} + +void ff_rel_grant (_SYNC_t mutex) +{ + CoLeaveMutexSection(mutex); +} + +int ff_del_syncobj (_SYNC_t mutex) +{ + return 1; +} +#endif + + +/*-----------------------------------------------------------------------*/ +/* Inidialize a Drive */ + +DSTATUS disk_initialize ( + BYTE drv /* Physical drive nmuber (0..) */ +) +{ + DSTATUS stat = 0; + + /* Supports only single drive */ + if (drv) + { + stat |= STA_NOINIT; + } + + /*-------------------------- SD Init ----------------------------- */ + if (SD_Init() != SD_OK) + { + TRACE("SD_Init() failed"); + stat |= STA_NOINIT; + } + + return(stat); +} + + + +/*-----------------------------------------------------------------------*/ +/* Return Disk Status */ + +DSTATUS disk_status ( + BYTE drv /* Physical drive nmuber (0..) */ +) +{ + DSTATUS stat = 0; + + if (SD_Detect() != SD_PRESENT) + stat |= STA_NODISK; + + // STA_NOTINIT - Subsystem not initailized + // STA_PROTECTED - Write protected, MMC/SD switch if available + + return(stat); +} + + + +/*-----------------------------------------------------------------------*/ +/* Read Sector(s) */ + +DRESULT disk_read ( + BYTE drv, /* Physical drive nmuber (0..) */ + BYTE *buff, /* Data buffer to store read data */ + DWORD sector, /* Sector address (LBA) */ + UINT count /* Number of sectors to read (1..255) */ +) +{ + SD_Error Status; + + // TRACE("disk_read %d %p %10d %d\n",drv,buff,sector,count); + + if (SD_Detect() != SD_PRESENT) + return(RES_NOTRDY); + + if ((DWORD)buff & 3) // DMA Alignment failure, do single up to aligned buffer + { + DRESULT res = RES_OK; + DWORD scratch[BLOCK_SIZE / 4]; // Alignment assured, you'll need a sufficiently big stack + + while(count--) + { + res = disk_read(drv, (BYTE *)scratch, sector++, 1); + + if (res != RES_OK) { + TRACE("disk_read() status=%d", res); + break; + } + + memcpy(buff, scratch, BLOCK_SIZE); + + buff += BLOCK_SIZE; + } + + return(res); + } + + Status = SD_ReadMultiBlocksFIXED(buff, sector, BLOCK_SIZE, count); // 4GB Compliant + + if (Status == SD_OK) + { + SDTransferState State; + + Status = SD_WaitReadOperation(); // Check if the Transfer is finished + + while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) + + if ((State == SD_TRANSFER_ERROR) || (Status != SD_OK)) { + TRACE("SD_ReadMultiBlocksFIXED() wait error (State=%d Status=%d)", State, Status); + return(RES_ERROR); + } + else + return(RES_OK); + } + else { + TRACE("SD_ReadMultiBlocksFIXED() error (Status=%d)", Status); + return(RES_ERROR); + } +} + +/*-----------------------------------------------------------------------*/ +/* Write Sector(s) */ + +#if _READONLY == 0 +DRESULT disk_write ( + BYTE drv, /* Physical drive nmuber (0..) */ + const BYTE *buff, /* Data to be written */ + DWORD sector, /* Sector address (LBA) */ + UINT count /* Number of sectors to write (1..255) */ +) +{ + SD_Error Status; + + // TRACE("disk_write %d %p %10d %d\n",drv,buff,sector,count); + + if (SD_Detect() != SD_PRESENT) + return(RES_NOTRDY); + + if ((DWORD)buff & 3) // DMA Alignment failure, do single up to aligned buffer + { + DRESULT res = RES_OK; + DWORD scratch[BLOCK_SIZE / 4]; // Alignment assured, you'll need a sufficiently big stack + + while(count--) + { + memcpy(scratch, buff, BLOCK_SIZE); + + res = disk_write(drv, (BYTE *)scratch, sector++, 1); + + if (res != RES_OK) + break; + + buff += BLOCK_SIZE; + } + + return(res); + } + + Status = SD_WriteMultiBlocksFIXED((uint8_t *)buff, sector, BLOCK_SIZE, count); // 4GB Compliant + + if (Status == SD_OK) + { + SDTransferState State; + + Status = SD_WaitWriteOperation(); // Check if the Transfer is finished + + while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) + + if ((State == SD_TRANSFER_ERROR) || (Status != SD_OK)) + return(RES_ERROR); + else + return(RES_OK); + } + else + return(RES_ERROR); +} +#endif /* _READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Miscellaneous Functions */ + +DRESULT disk_ioctl ( + BYTE drv, /* Physical drive nmuber (0..) */ + BYTE ctrl, /* Control code */ + void *buff /* Buffer to send/receive control data */ +) +{ + return RES_OK; +} + +// TODO everything here should not be in the driver layer ... + +FATFS g_FATFS_Obj; +#if defined(SPORT_FILE_LOG) +FIL g_telemetryFile = {0}; +#endif + +#if defined(BOOT) +void sdInit(void) +{ + if (f_mount(&g_FATFS_Obj, "", 1) == FR_OK) { + f_chdir("/"); + } +} +#else +// TODO shouldn't be there! +void sdInit(void) +{ + ioMutex = CoCreateMutex(); + if (ioMutex >= CFG_MAX_MUTEX ) { + // sd error + return; + } + + TRACE("AVANT F_MOUNT"); + if (f_mount(&g_FATFS_Obj, "", 1) == FR_OK) { + TRACE("APRES F_MOUNT"); + // call sdGetFreeSectors() now because f_getfree() takes a long time first time it's called + sdGetFreeSectors(); + + referenceSystemAudioFiles(); + +#if defined(SPORT_FILE_LOG) + f_open(&g_telemetryFile, LOGS_PATH "/sport.log", FA_OPEN_ALWAYS | FA_WRITE); + if (f_size(&g_telemetryFile) > 0) { + f_lseek(&g_telemetryFile, f_size(&g_telemetryFile)); // append + } +#endif + } + else { + TRACE("f_mount() failed"); + } +} + +void sdDone() +{ + if (sdMounted()) { + audioQueue.stopSD(); +#if defined(SPORT_FILE_LOG) + f_close(&g_telemetryFile); +#endif + f_mount(NULL, "", 0); // unmount SD + } +} +#endif + +uint32_t sdMounted() +{ + return g_FATFS_Obj.fs_type != 0; +} + +uint32_t sdIsHC() +{ + return true; // TODO (CardType & CT_BLOCK); +} + +uint32_t sdGetSpeed() +{ + return 330000; +} diff --git a/radio/src/targets/Horus/hal.h b/radio/src/targets/Horus/hal.h new file mode 100644 index 000000000..30b5a8f7a --- /dev/null +++ b/radio/src/targets/Horus/hal.h @@ -0,0 +1,260 @@ +#ifndef _HAL_ +#define _HAL_ + +// Keys +#define KEYS_RCC_AHB1Periph_GPIO (RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOH | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_GPIOJ) +#define KEYS_GPIO_REG_MENU GPIOC->IDR +#define KEYS_GPIO_PIN_MENU GPIO_Pin_13 // PC.13 +#define KEYS_GPIO_REG_EXIT GPIOI->IDR +#define KEYS_GPIO_PIN_EXIT GPIO_Pin_8 // PI.08 +#define KEYS_GPIO_REG_LEFT GPIOI->IDR +#define KEYS_GPIO_PIN_LEFT GPIO_Pin_7 // PI.07 +#define KEYS_GPIO_REG_ENTER GPIOC->IDR +#define KEYS_GPIO_PIN_ENTER GPIO_Pin_1 // PC.01 +#define KEYS_GPIO_REG_UP GPIOG->IDR +#define KEYS_GPIO_PIN_UP GPIO_Pin_13 // PG.13 +#define KEYS_GPIO_REG_DOWN GPIOI->IDR +#define KEYS_GPIO_PIN_DOWN GPIO_Pin_6 // PI.06 +#define KEYS_GPIO_REG_RIGHT GPIOC->IDR +#define KEYS_GPIO_PIN_RIGHT GPIO_Pin_4 // PC.04 + +// Rotary Encoder +#define ENC_GPIO GPIOH +#define ENC_GPIO_PIN_A GPIO_Pin_11 // PH.11 +#define ENC_GPIO_PIN_B GPIO_Pin_10 // PH.10 + +// Switches +#define SWITCHES_GPIO_REG_A_H GPIOI->IDR +#define SWITCHES_GPIO_PIN_A_H GPIO_Pin_15 // PI.15 +#define SWITCHES_GPIO_REG_A_L GPIOH->IDR +#define SWITCHES_GPIO_PIN_A_L GPIO_Pin_9 // PH.09 +#define SWITCHES_GPIO_REG_B_H GPIOH->IDR +#define SWITCHES_GPIO_PIN_B_H GPIO_Pin_12 // PH.12 +#define SWITCHES_GPIO_REG_B_L GPIOB->IDR +#define SWITCHES_GPIO_PIN_B_L GPIO_Pin_12 // PB.12 +#define SWITCHES_GPIO_REG_C_H GPIOB->IDR +#define SWITCHES_GPIO_PIN_C_H GPIO_Pin_15 // PB.15 +#define SWITCHES_GPIO_REG_C_L GPIOD->IDR +#define SWITCHES_GPIO_PIN_C_L GPIO_Pin_11 // PD.11 +#define SWITCHES_GPIO_REG_D_H GPIOJ->IDR +#define SWITCHES_GPIO_PIN_D_H GPIO_Pin_7 // PJ.07 +#define SWITCHES_GPIO_REG_D_L GPIOG->IDR +#define SWITCHES_GPIO_PIN_D_L GPIO_Pin_2 // PG.02 +#define SWITCHES_GPIO_REG_E_H GPIOH->IDR +#define SWITCHES_GPIO_PIN_E_H GPIO_Pin_4 // PH.04 +#define SWITCHES_GPIO_REG_E_L GPIOE->IDR +#define SWITCHES_GPIO_PIN_E_L GPIO_Pin_3 // PE.03 +#define SWITCHES_GPIO_REG_F GPIOH->IDR +#define SWITCHES_GPIO_PIN_F GPIO_Pin_3 // PH.03 +#define SWITCHES_GPIO_REG_G_H GPIOG->IDR +#define SWITCHES_GPIO_PIN_G_H GPIO_Pin_3 // PG.03 +#define SWITCHES_GPIO_REG_G_L GPIOG->IDR +#define SWITCHES_GPIO_PIN_G_L GPIO_Pin_6 // PG.06 +#define SWITCHES_GPIO_REG_H GPIOG->IDR +#define SWITCHES_GPIO_PIN_H GPIO_Pin_7 // PG.07 + +// Trims +#define TRIMS_GPIO_REG_LHL GPIOC->IDR +#define TRIMS_GPIO_PIN_LHL GPIO_Pin_0 // PC.00 +#define TRIMS_GPIO_REG_LHR GPIOI->IDR +#define TRIMS_GPIO_PIN_LHR GPIO_Pin_4 // PI.04 +#define TRIMS_GPIO_REG_LVD GPIOG->IDR +#define TRIMS_GPIO_PIN_LVD GPIO_Pin_12 // PG.12 +#define TRIMS_GPIO_REG_LVU GPIOJ->IDR +#define TRIMS_GPIO_PIN_LVU GPIO_Pin_14 // PJ.14 +#define TRIMS_GPIO_REG_RVD GPIOJ->IDR +#define TRIMS_GPIO_PIN_RVD GPIO_Pin_13 // PJ.13 +#define TRIMS_GPIO_REG_RHL GPIOD->IDR +#define TRIMS_GPIO_PIN_RHL GPIO_Pin_3 // PD.03 +#define TRIMS_GPIO_REG_RVU GPIOJ->IDR +#define TRIMS_GPIO_PIN_RVU GPIO_Pin_12 // PJ.12 +#define TRIMS_GPIO_REG_RHR GPIOD->IDR +#define TRIMS_GPIO_PIN_RHR GPIO_Pin_7 // PD.07 +#define TRIMS_GPIO_REG_RSD GPIOJ->IDR +#define TRIMS_GPIO_PIN_RSD GPIO_Pin_8 // PJ.08 +#define TRIMS_GPIO_REG_RSU GPIOD->IDR +#define TRIMS_GPIO_PIN_RSU GPIO_Pin_13 // PD.13 +#define TRIMS_GPIO_REG_LSD GPIOB->IDR +#define TRIMS_GPIO_PIN_LSD GPIO_Pin_14 // PB.14 +#define TRIMS_GPIO_REG_LSU GPIOB->IDR +#define TRIMS_GPIO_PIN_LSU GPIO_Pin_13 // PB.13 + +// Index of all keys +#define KEYS_GPIOB_PINS (SWITCHES_GPIO_PIN_B_L | SWITCHES_GPIO_PIN_C_H | TRIMS_GPIO_PIN_LSD | TRIMS_GPIO_PIN_LSU) +#define KEYS_GPIOC_PINS (KEYS_GPIO_PIN_MENU | KEYS_GPIO_PIN_ENTER | KEYS_GPIO_PIN_RIGHT | TRIMS_GPIO_PIN_LHL) +#define KEYS_GPIOD_PINS (SWITCHES_GPIO_PIN_C_L | TRIMS_GPIO_PIN_RHL | TRIMS_GPIO_PIN_RHR | TRIMS_GPIO_PIN_RSU) +#define KEYS_GPIOE_PINS (SWITCHES_GPIO_PIN_E_L) +#define KEYS_GPIOG_PINS (KEYS_GPIO_PIN_UP | SWITCHES_GPIO_PIN_D_L | SWITCHES_GPIO_PIN_G_H | SWITCHES_GPIO_PIN_G_L | SWITCHES_GPIO_PIN_H | TRIMS_GPIO_PIN_LVD) +#define KEYS_GPIOH_PINS (SWITCHES_GPIO_PIN_A_L | SWITCHES_GPIO_PIN_B_H | SWITCHES_GPIO_PIN_E_H | SWITCHES_GPIO_PIN_F | ENC_GPIO_PIN_A | ENC_GPIO_PIN_B) +#define KEYS_GPIOI_PINS (KEYS_GPIO_PIN_EXIT | KEYS_GPIO_PIN_LEFT | KEYS_GPIO_PIN_DOWN | SWITCHES_GPIO_PIN_A_H | TRIMS_GPIO_PIN_LHR) +#define KEYS_GPIOJ_PINS (SWITCHES_GPIO_PIN_D_H | TRIMS_GPIO_PIN_LVU | TRIMS_GPIO_PIN_RVD | TRIMS_GPIO_PIN_RVU | TRIMS_GPIO_PIN_RSD) + +// ADC +#define ADC_RCC_AHB1Periph_GPIO (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC) +#define ADC_GPIO_STICKS GPIOA +#define ADC_GPIO_PIN_STICK_LV GPIO_Pin_0 // PA.00 +#define ADC_GPIO_PIN_STICK_LH GPIO_Pin_1 // PA.01 +#define ADC_GPIO_PIN_STICK_RV GPIO_Pin_2 // PA.02 +#define ADC_GPIO_PIN_STICK_RH GPIO_Pin_3 // PA.03 +#define ADC_GPIO_POT_L1 GPIOC +#define ADC_GPIO_PIN_POT_L1 GPIO_Pin_3 // PC.03 +#define ADC_GPIO_POT_R1 GPIOA +#define ADC_GPIO_PIN_POT_R1 GPIO_Pin_5 // PA.05 +#define ADC_GPIO_POT_L2 GPIOB +#define ADC_GPIO_PIN_POT_L2 GPIO_Pin_1 // PB.01 +#define ADC_GPIO_POT_R2 GPIOB +#define ADC_GPIO_PIN_POT_R2 GPIO_Pin_0 // PB.00 +#define ADC_GPIO_SWITCHES_L3 GPIOC +#define ADC_GPIO_SWITCHES_PIN_L3 GPIO_Pin_1 // PC.01 +#define ADC_GPIO_SWITCHES_R3 GPIOA +#define ADC_GPIO_SWITCHES_PIN_R3 GPIO_Pin_7 // PA.07 +#define ADC_GPIO_SWITCHES_L4 GPIOC +#define ADC_GPIO_SWITCHES_PIN_L4 GPIO_Pin_0 // PC.00 +#define ADC_GPIO_SWITCHES_R4 GPIOA +#define ADC_GPIO_SWITCHES_PIN_R4 GPIO_Pin_6 // PA.06 +#define ADC_GPIO_BATT GPIOC +#define ADC_GPIO_PIN_BATT GPIO_Pin_4 // PC.04 + +// Power +#define PWR_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOJ +#define PWR_GPIO GPIOJ +#define PWR_GPIO_PIN_ON GPIO_Pin_0 // PJ.00 +#define PWR_GPIO_PIN_SWITCH GPIO_Pin_1 // PJ.01 + +// Led +#define LED_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOI +#define LED_GPIO GPIOI +#define LED_GPIO_PIN GPIO_Pin_5 // PI.05 + +// Serial Port (DEBUG) +#define SERIAL_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOB +#define SERIAL_RCC_APB1Periph_USART RCC_APB1Periph_USART3 +#define SERIAL_GPIO GPIOB +#define SERIAL_GPIO_PIN_TX GPIO_Pin_10 // PB.10 +#define SERIAL_GPIO_PIN_RX GPIO_Pin_11 // PB.11 +#define SERIAL_GPIO_PinSource_TX GPIO_PinSource10 +#define SERIAL_GPIO_PinSource_RX GPIO_PinSource11 +#define SERIAL_GPIO_AF GPIO_AF_USART3 +#define SERIAL_USART USART3 +#define SERIAL_USART_IRQHandler USART3_IRQHandler +#define SERIAL_USART_IRQn USART3_IRQn + +// Telemetry +#define TELEMETRY_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOD +#define TELEMETRY_RCC_APB1Periph_USART RCC_APB1Periph_USART2 +#define TELEMETRY_GPIO_DIR GPIOD +#define TELEMETRY_GPIO_PIN_DIR GPIO_Pin_4 // PD.04 +#define TELEMETRY_GPIO GPIOD +#define TELEMETRY_GPIO_PIN_TX GPIO_Pin_5 // PD.05 +#define TELEMETRY_GPIO_PIN_RX GPIO_Pin_6 // PD.06 +#define TELEMETRY_GPIO_PinSource_TX GPIO_PinSource5 +#define TELEMETRY_GPIO_PinSource_RX GPIO_PinSource6 +#define TELEMETRY_GPIO_AF GPIO_AF_USART2 +#define TELEMETRY_USART USART2 +#define TELEMETRY_USART_IRQHandler USART2_IRQHandler +#define TELEMETRY_USART_IRQn USART2_IRQn + +// USB +#define USB_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOA +#define USB_GPIO GPIOA +#define USB_GPIO_PIN_VBUS GPIO_Pin_9 // PA.09 +#define USB_GPIO_PIN_DM GPIO_Pin_11 // PA.11 +#define USB_GPIO_PIN_DP GPIO_Pin_12 // PA.12 +#define USB_GPIO_PinSource_DM GPIO_PinSource11 +#define USB_GPIO_PinSource_DP GPIO_PinSource12 +#define USB_GPIO_AF GPIO_AF_OTG1_FS + +// LCD +#define LCD_RCC_AHB1Periph (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_GPIOJ | RCC_AHB1Periph_GPIOK | RCC_AHB1Periph_DMA2D) +#define LCD_RCC_APB2Periph (RCC_APB2Periph_TIM8 | RCC_APB2Periph_LTDC) +#define LCD_GPIO_NRST GPIOF +#define LCD_GPIO_PIN_NRST GPIO_Pin_10 // PF.10 +#define LCD_GPIO_BL GPIOA +#define LCD_GPIO_PIN_BL GPIO_Pin_5 // PA.05 +#define LCD_GPIO_PinSource_BL GPIO_PinSource5 +#define LCD_GPIO_AF_BL GPIO_AF_TIM8 +#define LTDC_IRQ_PRIO 4 +#define DMA_SCREEN_IRQ_PRIO 6 + +// SD +#define SD_RCC_AHB1Periph (RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_DMA2) +#define SD_PRESENT_GPIO GPIOC +#define SD_PRESENT_GPIO_PIN GPIO_Pin_5 // PC.05 +#define SD_SDIO_DMA_STREAM DMA2_Stream3 +#define SD_SDIO_DMA_CHANNEL DMA_Channel_4 +#define SD_SDIO_DMA_FLAG_FEIF DMA_FLAG_FEIF3 +#define SD_SDIO_DMA_FLAG_DMEIF DMA_FLAG_DMEIF3 +#define SD_SDIO_DMA_FLAG_TEIF DMA_FLAG_TEIF3 +#define SD_SDIO_DMA_FLAG_HTIF DMA_FLAG_HTIF3 +#define SD_SDIO_DMA_FLAG_TCIF DMA_FLAG_TCIF3 +#define SD_SDIO_DMA_IRQn DMA2_Stream3_IRQn +#define SD_SDIO_DMA_IRQHANDLER DMA2_Stream3_IRQHandler +#define SD_SDIO_FIFO_ADDRESS ((uint32_t)0x40012C80) +#define SD_SDIO_CLK_DIV(fq) ((48000000 / (fq)) - 2) +#define SD_SDIO_INIT_CLK_DIV SD_SDIO_CLK_DIV(400000) +#define SD_SDIO_TRANSFER_CLK_DIV SD_SDIO_CLK_DIV(24000000) + +// Audio + +// I2C Bus: CAT5137 +#define I2C_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOB +#define I2C_RCC_APB1Periph_I2C RCC_APB1Periph_I2C1 +#define I2C I2C1 +#define I2C_GPIO GPIOB +#define I2C_GPIO_PIN_SCL GPIO_Pin_8 // PB.08 +#define I2C_GPIO_PIN_SDA GPIO_Pin_9 // PB.09 +#define I2C_GPIO_AF GPIO_AF_I2C1 +#define I2C_GPIO_PinSource_SCL GPIO_PinSource8 +#define I2C_GPIO_PinSource_SDA GPIO_PinSource9 +#define I2C_SPEED 400000 +#define I2C_ADDRESS_CAT5137 0x5C + +// Haptic +#define HAPTIC_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOA +#define HAPTIC_GPIO GPIOA +#define HAPTIC_GPIO_PIN GPIO_Pin_2 +#define HAPTIC_RCC_APB2Periph_TIMER RCC_APB2ENR_TIM11EN +#define HAPTIC_GPIO_TIMER TIM11 +#define HAPTIC_GPIO_AF GPIO_AF_TIM11 +#define HAPTIC_GPIO_PinSource GPIO_PinSource2 + +// Internal Module +#define INTMODULE_RCC_AHB1Periph_GPIO (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB) +#define INTMODULE_GPIO_PWR GPIOA +#define INTMODULE_GPIO_PIN_PWR GPIO_Pin_8 // PA.08 +#define INTMODULE_GPIO GPIOB +#define INTMODULE_GPIO_PIN GPIO_Pin_6 // PB.06 +#define INTMODULE_GPIO_PinSource GPIO_PinSource6 + +// External Module +#define EXTMODULE_RCC_AHB1Periph_GPIO (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB) +#define EXTMODULE_GPIO_PWR GPIOB +#define EXTMODULE_GPIO_PIN_PWR GPIO_Pin_3 // PB.03 +#define EXTMODULE_GPIO GPIOA +#define EXTMODULE_GPIO_PIN GPIO_Pin_15 // PA.15 +#define EXTMODULE_GPIO_PinSource GPIO_PinSource15 + +// Trainer Port +#define TRAINER_RCC_AHB1Periph (RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC) +#define TRAINER_RCC_APB1Periph RCC_APB1Periph_TIM3 +#define TRAINER_GPIO GPIOC +#define TRAINER_GPIO_PIN_IN GPIO_Pin_6 // PC.06 +#define TRAINER_GPIO_PIN_OUT GPIO_Pin_7 // PC.07 +#define TRAINER_GPIO_DETECT GPIOB +#define TRAINER_GPIO_PIN_DETECT GPIO_Pin_4 // PB.04 +#define TRAINER_TIMER TIM3 +#define TRAINER_TIMER_IRQn TIM3_IRQn +#define TRAINER_GPIO_PinSource_IN GPIO_PinSource6 +#define TRAINER_GPIO_AF GPIO_AF_TIM3 + +// 5ms Interrupt +#define INTERRUPT_5MS_APB1Periph RCC_APB1Periph_TIM14 +#define INTERRUPT_5MS_TIMER TIM14 +#define INTERRUPT_5MS_IRQn TIM8_TRG_COM_TIM14_IRQn +#define INTERRUPT_5MS_IRQHandler TIM8_TRG_COM_TIM14_IRQHandler + +// 2MHz Timer +#define TIMER_2MHz_APB1Periph RCC_APB1Periph_TIM7 +#define TIMER_2MHz_TIMER TIM7 + +#endif diff --git a/radio/src/targets/Horus/haptic_driver.cpp b/radio/src/targets/Horus/haptic_driver.cpp new file mode 100644 index 000000000..0f62cc625 --- /dev/null +++ b/radio/src/targets/Horus/haptic_driver.cpp @@ -0,0 +1,75 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +void hapticOff(void) +{ + HAPTIC_GPIO_TIMER->CCR1 = 0; +} + +void hapticOn(uint32_t pwmPercent) +{ + if (pwmPercent > 100) { + pwmPercent = 100; + } + HAPTIC_GPIO_TIMER->CCR1 = pwmPercent; +} + +void hapticInit(void) +{ + RCC_AHB1PeriphClockCmd(HAPTIC_RCC_AHB1Periph_GPIO, ENABLE); + RCC_APB2PeriphClockCmd(HAPTIC_RCC_APB2Periph_TIMER, ENABLE); + + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = HAPTIC_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(HAPTIC_GPIO, &GPIO_InitStructure); + + GPIO_PinAFConfig(HAPTIC_GPIO, HAPTIC_GPIO_PinSource, HAPTIC_GPIO_AF); + + HAPTIC_GPIO_TIMER->ARR = 100 ; + HAPTIC_GPIO_TIMER->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 10000 - 1; + HAPTIC_GPIO_TIMER->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 ; // PWM + HAPTIC_GPIO_TIMER->CCER = TIM_CCER_CC1E ; + + HAPTIC_GPIO_TIMER->CCR1 = 0 ; + HAPTIC_GPIO_TIMER->EGR = 0 ; + HAPTIC_GPIO_TIMER->CR1 = TIM_CR1_CEN ; // Counter enable +} diff --git a/radio/src/targets/Horus/i2c_driver.cpp b/radio/src/targets/Horus/i2c_driver.cpp new file mode 100644 index 000000000..79ad59260 --- /dev/null +++ b/radio/src/targets/Horus/i2c_driver.cpp @@ -0,0 +1,116 @@ +#include "board_horus.h" + +void i2cInit(void) +{ + RCC_AHB1PeriphClockCmd(I2C_RCC_AHB1Periph_GPIO, ENABLE); + RCC_APB1PeriphClockCmd(I2C_RCC_APB1Periph_I2C, ENABLE); + + GPIO_PinAFConfig(I2C_GPIO, I2C_GPIO_PinSource_SCL, I2C_GPIO_AF); + GPIO_PinAFConfig(I2C_GPIO, I2C_GPIO_PinSource_SDA, I2C_GPIO_AF); + + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = I2C_GPIO_PIN_SCL | I2C_GPIO_PIN_SDA; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(I2C_GPIO, &GPIO_InitStructure); + + I2C_InitTypeDef I2C_InitStructure; + I2C_InitStructure.I2C_ClockSpeed = I2C_SPEED; + I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; + I2C_InitStructure.I2C_OwnAddress1 = 0x00; + I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; + I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; + I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; + I2C_Init(I2C, &I2C_InitStructure); + I2C_Cmd(I2C, ENABLE); +} + +#define I2C_TIMEOUT_MAX 1000 +bool I2C_WaitEvent(uint32_t event) +{ + uint32_t timeout = I2C_TIMEOUT_MAX; + while (!I2C_CheckEvent(I2C, event)) { + if ((timeout--) == 0) return false; + } + return true; +} + +bool I2C_WaitEventCleared(uint32_t event) +{ + uint32_t timeout = I2C_TIMEOUT_MAX; + while (I2C_CheckEvent(I2C, event)) { + if ((timeout--) == 0) return false; + } + return true; +} + +uint8_t i2cReadRegister(uint8_t address, uint8_t index) +{ + if (!I2C_WaitEventCleared(I2C_FLAG_BUSY)) + return 0; + + I2C_GenerateSTART(I2C, ENABLE); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) + return 0; + + I2C_Send7bitAddress(I2C, address, I2C_Direction_Transmitter); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) + return 0; + + I2C_SendData(I2C, index); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) + return 0; + + I2C_GenerateSTART(I2C, ENABLE); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) + return 0; + + I2C_Send7bitAddress(I2C, address, I2C_Direction_Receiver); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) + return 0; + + I2C_AcknowledgeConfig(I2C, DISABLE); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_RECEIVED)) + return 0; + + uint8_t result = I2C_ReceiveData(I2C); + + I2C_GenerateSTOP(I2C, ENABLE); + + return result; +} + +void i2cWriteRegister(uint8_t address, uint8_t index, uint8_t data) +{ + if (!I2C_WaitEventCleared(I2C_FLAG_BUSY)) + return; + + I2C_GenerateSTART(I2C, ENABLE); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) + return; + + I2C_Send7bitAddress(I2C, address, I2C_Direction_Transmitter); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) + return; + + I2C_SendData(I2C, index); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) + return; + + I2C_SendData(I2C, data); + if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) + return; + + I2C_GenerateSTOP(I2C, ENABLE); +} + +void setVolume(uint8_t volume) +{ + if (volume > VOLUME_LEVEL_MAX) { + volume = VOLUME_LEVEL_MAX; + } + + i2cWriteRegister(I2C_ADDRESS_CAT5137, 0, volumeScale[volume]); +} diff --git a/radio/src/targets/Horus/keys_driver.cpp b/radio/src/targets/Horus/keys_driver.cpp new file mode 100644 index 000000000..1c426f648 --- /dev/null +++ b/radio/src/targets/Horus/keys_driver.cpp @@ -0,0 +1,242 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +uint32_t readKeys() +{ + uint32_t result = 0; + + if (~KEYS_GPIO_REG_MENU & KEYS_GPIO_PIN_MENU) + result |= 1 << KEY_MENU; + if (~KEYS_GPIO_REG_EXIT & KEYS_GPIO_PIN_EXIT) + result |= 1 << KEY_EXIT; + if (~KEYS_GPIO_REG_ENTER & KEYS_GPIO_PIN_ENTER) + result |= 1 << KEY_ENTER; + if (~KEYS_GPIO_REG_UP & KEYS_GPIO_PIN_UP) + result |= 1 << KEY_UP; + if (~KEYS_GPIO_REG_DOWN & KEYS_GPIO_PIN_DOWN) + result |= 1 << KEY_DOWN; + if (~KEYS_GPIO_REG_RIGHT & KEYS_GPIO_PIN_RIGHT) + result |= 1 << KEY_RIGHT; + if (~KEYS_GPIO_REG_LEFT & KEYS_GPIO_PIN_LEFT) + result |= 1 << KEY_LEFT; + + // TRACE("readKeys(): %x", result); + + return result; +} + +uint32_t readTrims() +{ + uint32_t result = 0; + + if (~TRIMS_GPIO_REG_LHL & TRIMS_GPIO_PIN_LHL) + result |= 0x01; + if (~TRIMS_GPIO_REG_LHR & TRIMS_GPIO_PIN_LHR) + result |= 0x02; + if (~TRIMS_GPIO_REG_LVD & TRIMS_GPIO_PIN_LVD) + result |= 0x04; + if (~TRIMS_GPIO_REG_LVU & TRIMS_GPIO_PIN_LVU) + result |= 0x08; + if (~TRIMS_GPIO_REG_RVD & TRIMS_GPIO_PIN_RVD) + result |= 0x10; + if (~TRIMS_GPIO_REG_RVU & TRIMS_GPIO_PIN_RVU) + result |= 0x20; + if (~TRIMS_GPIO_REG_RHL & TRIMS_GPIO_PIN_RHL) + result |= 0x40; + if (~TRIMS_GPIO_REG_RHR & TRIMS_GPIO_PIN_RHR) + result |= 0x80; +#if 0 + if (~TRIMS_GPIO_REG_LSD & TRIMS_GPIO_PIN_LSD) + result |= 0x100; + if (~TRIMS_GPIO_REG_LSU & TRIMS_GPIO_PIN_LSU) + result |= 0x200; + if (~TRIMS_GPIO_REG_RSD & TRIMS_GPIO_PIN_RSD) + result |= 0x400; + if (~TRIMS_GPIO_REG_RSU & TRIMS_GPIO_PIN_RSU) + result |= 0x800; +#endif + // TRACE("readTrims(): result=0x%04x", result); + + return result; +} + +uint8_t trimDown(uint8_t idx) +{ + return readTrims() & (1 << idx); +} + +uint8_t keyDown() +{ + return readKeys(); +} + +rotenc_t rotencValue; +void checkRotaryEncoder() +{ + static uint32_t Rotary_position; + register uint32_t dummy ; + + dummy = ENC_GPIO->IDR ; // Read Rotary encode + dummy >>= 10; // quick & dirty! + dummy &= 0x03 ; // pick out the two bits + if ( dummy != ( Rotary_position & 0x03 ) ) { + if ( ( Rotary_position & 0x01 ) ^ ( ( dummy & 0x02) >> 1 ) ) { + --rotencValue; + } + else { + ++rotencValue; + } + Rotary_position &= ~0x03 ; + Rotary_position |= dummy ; + } +} + +/* TODO common to ARM */ +void readKeysAndTrims() +{ + register uint32_t i; + + uint8_t enuk = 0; + uint32_t in = readKeys(); + for (i = 0; i < TRM_BASE; i++) { + keys[enuk++].input(in & (1 << i)); + } + + static rotenc_t rePreviousValue; + rotenc_t reNewValue = (rotencValue / 2); + int8_t scrollRE = reNewValue - rePreviousValue; + if (scrollRE) { + rePreviousValue = reNewValue; + if (scrollRE < 0) { + putEvent(EVT_KEY_FIRST(KEY_MINUS)); + } + else { + putEvent(EVT_KEY_FIRST(KEY_PLUS)); + } + } + + in = readTrims(); + for (i = 1; i < 4096; i <<= 1) { + if (in & i) TRACE("trim %d", enuk); + keys[enuk++].input(in & i); + } +} + +#define ADD_2POS_CASE(x) \ + case SW_S ## x ## 0: \ + xxx = SWITCHES_GPIO_REG_ ## x & SWITCHES_GPIO_PIN_ ## x ; \ + break; \ + case SW_S ## x ## 2: \ + xxx = ~SWITCHES_GPIO_REG_ ## x & SWITCHES_GPIO_PIN_ ## x ; \ + break +#define ADD_3POS_CASE(x, i) \ + case SW_S ## x ## 0: \ + xxx = (SWITCHES_GPIO_REG_ ## x ## _H & SWITCHES_GPIO_PIN_ ## x ## _H); \ + if (IS_3POS(i)) { \ + xxx = xxx && (~SWITCHES_GPIO_REG_ ## x ## _L & SWITCHES_GPIO_PIN_ ## x ## _L); \ + } \ + break; \ + case SW_S ## x ## 1: \ + xxx = (SWITCHES_GPIO_REG_ ## x ## _H & SWITCHES_GPIO_PIN_ ## x ## _H) && (SWITCHES_GPIO_REG_ ## x ## _L & SWITCHES_GPIO_PIN_ ## x ## _L); \ + break; \ + case SW_S ## x ## 2: \ + xxx = (~SWITCHES_GPIO_REG_ ## x ## _H & SWITCHES_GPIO_PIN_ ## x ## _H); \ + if (IS_3POS(i)) { \ + xxx = xxx && (SWITCHES_GPIO_REG_ ## x ## _L & SWITCHES_GPIO_PIN_ ## x ## _L); \ + } \ + break + +#if !defined(BOOT) +bool switchState(EnumKeys enuk) +{ + register uint32_t xxx = 0; + + if (enuk < (int) DIM(keys)) return keys[enuk].state() ? 1 : 0; + + switch ((uint8_t) enuk) { + ADD_3POS_CASE(A, 0); + ADD_3POS_CASE(B, 1); + ADD_3POS_CASE(C, 2); + ADD_3POS_CASE(D, 3); + ADD_3POS_CASE(E, 4); + ADD_2POS_CASE(F); + ADD_3POS_CASE(G, 6); + ADD_2POS_CASE(H); + default: + break; + } + + // TRACE("switch %d => %d", enuk, xxx); + return xxx; +} +#endif + +void keysInit() +{ + RCC_AHB1PeriphClockCmd(KEYS_RCC_AHB1Periph_GPIO, ENABLE); + + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOB_PINS; + GPIO_Init(GPIOB, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOC_PINS; + GPIO_Init(GPIOC, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOD_PINS; + GPIO_Init(GPIOD, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOE_PINS; + GPIO_Init(GPIOE, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOG_PINS; + GPIO_Init(GPIOG, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOH_PINS; + GPIO_Init(GPIOH, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOI_PINS; + GPIO_Init(GPIOI, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = KEYS_GPIOJ_PINS; + GPIO_Init(GPIOJ, &GPIO_InitStructure); +} diff --git a/radio/src/targets/Horus/lcd_driver.cpp b/radio/src/targets/Horus/lcd_driver.cpp new file mode 100644 index 000000000..7e6b38a1a --- /dev/null +++ b/radio/src/targets/Horus/lcd_driver.cpp @@ -0,0 +1,529 @@ +/** + ****************************************************************************** + * @file Project/lcd/lcd.c + * @author FrSky Application Team + * @Hardware version V0.2 + * @date 11-July-2012 + * @brief This file provides LCD Init and botom drivers. + * * + ****************************************************************************** +*/ + +#include "../../opentx.h" + +#define HBP 42 +#define VBP 12 + +#define HSW 2 +#define VSW 10 + +#define HFP 3 +#define VFP 2 + +#define LCD_PIXEL_WIDTH ((uint16_t)480) +#define LCD_PIXEL_HEIGHT ((uint16_t)272) +/** + * @brief LCD Direction + */ +#define LCD_DIR_HORIZONTAL 0x0000 +#define LCD_DIR_VERTICAL 0x0001 + +/** + * @brief LCD Layer + */ +#define LCD_BACKGROUND_LAYER 0x0000 +#define LCD_FOREGROUND_LAYER 0x0001 + +/** + * @brief The Touch panel connector for LCD panel addresses + */ +#define LCD_ADDR 0x80 +#define ASSEMBLE_RGB(R ,G, B) ((((R)& 0xF8) << 8) | (((G) & 0xFC) << 3) | (((B) & 0xF8) >> 3)) + +#define LCD_BKLIGHT_PWM_FREQ 300 +#define ABS(X) ((X) > 0 ? (X) : -(X)) + +#define POLY_X(Z) ((int32_t)((Points + Z)->X)) +#define POLY_Y(Z) ((int32_t)((Points + Z)->Y)) + +/* Global variables to set the written text color */ +static uint16_t CurrentTextColor = 0x0000; +static uint16_t CurrentBackColor = 0xFFFF; + +/** @defgroup STM324x9I_EVAL_LCD_Private_Variables + * @{ + */ +// static sFONT *LCD_Currentfonts; +#define LCD_DEFAULT_FRAME_BUFFER SDRAM_BANK_ADDR +#define FRAME_BUFFER_OFFSET ((uint32_t)0x100000) +/* Default LCD configuration with LCD Layer 1 */ +static uint32_t CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER; +static uint32_t CurrentLayer = LCD_BACKGROUND_LAYER; + +#define NRST_LOW() do { LCD_GPIO_NRST->BSRRH = LCD_GPIO_PIN_NRST; } while(0) +#define NRST_HIGH() do { LCD_GPIO_NRST->BSRRL = LCD_GPIO_PIN_NRST; } while(0) + +static void LCD_AF_GPIOConfig(void) +{ + GPIO_InitTypeDef GPIO_InitStructure; + + /* GPIOs Configuration */ + /* + +------------------------+-----------------------+----------------------------+ + + LCD pins assignment + + +------------------------+-----------------------+---------------------------- + | | LCD_TFT G2 <-> PJ.09 | | + | LCD_TFT R3 <-> PJ.02 | LCD_TFT G3 <-> PJ.10 | LCD_TFT B3 <-> PJ.15 | + | LCD_TFT R4 <-> PJ.03 | LCD_TFT G4 <-> PJ.11 | LCD_TFT B4 <-> PK.03 | + | LCD_TFT R5 <-> PJ.04 | LCD_TFT G5 <-> PK.00 | LCD_TFT B5 <-> PK.04 | + | LCD_TFT R6 <-> PJ.05 | LCD_TFT G6 <-> PK.01 | LCD_TFT B6 <-> PK.05 | + | LCD_TFT R7 <-> PJ.06 | LCD_TFT G7 <-> PK.02 | LCD_TFT B7 <-> PK.06 | + ------------------------------------------------------------------------------- + | LCD_TFT HSYNC <-> PI.12 | LCDTFT VSYNC <-> PI.13 | + | LCD_TFT CLK <-> PI.14 | LCD_TFT DE <-> PK.07 /// + ----------------------------------------------------- + |LCD_TFT backlight <-> PA.04| LCD_CS <-> PI.10 |LCD_SCK<->PI.11 + ----------------------------------------------------- + */ + /*GPIOI configuration*/ + + GPIO_PinAFConfig(GPIOI,GPIO_PinSource12,GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOI,GPIO_PinSource13,GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOI,GPIO_PinSource14,GPIO_AF_LTDC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14; + GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType =GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_NOPULL; + GPIO_Init(GPIOI,&GPIO_InitStructure); + + GPIO_PinAFConfig(GPIOK, GPIO_PinSource7, GPIO_AF_LTDC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; + GPIO_Init(GPIOK, &GPIO_InitStructure); + + /* GPIOJ configuration */ + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource2, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource3, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource4, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource5, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource6, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource9, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource10, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource11, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource15, GPIO_AF_LTDC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | \ + GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_15; + + GPIO_Init(GPIOJ, &GPIO_InitStructure); + + /* GPIOK configuration */ + GPIO_PinAFConfig(GPIOK, GPIO_PinSource0, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource1, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource2, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource3, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource4, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource5, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource6, GPIO_AF_LTDC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 ; + + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(GPIOK, &GPIO_InitStructure); +} + +static void LCD_Backlight_Config(void) +{ + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = LCD_GPIO_PIN_BL; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(LCD_GPIO_BL, &GPIO_InitStructure); + GPIO_PinAFConfig(LCD_GPIO_BL, LCD_GPIO_PinSource_BL, LCD_GPIO_AF_BL); +} + +static void LCD_NRSTConfig(void) +{ + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = LCD_GPIO_PIN_NRST; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(LCD_GPIO_NRST, &GPIO_InitStructure); +} + +static void delay3(uint32_t nCount) +{ + uint32_t index = 0; + for(index = (1000 * 100 * nCount); index != 0; --index) + { + __asm("nop\n"); + } +} + +static void lcd_reset(void) +{ + NRST_HIGH(); + delay3(1); + + NRST_LOW(); // RESET(); + delay3(20); + + NRST_HIGH(); + delay3(30); +} + +void LCD_Init_LTDC(void) +{ + LTDC_InitTypeDef LTDC_InitStruct; + + /* Configure PLLSAI prescalers for LCD */ + /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */ + /* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAI_N = 192 Mhz */ + /* PLLLCDCLK = PLLSAI_VCO Output/PLL_LTDC = 192/3 = 64 Mhz */ + /* LTDC clock frequency = PLLLCDCLK / RCC_PLLSAIDivR = 64/4 = 16 Mhz */ + //second pam is for audio + //third pam is for LCD + RCC_PLLSAIConfig(192, 6, 3); + RCC_LTDCCLKDivConfig(RCC_PLLSAIDivR_Div4); //Modify by Fy + /* Enable PLLSAI Clock */ + RCC_PLLSAICmd(ENABLE); + + /* Wait for PLLSAI activation */ + while(RCC_GetFlagStatus(RCC_FLAG_PLLSAIRDY) == RESET) + { + } + + /* LTDC Configuration *********************************************************/ + /* Polarity configuration */ + /* Initialize the horizontal synchronization polarity as active low */ + LTDC_InitStruct.LTDC_HSPolarity = LTDC_HSPolarity_AL; + /* Initialize the vertical synchronization polarity as active low */ + LTDC_InitStruct.LTDC_VSPolarity = LTDC_VSPolarity_AL; + /* Initialize the data enable polarity as active low */ + LTDC_InitStruct.LTDC_DEPolarity = LTDC_DEPolarity_AL; + /* Initialize the pixel clock polarity as input pixel clock */ + LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IPC; + + /* Configure R,G,B component values for LCD background color */ + LTDC_InitStruct.LTDC_BackgroundRedValue = 0; + LTDC_InitStruct.LTDC_BackgroundGreenValue = 0; + LTDC_InitStruct.LTDC_BackgroundBlueValue = 0; + + /* Configure horizontal synchronization width */ + LTDC_InitStruct.LTDC_HorizontalSync = HSW; + /* Configure vertical synchronization height */ + LTDC_InitStruct.LTDC_VerticalSync = VSW; + /* Configure accumulated horizontal back porch */ + LTDC_InitStruct.LTDC_AccumulatedHBP = HBP; + /* Configure accumulated vertical back porch */ + LTDC_InitStruct.LTDC_AccumulatedVBP = VBP; + /* Configure accumulated active width */ + LTDC_InitStruct.LTDC_AccumulatedActiveW = LCD_PIXEL_WIDTH + HBP; + /* Configure accumulated active height */ + LTDC_InitStruct.LTDC_AccumulatedActiveH = LCD_PIXEL_HEIGHT + VBP; + /* Configure total width */ + LTDC_InitStruct.LTDC_TotalWidth = LCD_PIXEL_WIDTH + HBP + HFP; + /* Configure total height */ + LTDC_InitStruct.LTDC_TotalHeigh = LCD_PIXEL_HEIGHT + VBP + VFP; + +// init ltdc + LTDC_Init(<DC_InitStruct); + +#if 0 + LTDC_ITConfig(LTDC_IER_LIE, ENABLE); + NVIC_InitTypeDef NVIC_InitStructure; + NVIC_InitStructure.NVIC_IRQChannel = LTDC_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = LTDC_IRQ_PRIO; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; /* Not used as 4 bits are used for the pr e-emption priority. */; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init( &NVIC_InitStructure ); + + DMA2D_ITConfig(DMA2D_CR_TCIE, ENABLE); + NVIC_InitStructure.NVIC_IRQChannel = DMA2D_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = DMA_SCREEN_IRQ_PRIO; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; /* Not used as 4 bits are used for the pr e-emption priority. */; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init( &NVIC_InitStructure ); + + DMA2D->IFCR = (unsigned long)DMA2D_IFSR_CTCIF; +#endif +} + +/** + * @brief Initializes the LCD Layers. + * @param None + * @retval None + */ +void LCD_LayerInit() +{ + LTDC_Layer_InitTypeDef LTDC_Layer_InitStruct; + + /* Windowing configuration */ + /* In this case all the active display area is used to display a picture then : + Horizontal start = horizontal synchronization + Horizontal back porch = 30 + Horizontal stop = Horizontal start + window width -1 = 30 + 240 -1 + Vertical start = vertical synchronization + vertical back porch = 4 + Vertical stop = Vertical start + window height -1 = 4 + 320 -1 */ + LTDC_Layer_InitStruct.LTDC_HorizontalStart = HBP + 1; + LTDC_Layer_InitStruct.LTDC_HorizontalStop = (LCD_PIXEL_WIDTH + HBP); + LTDC_Layer_InitStruct.LTDC_VerticalStart = VBP + 1;; + LTDC_Layer_InitStruct.LTDC_VerticalStop = (LCD_PIXEL_HEIGHT + VBP); + + /* Pixel Format configuration*/ + LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_RGB565; + /* Alpha constant (255 totally opaque) */ + LTDC_Layer_InitStruct.LTDC_ConstantAlpha = 255; + /* Default Color configuration (configure A,R,G,B component values) */ + LTDC_Layer_InitStruct.LTDC_DefaultColorBlue = 0; + LTDC_Layer_InitStruct.LTDC_DefaultColorGreen = 0; + LTDC_Layer_InitStruct.LTDC_DefaultColorRed = 0; + LTDC_Layer_InitStruct.LTDC_DefaultColorAlpha = 0; + + /* Configure blending factors */ + LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_CA; + LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_CA; + + /* the length of one line of pixels in bytes + 3 then : + Line Lenth = Active high width x number of bytes per pixel + 3 + Active high width = LCD_PIXEL_WIDTH + number of bytes per pixel = 2 (pixel_format : RGB565) + */ + LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((LCD_PIXEL_WIDTH * 2) + 3); + /* the pitch is the increment from the start of one line of pixels to the + start of the next line in bytes, then : + Pitch = Active high width x number of bytes per pixel */ + LTDC_Layer_InitStruct.LTDC_CFBPitch = (LCD_PIXEL_WIDTH * 2); + + /* Configure the number of lines */ + LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_PIXEL_HEIGHT; + + /* Start Address configuration : the LCD Frame buffer is defined on SDRAM w/ Offset */ + LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_DEFAULT_FRAME_BUFFER; + + /* Initialize LTDC layer 1 */ + LTDC_LayerInit(LTDC_Layer1, <DC_Layer_InitStruct); + + /* Configure Layer 2 */ + LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_PAxCA; + LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_PAxCA; + + /* Start Address configuration : the LCD Frame buffer is defined on SDRAM w/ Offset */ + LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_DEFAULT_FRAME_BUFFER + FRAME_BUFFER_OFFSET; + + /* Initialize LTDC layer 2 */ + LTDC_LayerInit(LTDC_Layer2, <DC_Layer_InitStruct); + + /* LTDC configuration reload */ + LTDC_ReloadConfig(LTDC_IMReload); + + LTDC_LayerCmd(LTDC_Layer1, ENABLE); + LTDC_LayerCmd(LTDC_Layer2, ENABLE); + + LTDC_ReloadConfig(LTDC_IMReload); + + /* dithering activation */ + LTDC_DitherCmd(ENABLE); +} + +static inline long divRoundClosest(const long n, const long d) +{ + return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d); +} + +void LCD_ControlLight(uint16_t dutyCycle) +{ + TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; + TIM_OCInitTypeDef TIM_OCInitStructure; + + uint16_t freq = LCD_BKLIGHT_PWM_FREQ; + + uint32_t temp = 0; + temp = 1000000 / freq - 1; + + TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock / 1000000 - 1 ; + TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; + TIM_TimeBaseStructure.TIM_Period = temp; + TIM_TimeBaseStructure.TIM_ClockDivision = 0; + TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; + + TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure); + + unsigned long tempValue; + tempValue = (unsigned long)divRoundClosest((temp+1)*(100-dutyCycle), dutyCycle); + + TIM_Cmd(TIM8, DISABLE); + + /* Channel 1 Configuration in PWM mode */ + TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; + TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; + TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; + TIM_OCInitStructure.TIM_Pulse = (uint32_t)tempValue; + TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; + TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; + TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; + TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set; + TIM_OC1Init(TIM8, &TIM_OCInitStructure); + + TIM_Cmd(TIM8, ENABLE); + TIM_CtrlPWMOutputs(TIM8, ENABLE); +} + +/*********************************output****************************************/ +/** + * @brief Initializes the LCD. + * @param None + * @retval None + */ +void LCD_Init(void) +{ + RCC_AHB1PeriphClockCmd(LCD_RCC_AHB1Periph, ENABLE); + RCC_APB2PeriphClockCmd(LCD_RCC_APB2Periph, ENABLE); + + /* Reset the LCD --------------------------------------------------------*/ + LCD_NRSTConfig(); + lcd_reset(); + + /* Configure the LCD Control pins */ + LCD_AF_GPIOConfig(); + + LCD_Init_LTDC(); + + //config backlight + LCD_Backlight_Config(); + + //Max Value + LCD_ControlLight(100); +} + +/** + * @brief Sets the LCD Layer. + * @param Layerx: specifies the Layer foreground or background. + * @retval None + */ +void LCD_SetLayer(uint32_t Layerx) +{ + if (Layerx == LCD_BACKGROUND_LAYER) + { + CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER; + CurrentLayer = LCD_BACKGROUND_LAYER; + } + else + { + CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER + FRAME_BUFFER_OFFSET; + CurrentLayer = LCD_FOREGROUND_LAYER; + } +} + +#define LCD_COLOR_WHITE 0xFFFF +#define LCD_COLOR_RED 0xF800 + +void LCD_DrawCircle(uint16_t Xpos, uint16_t Ypos, uint16_t Radius) +{ + int x = -Radius, y = 0, err = 2-2*Radius, e2; + do { + *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos-x) + LCD_PIXEL_WIDTH*(Ypos+y)))) = LCD_COLOR_RED; + *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos+x) + LCD_PIXEL_WIDTH*(Ypos+y)))) = LCD_COLOR_RED; + *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos+x) + LCD_PIXEL_WIDTH*(Ypos-y)))) = LCD_COLOR_RED; + *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos-x) + LCD_PIXEL_WIDTH*(Ypos-y)))) = LCD_COLOR_RED; + + e2 = err; + if (e2 <= y) { + err += ++y*2+1; + if (-x == y && e2 <= x) e2 = 0; + } + if (e2 > x) err += ++x*2+1; + } + while (x <= 0); +} + +void LCD_Clear(uint16_t Color) +{ + uint16_t *index ; + + /* erase memory */ + for (index = (uint16_t *)CurrentFrameBuffer; index < (uint16_t *)(CurrentFrameBuffer+(FRAME_BUFFER_OFFSET)); index++) + { + *index = Color; + } +} + +/** + * @brief Configure the transparency. + * @param transparency: specifies the transparency, + * This parameter must range from 0x00 to 0xFF. + * @retval None + */ +void LCD_SetTransparency(uint8_t transparency) +{ + if (CurrentLayer == LCD_BACKGROUND_LAYER) + { + LTDC_LayerAlpha(LTDC_Layer1, transparency); + } + else + { + LTDC_LayerAlpha(LTDC_Layer2, transparency); + } + LTDC_ReloadConfig(LTDC_IMReload); +} + +void LCD_SetColorKeying(uint32_t RGBValue) +{ + uint32_t tmp = 0; + + LTDC_ColorKeying_InitTypeDef LTDC_colorkeying_InitStruct; + + /* configure the color Keying */ + LTDC_colorkeying_InitStruct.LTDC_ColorKeyBlue = 0xFF & RGBValue; + tmp = (0xFF00 & RGBValue); + LTDC_colorkeying_InitStruct.LTDC_ColorKeyGreen = (tmp >> 8); + tmp = (0xFF0000 & RGBValue); + LTDC_colorkeying_InitStruct.LTDC_ColorKeyRed = (tmp >> 16); + + if (CurrentLayer == LCD_BACKGROUND_LAYER) + { + /* Enable the color Keying for Layer1 */ + LTDC_ColorKeyingConfig(LTDC_Layer1, <DC_colorkeying_InitStruct, ENABLE); + LTDC_ReloadConfig(LTDC_IMReload); + } + else + { + /* Enable the color Keying for Layer2 */ + LTDC_ColorKeyingConfig(LTDC_Layer2, <DC_colorkeying_InitStruct, ENABLE); + LTDC_ReloadConfig(LTDC_IMReload); + } +} + +void lcdInit(void) +{ + /* Initialize the LCD */ + LCD_Init(); + LCD_LayerInit(); + + /* Enable LCD display */ + LTDC_Cmd(ENABLE); + + /* Set Background layer */ + LCD_SetLayer(LCD_BACKGROUND_LAYER); + LCD_Clear(0); + LCD_SetTransparency(0); + + /* Set Foreground layer */ + LCD_SetLayer(LCD_FOREGROUND_LAYER); + LCD_Clear(0); + LCD_SetTransparency(255); +} + + +void lcdRefresh() +{ + memcpy((void *)(SDRAM_BANK_ADDR+FRAME_BUFFER_OFFSET), (void *)SDRAM_BANK_ADDR, 480*272*2); +} diff --git a/radio/src/targets/Horus/led_driver.cpp b/radio/src/targets/Horus/led_driver.cpp new file mode 100644 index 000000000..8da96ac4c --- /dev/null +++ b/radio/src/targets/Horus/led_driver.cpp @@ -0,0 +1,67 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "board_horus.h" + +void ledInit() +{ + RCC_AHB1PeriphClockCmd(LED_RCC_AHB1Periph_GPIO, ENABLE); + + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = LED_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(LED_GPIO, &GPIO_InitStructure); +} + +void ledOff() +{ + RCC_AHB1PeriphClockCmd(LED_RCC_AHB1Periph_GPIO, DISABLE); +} + +void ledRed() +{ + ledInit(); + GPIO_SetBits(LED_GPIO, LED_GPIO_PIN); +} + +void ledBlue() +{ + ledInit(); + GPIO_ResetBits(LED_GPIO, LED_GPIO_PIN); +} diff --git a/radio/src/targets/Horus/pulses_driver.cpp b/radio/src/targets/Horus/pulses_driver.cpp new file mode 100644 index 000000000..9094ee3b9 --- /dev/null +++ b/radio/src/targets/Horus/pulses_driver.cpp @@ -0,0 +1,321 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +void setupPulses(unsigned int port); +void setupPulsesPPM(unsigned int port); +void setupPulsesPXX(unsigned int port); + +void set_external_ppm_parameters(uint32_t idleTime, uint32_t delay, uint32_t positive) +{ + TIM1->CCR2 = idleTime; + TIM1->CCR1 = delay; + // we are using complementary output so logic has to be reversed here + TIM1->CCER = TIM_CCER_CC1NE | (positive ? 0 : TIM_CCER_CC1NP); +} + +void init_no_pulses(uint32_t port) +{ + EXTERNAL_MODULE_OFF(); + + // Timer1, channel 1 + GPIO_InitTypeDef GPIO_InitStructure; + RCC_AHB1PeriphClockCmd(EXTMODULE_RCC_AHB1Periph_GPIO, ENABLE); + + GPIO_InitStructure.GPIO_Pin = EXTMODULE_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT ; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(EXTMODULE_GPIO, &GPIO_InitStructure); + + GPIO_SetBits(EXTMODULE_GPIO, EXTMODULE_GPIO_PIN) ; // Set high + + RCC->APB2ENR |= RCC_APB2ENR_TIM1EN ; // Enable clock + + TIM1->CR1 &= ~TIM_CR1_CEN ; + TIM1->ARR = 36000 ; // 18mS + TIM1->CCR2 = 32000 ; // Update time + TIM1->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 2000000 - 1 ; // 0.5uS from 30MHz + + TIM1->CCER = TIM_CCER_CC3E ; + + TIM1->CCMR2 = 0 ; + TIM1->EGR = 1 ; // Restart + + TIM1->CCMR2 = TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_0 ; // Toggle CC1 o/p + TIM1->SR &= ~TIM_SR_CC2IF ; // Clear flag + TIM1->DIER |= TIM_DIER_CC2IE ; // Enable this interrupt + TIM1->CR1 |= TIM_CR1_CEN ; + NVIC_EnableIRQ(TIM1_CC_IRQn) ; + NVIC_SetPriority(TIM1_CC_IRQn, 7); +} + +void disable_no_pulses(uint32_t port) +{ + NVIC_DisableIRQ(TIM1_CC_IRQn) ; + TIM1->DIER &= ~TIM_DIER_CC2IE ; + TIM1->CR1 &= ~TIM_CR1_CEN ; +} + +void init_pxx(uint32_t port) +{ + EXTERNAL_MODULE_ON(); + + // Timer1, channel1 + setupPulsesPXX(EXTERNAL_MODULE); + + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN ; // Enable portA clock + GPIO_InitTypeDef GPIO_InitStructure; + RCC_AHB1PeriphClockCmd(EXTMODULE_RCC_AHB1Periph_GPIO, ENABLE); + GPIO_PinAFConfig(EXTMODULE_GPIO, EXTMODULE_GPIO_PinSource, GPIO_AF_TIM1); + GPIO_InitStructure.GPIO_Pin = EXTMODULE_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(EXTMODULE_GPIO, &GPIO_InitStructure); + + RCC->APB2ENR |= RCC_APB2ENR_TIM1EN ; // Enable clock + RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN ; // Enable DMA2 clock + + TIM1->CR1 &= ~TIM_CR1_CEN ; + TIM1->ARR = 18000 ; // 9mS + TIM1->CCR2 = 15000 ; // Update time + TIM1->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 2000000 - 1 ; // 0.5uS from 30MHz + TIM1->CCER = TIM_CCER_CC1E ; + TIM1->CR2 = TIM_CR2_OIS1 ; // O/P idle high + TIM1->BDTR = TIM_BDTR_MOE ; // Enable outputs + TIM1->CCR1 = modulePulsesData[EXTERNAL_MODULE].pxx.pulses[0]; + TIM1->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_0 ; // Force O/P high + TIM1->EGR = 1 ; // Restart + TIM1->DIER |= TIM_DIER_CC1DE ; // Enable DMA on CC3 match + TIM1->DCR = 15 ; // TODO check // DMA to CC1 + + // Enable the DMA channel here, DMA2 stream 2, channel 7 + DMA2_Stream2->CR &= ~DMA_SxCR_EN ; // Disable DMA + DMA2->LIFCR = DMA_LIFCR_CTCIF2 | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTEIF2 | DMA_LIFCR_CDMEIF2 | DMA_LIFCR_CFEIF2 ; // Write ones to clear bits + DMA2_Stream2->CR = DMA_SxCR_CHSEL_0 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_2 | DMA_SxCR_PL_0 | DMA_SxCR_MSIZE_0 + | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_PFCTRL ; + DMA2_Stream2->PAR = CONVERT_PTR_UINT(&TIM1->DMAR); + DMA2_Stream2->M0AR = CONVERT_PTR_UINT(&modulePulsesData[EXTERNAL_MODULE].pxx.pulses[1]); +// DMA2_Stream2->FCR = 0x05 ; //DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ; +// DMA2_Stream2->NDTR = 100 ; + DMA2_Stream2->CR |= DMA_SxCR_EN ; // Enable DMA + + TIM1->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0 ; // Toggle CC1 o/p + TIM1->SR &= ~TIM_SR_CC2IF ; // Clear flag + TIM1->DIER |= TIM_DIER_CC2IE ; // Enable this interrupt + TIM1->CR1 |= TIM_CR1_CEN ; + NVIC_EnableIRQ(TIM1_CC_IRQn); + NVIC_SetPriority(TIM1_CC_IRQn, 7); +} + +void disable_pxx(uint32_t port) +{ + DMA2_Stream2->CR &= ~DMA_SxCR_EN ; // Disable DMA + NVIC_DisableIRQ(TIM1_CC_IRQn) ; + TIM1->DIER &= ~TIM_DIER_CC2IE ; + TIM1->CR1 &= ~TIM_CR1_CEN ; + EXTERNAL_MODULE_OFF(); +} + +#if defined(DSM2) +void init_dsm2(uint32_t port) +{ + EXTERNAL_MODULE_ON(); + + // Timer8 + setupPulsesDSM2(EXTERNAL_MODULE); + + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN ; // Enable portA clock + GPIO_InitTypeDef GPIO_InitStructure; + RCC_AHB1PeriphClockCmd(EXTMODULE_RCC_AHB1Periph_GPIO, ENABLE); + GPIO_PinAFConfig(EXTMODULE_GPIO, EXTMODULE_GPIO_PinSource, GPIO_AF_TIM1); + GPIO_InitStructure.GPIO_Pin = EXTMODULE_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(EXTMODULE_GPIO, &GPIO_InitStructure); + RCC->APB2ENR |= RCC_APB2ENR_TIM1EN ; // Enable clock + RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN ; // Enable DMA2 clock + + TIM1->CR1 &= ~TIM_CR1_CEN ; + TIM1->ARR = 44000 ; // 22mS + TIM1->CCR2 = 40000 ; // Update time + TIM1->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 2000000 - 1 ; // 0.5uS from 30MHz + TIM1->CCER = TIM_CCER_CC1NE | TIM_CCER_CC1NP ; + TIM1->CR2 = TIM_CR2_OIS1 ; // O/P idle high + TIM1->BDTR = TIM_BDTR_MOE ; // Enable outputs + TIM1->CCR1 = modulePulsesData[EXTERNAL_MODULE].dsm2.pulses[0]; + TIM1->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_0 ; // Force O/P high + TIM1->EGR = 1 ; // Restart + +// TIM1->SR &= ~TIM_SR_UIF ; // Clear flag +// TIM1->SR &= ~TIM_SR_CC2IF ; // Clear flag + TIM1->DIER |= TIM_DIER_CC1DE ; // Enable DMA on CC1 match + TIM1->DCR = 13 ; // DMA to CC1 + +// TIM1->CR1 = TIM_CR1_OPM ; // Just run once + // Enable the DMA channel here, DMA2 stream 2, channel 7 + DMA2_Stream2->CR &= ~DMA_SxCR_EN ; // Disable DMA + DMA2->LIFCR = DMA_LIFCR_CTCIF2 | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTEIF2 | DMA_LIFCR_CDMEIF2 | DMA_LIFCR_CFEIF2 ; // Write ones to clear bits + DMA2_Stream2->CR = DMA_SxCR_CHSEL_0 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_2 | DMA_SxCR_PL_0 | DMA_SxCR_MSIZE_0 + | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_PFCTRL ; + DMA2_Stream2->PAR = CONVERT_PTR_UINT(&TIM1->DMAR); + DMA2_Stream2->M0AR = CONVERT_PTR_UINT(&modulePulsesData[EXTERNAL_MODULE].dsm2.pulses[1]); +// DMA2_Stream2->FCR = 0x05 ; //DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ; +// DMA2_Stream2->NDTR = 100 ; + DMA2_Stream2->CR |= DMA_SxCR_EN ; // Enable DMA + + TIM1->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0 ; // Toggle CC1 o/p + TIM1->SR &= ~TIM_SR_CC2IF ; // Clear flag + TIM1->DIER |= TIM_DIER_CC2IE ; // Enable this interrupt + TIM1->CR1 |= TIM_CR1_CEN ; + NVIC_EnableIRQ(TIM1_CC_IRQn) ; + NVIC_SetPriority(TIM1_CC_IRQn, 7); +} + +void disable_dsm2(uint32_t port) +{ + DMA2_Stream2->CR &= ~DMA_SxCR_EN ; // Disable DMA + NVIC_DisableIRQ(TIM1_CC_IRQn) ; + TIM1->DIER &= ~TIM_DIER_CC2IE ; + TIM1->CR1 &= ~TIM_CR1_CEN ; + EXTERNAL_MODULE_OFF(); +} +#endif + +// PPM output +// Timer 1, channel 1 on PA8 for prototype +// Pin is AF1 function for timer 1 +void init_ppm(uint32_t port) +{ + EXTERNAL_MODULE_ON(); + + // Timer1 + modulePulsesData[EXTERNAL_MODULE].ppm.ptr = modulePulsesData[EXTERNAL_MODULE].ppm.pulses; + + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN ; // Enable portA clock + configure_pins( EXTMODULE_GPIO_PIN, PIN_PERIPHERAL | PIN_PORTA | PIN_PER_3 | PIN_OS25 | PIN_PUSHPULL ) ; + RCC->APB2ENR |= RCC_APB2ENR_TIM1EN ; // Enable clock + TIM1->CR1 &= ~TIM_CR1_CEN ; + + // setupPulsesPPM() is also configuring registers, + // so it has to be called after the peripheral is enabled + setupPulsesPPM(EXTERNAL_MODULE) ; + + TIM1->ARR = *modulePulsesData[EXTERNAL_MODULE].ppm.ptr++ ; + TIM1->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 2000000 - 1 ; // 0.5uS from 30MHz + + TIM1->CCER = TIM_CCER_CC3E ; + + TIM1->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC2PE; // PWM mode 1 + TIM1->BDTR = TIM_BDTR_MOE ; + TIM1->EGR = 1 ; + TIM1->DIER = TIM_DIER_UDE ; + + TIM1->SR &= ~TIM_SR_UIF ; // Clear flag + TIM1->SR &= ~TIM_SR_CC2IF ; // Clear flag + TIM1->DIER |= TIM_DIER_CC2IE ; + TIM1->DIER |= TIM_DIER_UIE ; + + TIM1->CR1 = TIM_CR1_CEN ; + NVIC_EnableIRQ(TIM1_CC_IRQn) ; + NVIC_SetPriority(TIM1_CC_IRQn, 7); + NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn) ; + NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 7); +} + +void disable_ppm(uint32_t port) +{ + NVIC_DisableIRQ(TIM1_CC_IRQn) ; + NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn) ; + TIM1->DIER &= ~TIM_DIER_CC2IE & ~TIM_DIER_UIE ; + TIM1->CR1 &= ~TIM_CR1_CEN ; + EXTERNAL_MODULE_OFF(); +} + +#if !defined(SIMU) +extern "C" void TIM1_CC_IRQHandler() +{ + TIM1->DIER &= ~TIM_DIER_CC2IE ; // stop this interrupt + TIM1->SR &= ~TIM_SR_CC2IF ; // Clear flag + + setupPulses(EXTERNAL_MODULE) ; + + if (s_current_protocol[EXTERNAL_MODULE] == PROTO_PXX) { + DMA2_Stream2->CR &= ~DMA_SxCR_EN ; // Disable DMA + DMA2->LIFCR = DMA_LIFCR_CTCIF2 | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTEIF2 | DMA_LIFCR_CDMEIF2 | DMA_LIFCR_CFEIF2 ; // Write ones to clear bits + DMA2_Stream2->M0AR = CONVERT_PTR_UINT(&modulePulsesData[EXTERNAL_MODULE].pxx.pulses[1]); + DMA2_Stream2->CR |= DMA_SxCR_EN ; // Enable DMA + TIM1->CCR1 = modulePulsesData[EXTERNAL_MODULE].pxx.pulses[0]; + TIM1->DIER |= TIM_DIER_CC2IE ; // Enable this interrupt + } +#if defined(DSM2) + else if (s_current_protocol[EXTERNAL_MODULE] >= PROTO_DSM2_LP45 && s_current_protocol[EXTERNAL_MODULE] <= PROTO_DSM2_DSMX) { + DMA2_Stream2->CR &= ~DMA_SxCR_EN ; // Disable DMA + DMA2->LIFCR = DMA_LIFCR_CTCIF2 | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTEIF2 | DMA_LIFCR_CDMEIF2 | DMA_LIFCR_CFEIF2 ; // Write ones to clear bits + DMA2_Stream2->M0AR = CONVERT_PTR_UINT(&modulePulsesData[EXTERNAL_MODULE].dsm2.pulses[1]); + DMA2_Stream2->CR |= DMA_SxCR_EN ; // Enable DMA + TIM1->CCR1 = modulePulsesData[EXTERNAL_MODULE].dsm2.pulses[0]; + TIM1->DIER |= TIM_DIER_CC2IE ; // Enable this interrupt + } +#endif + else if (s_current_protocol[EXTERNAL_MODULE] == PROTO_PPM) { + modulePulsesData[EXTERNAL_MODULE].ppm.ptr = modulePulsesData[EXTERNAL_MODULE].ppm.pulses; + TIM1->DIER |= TIM_DIER_UDE ; + TIM1->SR &= ~TIM_SR_UIF ; // Clear this flag + TIM1->DIER |= TIM_DIER_UIE ; // Enable this interrupt + } + else { + TIM1->DIER |= TIM_DIER_CC2IE ; // Enable this interrupt + } +} + +extern "C" void TIM1_UP_TIM10_IRQHandler() +{ + TIM1->SR &= ~TIM_SR_UIF ; // Clear flag + + TIM1->ARR = *modulePulsesData[EXTERNAL_MODULE].ppm.ptr++ ; + if (*modulePulsesData[EXTERNAL_MODULE].ppm.ptr == 0) { + TIM1->SR &= ~TIM_SR_CC2IF ; // Clear this flag + TIM1->DIER |= TIM_DIER_CC2IE ; // Enable this interrupt + } +} +#endif diff --git a/radio/src/targets/Horus/pwr_driver.c b/radio/src/targets/Horus/pwr_driver.c new file mode 100644 index 000000000..a7202ca8e --- /dev/null +++ b/radio/src/targets/Horus/pwr_driver.c @@ -0,0 +1,119 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../pwr.h" +#include "board_horus.h" + +extern volatile uint32_t g_tmr10ms; +#define get_tmr10ms() g_tmr10ms + +void pwrInit() +{ + GPIO_InitTypeDef GPIO_InitStructure; + + /* Clock enable */ + RCC_AHB1PeriphClockCmd(PWR_RCC_AHB1Periph_GPIO, ENABLE); + + /* GPIO Configuration*/ + GPIO_InitStructure.GPIO_Pin = PWR_GPIO_PIN_ON; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(PWR_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = PWR_GPIO_PIN_SWITCH; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(PWR_GPIO, &GPIO_InitStructure); + + // Soft power ON + GPIO_SetBits(PWR_GPIO, PWR_GPIO_PIN_ON); + + // Init Module PWR + GPIO_ResetBits(EXTMODULE_GPIO_PWR, EXTMODULE_GPIO_PIN_PWR); + GPIO_InitStructure.GPIO_Pin = EXTMODULE_GPIO_PIN_PWR; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(EXTMODULE_GPIO_PWR, &GPIO_InitStructure); + + // TODO while (pwrPressed()) ; + +} + +void pwrOff() +{ + GPIO_ResetBits(PWR_GPIO, PWR_GPIO_PIN_ON); +} + +uint32_t pwrPressed() +{ + return GPIO_ReadInputDataBit(PWR_GPIO, PWR_GPIO_PIN_SWITCH) == Bit_RESET; +} + +#if !defined(BOOT) +uint32_t pwrPressTime = 0; +uint32_t pwrPressedDuration() +{ + if (pwrPressTime == 0) { + return 0; + } + else { + return get_tmr10ms() - pwrPressTime; + } +} +uint32_t pwrCheck() +{ + if (pwrPressed()) { + if (pwrPressTime == 0) { + pwrPressTime = get_tmr10ms(); + } + else { + if (get_tmr10ms() - pwrPressTime > 250) { + return e_power_off; + } + } + } + else { + pwrPressTime = 0; + } + return e_power_on; +} +#endif diff --git a/radio/src/targets/Horus/sdio_sd.c b/radio/src/targets/Horus/sdio_sd.c new file mode 100644 index 000000000..8749bc62f --- /dev/null +++ b/radio/src/targets/Horus/sdio_sd.c @@ -0,0 +1,3174 @@ +/** + ****************************************************************************** + * @file stm32f4_discovery_sdio_sd.c + * @author MCD Application Team + * @version V1.0.2 + * @date 05-March-2012 + * @brief This file provides a set of functions needed to manage the SDIO SD + * Card memory mounted on STM32F4 Discovery board. + * + * + * @verbatim + * + * =================================================================== + * How to use this driver + * =================================================================== + * It implements a high level communication layer for read and write + * from/to this memory. The needed STM32 hardware resources (SDIO and + * GPIO) are defined in stm32f4_discovery.h file, and the initialization is + * performed in SD_LowLevel_Init() function declared in stm32f4_discovery.c + * file. + * You can easily tailor this driver to any other development board, + * by just adapting the defines for hardware resources and + * SD_LowLevel_Init() function. + * + * A - SD Card Initialization and configuration + * ============================================ + * - To initialize the SD Card, use the SD_Init() function. It + * Initializes the SD Card and put it into StandBy State (Ready + * for data transfer). This function provide the following operations: + * + * 1 - Apply the SD Card initialization process at 400KHz and check + * the SD Card type (Standard Capacity or High Capacity). You + * can change or adapt this frequency by adjusting the + * "SDIO_INIT_CLK_DIV" define inside the stm32f4_discovery.h file. + * The SD Card frequency (SDIO_CK) is computed as follows: + * + * +---------------------------------------------+ + * | SDIO_CK = SDIOCLK / (SDIO_INIT_CLK_DIV + 2) | + * +---------------------------------------------+ + * + * In initialization mode and according to the SD Card standard, + * make sure that the SDIO_CK frequency don't exceed 400KHz. + * + * 2 - Get the SD CID and CSD data. All these information are + * managed by the SDCardInfo structure. This structure provide + * also ready computed SD Card capacity and Block size. + * + * 3 - Configure the SD Card Data transfer frequency. By Default, + * the card transfer frequency is set to 24MHz. You can change + * or adapt this frequency by adjusting the "SDIO_TRANSFER_CLK_DIV" + * define inside the stm32f4_discovery.h file. + * The SD Card frequency (SDIO_CK) is computed as follows: + * + * +---------------------------------------------+ + * | SDIO_CK = SDIOCLK / (SDIO_INIT_CLK_DIV + 2) | + * +---------------------------------------------+ + * + * In transfer mode and according to the SD Card standard, + * make sure that the SDIO_CK frequency don't exceed 25MHz + * and 50MHz in High-speed mode switch. + * To be able to use a frequency higher than 24MHz, you should + * use the SDIO peripheral in bypass mode. Refer to the + * corresponding reference manual for more details. + * + * 4 - Select the corresponding SD Card according to the address + * read with the step 2. + * + * 5 - Configure the SD Card in wide bus mode: 4-bits data. + * + * B - SD Card Read operation + * ========================== + * - You can read SD card by using two function: SD_ReadBlock() and + * SD_ReadMultiBlocks() functions. These functions support only + * 512-byte block length. + * - The SD_ReadBlock() function read only one block (512-byte). This + * function can transfer the data using DMA controller or using + * polling mode. To select between DMA or polling mode refer to + * "SD_DMA_MODE" or "SD_POLLING_MODE" inside the stm32f4_discovery_sdio_sd.h + * file and uncomment the corresponding line. By default the SD DMA + * mode is selected + * - The SD_ReadMultiBlocks() function read only mutli blocks (multiple + * of 512-byte). + * - Any read operation should be followed by two functions to check + * if the DMA Controller and SD Card status. + * - SD_ReadWaitOperation(): this function insure that the DMA + * controller has finished all data transfer. + * - SD_GetStatus(): to check that the SD Card has finished the + * data transfer and it is ready for data. + * + * - The DMA transfer is finished by the SDIO Data End interrupt. + * User has to call the SD_ProcessIRQ() function inside the SDIO_IRQHandler() + * and SD_ProcessDMAIRQ() function inside the DMA2_Streamx_IRQHandler(). + * Don't forget to enable the SDIO_IRQn and DMA2_Stream3_IRQn or + * DMA2_Stream6_IRQn interrupts using the NVIC controller. + * + * C - SD Card Write operation + * =========================== + * - You can write SD card by using two function: SD_WriteBlock() and + * SD_WriteMultiBlocks() functions. These functions support only + * 512-byte block length. + * - The SD_WriteBlock() function write only one block (512-byte). This + * function can transfer the data using DMA controller or using + * polling mode. To select between DMA or polling mode refer to + * "SD_DMA_MODE" or "SD_POLLING_MODE" inside the stm32f4_discovery_sdio_sd.h + * file and uncomment the corresponding line. By default the SD DMA + * mode is selected + * - The SD_WriteMultiBlocks() function write only mutli blocks (multiple + * of 512-byte). + * - Any write operation should be followed by two functions to check + * if the DMA Controller and SD Card status. + * - SD_ReadWaitOperation(): this function insure that the DMA + * controller has finished all data transfer. + * - SD_GetStatus(): to check that the SD Card has finished the + * data transfer and it is ready for data. + * + * - The DMA transfer is finished by the SDIO Data End interrupt. + * User has to call the SD_ProcessIRQ() function inside the SDIO_IRQHandler() + * and SD_ProcessDMAIRQ() function inside the DMA2_Streamx_IRQHandler(). + * Don't forget to enable the SDIO_IRQn and DMA2_Stream3_IRQn or + * DMA2_Stream6_IRQn interrupts using the NVIC controller. + * + * + * D - SD card status + * ================== + * - At any time, you can check the SD Card status and get the SD card + * state by using the SD_GetStatus() function. This function checks + * first if the SD card is still connected and then get the internal + * SD Card transfer state. + * - You can also get the SD card SD Status register by using the + * SD_SendSDStatus() function. + * + * E - Programming Model (Selecting DMA for SDIO data Transfer) + * ============================================================ + * Status = SD_Init(); // Initialization Step as described in section A + * + * // SDIO Interrupt ENABLE + * NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn; + * NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; + * NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; + * NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + * NVIC_Init(&NVIC_InitStructure); + * // DMA2 STREAMx Interrupt ENABLE + * NVIC_InitStructure.NVIC_IRQChannel = SD_SDIO_DMA_IRQn; + * NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; + * NVIC_Init(&NVIC_InitStructure); + * + * // Write operation as described in Section C + * Status = SD_WriteBlock(buffer, address, 512); + * Status = SD_WaitWriteOperation(); + * while(SD_GetStatus() != SD_TRANSFER_OK); + * + * Status = SD_WriteMultiBlocks(buffer, address, 512, NUMBEROFBLOCKS); + * Status = SD_WaitWriteOperation(); + * while(SD_GetStatus() != SD_TRANSFER_OK); + * + * // Read operation as described in Section B + * Status = SD_ReadBlock(buffer, address, 512); + * Status = SD_WaitReadOperation(); + * while(SD_GetStatus() != SD_TRANSFER_OK); + * + * Status = SD_ReadMultiBlocks(buffer, address, 512, NUMBEROFBLOCKS); + * Status = SD_WaitReadOperation(); + * while(SD_GetStatus() != SD_TRANSFER_OK); + * + * - Add the SDIO and DMA2 StreamX (3 or 6) IRQ Handlers: + * void SDIO_IRQHandler(void) + * { + * SD_ProcessIRQ(); + * } + * void SD_SDIO_DMA_IRQHANDLER(void) + * { + * SD_ProcessDMAIRQ(); + * } + * + * F - Programming Model (Selecting Polling for SDIO data Transfer) + * ================================================================ + * //Only SD Card Single Block operation are managed. + * Status = SD_Init(); // Initialization Step as described in section + * + * // Write operation as described in Section C + * Status = SD_WriteBlock(buffer, address, 512); + * + * // Read operation as described in Section B + * Status = SD_ReadBlock(buffer, address, 512); + * + * STM32 SDIO Pin assignment + * ========================= + * +-----------------------------------------------------------+ + * | Pin assignment | + * +-----------------------------+---------------+-------------+ + * | STM32 SDIO Pins | SD | Pin | + * +-----------------------------+---------------+-------------+ + * | SDIO D2 | D2 | 1 | + * | SDIO D3 | D3 | 2 | + * | SDIO CMD | CMD | 3 | + * | | VCC | 4 (3.3 V)| + * | SDIO CLK | CLK | 5 | + * | | GND | 6 (0 V) | + * | SDIO D0 | D0 | 7 | + * | SDIO D1 | D1 | 8 | + * +-----------------------------+---------------+-------------+ + * + * @endverbatim + * + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + + PATCHED BY CLIVE SOURCER32@GMAIL.COM TO SUPPORT MEDIA >4GB 21-JUN-2012 + + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "sdio_sd.h" +#include "../../debug.h" + +/** @addtogroup Utilities + * @{ + */ + +/** @addtogroup STM32_EVAL + * @{ + */ + +/** @addtogroup STM32F4_DISCOVERY + * @{ + */ + +/** @addtogroup STM32F4_DISCOVERY_SDIO_SD + * @brief This file provides all the SD Card driver firmware functions. + * @{ + */ + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Types + * @{ + */ +/** + * @} + */ + + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Defines + * @{ + */ + +/** + * @brief SDIO Static flags, TimeOut, FIFO Address + */ +#define SDIO_STATIC_FLAGS ((uint32_t)0x000005FF) +#define SDIO_CMD0TIMEOUT ((uint32_t)0x00010000) + +/** + * @brief Mask for errors Card Status R1 (OCR Register) + */ +#define SD_OCR_ADDR_OUT_OF_RANGE ((uint32_t)0x80000000) +#define SD_OCR_ADDR_MISALIGNED ((uint32_t)0x40000000) +#define SD_OCR_BLOCK_LEN_ERR ((uint32_t)0x20000000) +#define SD_OCR_ERASE_SEQ_ERR ((uint32_t)0x10000000) +#define SD_OCR_BAD_ERASE_PARAM ((uint32_t)0x08000000) +#define SD_OCR_WRITE_PROT_VIOLATION ((uint32_t)0x04000000) +#define SD_OCR_LOCK_UNLOCK_FAILED ((uint32_t)0x01000000) +#define SD_OCR_COM_CRC_FAILED ((uint32_t)0x00800000) +#define SD_OCR_ILLEGAL_CMD ((uint32_t)0x00400000) +#define SD_OCR_CARD_ECC_FAILED ((uint32_t)0x00200000) +#define SD_OCR_CC_ERROR ((uint32_t)0x00100000) +#define SD_OCR_GENERAL_UNKNOWN_ERROR ((uint32_t)0x00080000) +#define SD_OCR_STREAM_READ_UNDERRUN ((uint32_t)0x00040000) +#define SD_OCR_STREAM_WRITE_OVERRUN ((uint32_t)0x00020000) +#define SD_OCR_CID_CSD_OVERWRIETE ((uint32_t)0x00010000) +#define SD_OCR_WP_ERASE_SKIP ((uint32_t)0x00008000) +#define SD_OCR_CARD_ECC_DISABLED ((uint32_t)0x00004000) +#define SD_OCR_ERASE_RESET ((uint32_t)0x00002000) +#define SD_OCR_AKE_SEQ_ERROR ((uint32_t)0x00000008) +#define SD_OCR_ERRORBITS ((uint32_t)0xFDFFE008) + +/** + * @brief Masks for R6 Response + */ +#define SD_R6_GENERAL_UNKNOWN_ERROR ((uint32_t)0x00002000) +#define SD_R6_ILLEGAL_CMD ((uint32_t)0x00004000) +#define SD_R6_COM_CRC_FAILED ((uint32_t)0x00008000) + +#define SD_VOLTAGE_WINDOW_SD ((uint32_t)0x80100000) +#define SD_HIGH_CAPACITY ((uint32_t)0x40000000) +#define SD_STD_CAPACITY ((uint32_t)0x00000000) +#define SD_CHECK_PATTERN ((uint32_t)0x000001AA) + +#define SD_MAX_VOLT_TRIAL ((uint32_t)0x0000FFFF) +#define SD_ALLZERO ((uint32_t)0x00000000) + +#define SD_WIDE_BUS_SUPPORT ((uint32_t)0x00040000) +#define SD_SINGLE_BUS_SUPPORT ((uint32_t)0x00010000) +#define SD_CARD_LOCKED ((uint32_t)0x02000000) + +#define SD_DATATIMEOUT ((uint32_t)0xFFFFFFFF) +#define SD_0TO7BITS ((uint32_t)0x000000FF) +#define SD_8TO15BITS ((uint32_t)0x0000FF00) +#define SD_16TO23BITS ((uint32_t)0x00FF0000) +#define SD_24TO31BITS ((uint32_t)0xFF000000) +#define SD_MAX_DATA_LENGTH ((uint32_t)0x01FFFFFF) + +#define SD_HALFFIFO ((uint32_t)0x00000008) +#define SD_HALFFIFOBYTES ((uint32_t)0x00000020) + +/** + * @brief Command Class Supported + */ +#define SD_CCCC_LOCK_UNLOCK ((uint32_t)0x00000080) +#define SD_CCCC_WRITE_PROT ((uint32_t)0x00000040) +#define SD_CCCC_ERASE ((uint32_t)0x00000020) + +/** + * @brief Following commands are SD Card Specific commands. + * SDIO_APP_CMD should be sent before sending these commands. + */ +#define SDIO_SEND_IF_COND ((uint32_t)0x00000008) + +/** + * @} + */ + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Macros + * @{ + */ +/** + * @} + */ + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Variables + * @{ + */ + +static uint32_t CardType = SDIO_STD_CAPACITY_SD_CARD_V1_1; +static uint32_t CSD_Tab[4], CID_Tab[4], RCA = 0; +static uint8_t SDSTATUS_Tab[16]; +__IO uint32_t StopCondition = 0; +__IO SD_Error TransferError = SD_OK; +__IO uint32_t TransferEnd = 0, DMAEndOfTransfer = 0; +SD_CardInfo SDCardInfo; + +SDIO_InitTypeDef SDIO_InitStructure; +SDIO_CmdInitTypeDef SDIO_CmdInitStructure; +SDIO_DataInitTypeDef SDIO_DataInitStructure; +/** + * @} + */ + + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Function_Prototypes + * @{ + */ +static SD_Error CmdError(void); +static SD_Error CmdResp1Error(uint8_t cmd); +static SD_Error CmdResp7Error(void); +static SD_Error CmdResp3Error(void); +static SD_Error CmdResp2Error(void); +static SD_Error CmdResp6Error(uint8_t cmd, uint16_t *prca); +static SD_Error SDEnWideBus(FunctionalState NewState); +static SD_Error IsCardProgramming(uint8_t *pstatus); +static SD_Error FindSCR(uint16_t rca, uint32_t *pscr); +uint8_t convert_from_bytes_to_power_of_two(uint16_t NumberOfBytes); + +/** + * @brief DeInitializes the SDIO interface. + * @param None + * @retval None + */ +void SD_LowLevel_DeInit(void) +{ + GPIO_InitTypeDef GPIO_InitStructure; + + /*!< Disable SDIO Clock */ + SDIO_ClockCmd(DISABLE); + + /*!< Set Power State to OFF */ + SDIO_SetPowerState(SDIO_PowerState_OFF); + + /*!< DeInitializes the SDIO peripheral */ + SDIO_DeInit(); + + /* Disable the SDIO APB2 Clock */ + RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, DISABLE); + + GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_MCO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_MCO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_MCO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_MCO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_MCO); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_MCO); + + /* Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(GPIOC, &GPIO_InitStructure); + + /* Configure PD.02 CMD line */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; + GPIO_Init(GPIOD, &GPIO_InitStructure); + + /* Configure PC.12 pin: CLK pin */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; + GPIO_Init(GPIOC, &GPIO_InitStructure); +} + +/** + * @brief Initializes the SD Card and put it into StandBy State (Ready for + * data transfer). + * @param None + * @retval None + */ +void SD_LowLevel_Init(void) +{ + GPIO_InitTypeDef GPIO_InitStructure; + + RCC_AHB1PeriphClockCmd(SD_RCC_AHB1Periph, ENABLE); + + GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_SDIO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_SDIO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SDIO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SDIO); + GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SDIO); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_SDIO); + + /* Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(GPIOC, &GPIO_InitStructure); + + /* Configure PD.02 CMD line */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; + GPIO_Init(GPIOD, &GPIO_InitStructure); + + /* Configure PC.12 pin: CLK pin */ + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(GPIOC, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = SD_PRESENT_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(SD_PRESENT_GPIO, &GPIO_InitStructure); + + /* Enable the SDIO APB2 Clock */ + RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE); + + // SDIO Interrupt ENABLE + NVIC_InitTypeDef NVIC_InitStructure; + NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure); + // DMA2 STREAMx Interrupt ENABLE + NVIC_InitStructure.NVIC_IRQChannel = SD_SDIO_DMA_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; + NVIC_Init(&NVIC_InitStructure); +} + +/** + * @brief Configures the DMA2 Channel4 for SDIO Tx request. + * @param BufferSRC: pointer to the source buffer + * @param BufferSize: buffer size + * @retval None + */ +void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize) +{ + DMA_InitTypeDef SDDMA_InitStructure; + + DMA_ClearFlag(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_FEIF | SD_SDIO_DMA_FLAG_DMEIF | SD_SDIO_DMA_FLAG_TEIF | SD_SDIO_DMA_FLAG_HTIF | SD_SDIO_DMA_FLAG_TCIF); + + /* DMA2 Stream3 or Stream6 disable */ + DMA_Cmd(SD_SDIO_DMA_STREAM, DISABLE); + + /* DMA2 Stream3 or Stream6 Config */ + DMA_DeInit(SD_SDIO_DMA_STREAM); + + SDDMA_InitStructure.DMA_Channel = SD_SDIO_DMA_CHANNEL; + SDDMA_InitStructure.DMA_PeripheralBaseAddr = SD_SDIO_FIFO_ADDRESS; + SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferSRC; + SDDMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; + SDDMA_InitStructure.DMA_BufferSize = 0; + SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; + SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; + SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; + SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; + SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal; + SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; + SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; + SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; + SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4; + SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4; + DMA_Init(SD_SDIO_DMA_STREAM, &SDDMA_InitStructure); + DMA_ITConfig(SD_SDIO_DMA_STREAM, DMA_IT_TC, ENABLE); + DMA_FlowControllerConfig(SD_SDIO_DMA_STREAM, DMA_FlowCtrl_Peripheral); + + /* DMA2 Stream3 or Stream6 enable */ + DMA_Cmd(SD_SDIO_DMA_STREAM, ENABLE); + +} + +/** + * @brief Configures the DMA2 Channel4 for SDIO Rx request. + * @param BufferDST: pointer to the destination buffer + * @param BufferSize: buffer size + * @retval None + */ +void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize) +{ + DMA_InitTypeDef SDDMA_InitStructure; + + DMA_ClearFlag(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_FEIF | SD_SDIO_DMA_FLAG_DMEIF | SD_SDIO_DMA_FLAG_TEIF | SD_SDIO_DMA_FLAG_HTIF | SD_SDIO_DMA_FLAG_TCIF); + + /* DMA2 Stream3 or Stream6 disable */ + DMA_Cmd(SD_SDIO_DMA_STREAM, DISABLE); + + /* DMA2 Stream3 or Stream6 Config */ + DMA_DeInit(SD_SDIO_DMA_STREAM); + + SDDMA_InitStructure.DMA_Channel = SD_SDIO_DMA_CHANNEL; + SDDMA_InitStructure.DMA_PeripheralBaseAddr = SD_SDIO_FIFO_ADDRESS; + SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferDST; + SDDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; + SDDMA_InitStructure.DMA_BufferSize = 0; + SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; + SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; + SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; + SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; + SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal; + SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; + SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; + SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; + SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4; + SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4; + DMA_Init(SD_SDIO_DMA_STREAM, &SDDMA_InitStructure); + DMA_ITConfig(SD_SDIO_DMA_STREAM, DMA_IT_TC, ENABLE); + DMA_FlowControllerConfig(SD_SDIO_DMA_STREAM, DMA_FlowCtrl_Peripheral); + + /* DMA2 Stream3 or Stream6 enable */ + DMA_Cmd(SD_SDIO_DMA_STREAM, ENABLE); +} + + +/** + * @} + */ + + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Functions + * @{ + */ + +/** + * @brief DeInitializes the SDIO interface. + * @param None + * @retval None + */ +void SD_DeInit(void) +{ + SD_LowLevel_DeInit(); +} + +/** + * @brief Initializes the SD Card and put it into StandBy State (Ready for data + * transfer). + * @param None + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_Init(void) +{ + __IO SD_Error errorstatus = SD_OK; + + /* SDIO Peripheral Low Level Init */ + SD_LowLevel_Init(); + + SDIO_DeInit(); + + errorstatus = SD_PowerON(); + if (errorstatus != SD_OK) + { + TRACE("SD_PowerON() status=%d", errorstatus); + /*!< CMD Response TimeOut (wait for CMDSENT flag) */ + return(errorstatus); + } + + errorstatus = SD_InitializeCards(); + if (errorstatus != SD_OK) + { + TRACE("SD_InitializeCards() status=%d", errorstatus); + /*!< CMD Response TimeOut (wait for CMDSENT flag) */ + return(errorstatus); + } + + /*!< Configure the SDIO peripheral */ + /*!< SDIO_CK = SDIOCLK / (SDIO_TRANSFER_CLK_DIV + 2) */ + /*!< on STM32F4xx devices, SDIOCLK is fixed to 48MHz */ + SDIO_InitStructure.SDIO_ClockDiv = SD_SDIO_TRANSFER_CLK_DIV; + SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising; + SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; + SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; + SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b; + SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; + SDIO_Init(&SDIO_InitStructure); + + /*----------------- Read CSD/CID MSD registers ------------------*/ + errorstatus = SD_GetCardInfo(&SDCardInfo); + + if (errorstatus == SD_OK) + { + /*----------------- Select Card --------------------------------*/ + errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16)); + } + + if (errorstatus == SD_OK) + { + errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b); + } + + return(errorstatus); +} + +/** + * @brief Gets the cuurent sd card data transfer status. + * @param None + * @retval SDTransferState: Data Transfer state. + * This value can be: + * - SD_TRANSFER_OK: No data transfer is acting + * - SD_TRANSFER_BUSY: Data transfer is acting + */ +SDTransferState SD_GetStatus(void) +{ + SDCardState cardstate = SD_CARD_TRANSFER; + + cardstate = SD_GetState(); + + if (cardstate == SD_CARD_TRANSFER) + { + return(SD_TRANSFER_OK); + } + else if(cardstate == SD_CARD_ERROR) + { + return (SD_TRANSFER_ERROR); + } + else + { + return(SD_TRANSFER_BUSY); + } +} + +/** + * @brief Returns the current card's state. + * @param None + * @retval SDCardState: SD Card Error or SD Card Current State. + */ +SDCardState SD_GetState(void) +{ + uint32_t resp1 = 0; + + if(SD_Detect()== SD_PRESENT) + { + if (SD_SendStatus(&resp1) != SD_OK) + { + return SD_CARD_ERROR; + } + else + { + return (SDCardState)((resp1 >> 9) & 0x0F); + } + } + else + { + return SD_CARD_ERROR; + } +} + +/** + * @brief Detect if SD card is correctly plugged in the memory slot. + * @param None + * @retval Return if SD is detected or not + */ +uint8_t SD_Detect(void) +{ + __IO uint8_t status = SD_PRESENT; + +#if 0 // TODO + /*!< Check GPIO to detect SD */ + if (GPIO_ReadInputDataBit(SD_DETECT_GPIO_PORT, SD_DETECT_PIN) != Bit_RESET) + { + status = SD_NOT_PRESENT; + } +#endif + + return status; +} + +/** + * @brief Enquires cards about their operating voltage and configures + * clock controls. + * @param None + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_PowerON(void) +{ + __IO SD_Error errorstatus = SD_OK; + uint32_t response = 0, count = 0, validvoltage = 0; + uint32_t SDType = SD_STD_CAPACITY; + + /*!< Power ON Sequence -----------------------------------------------------*/ + /*!< Configure the SDIO peripheral */ + /*!< SDIO_CK = SDIOCLK / (SDIO_INIT_CLK_DIV + 2) */ + /*!< on STM32F4xx devices, SDIOCLK is fixed to 48MHz */ + /*!< SDIO_CK for initialization should not exceed 400 KHz */ + SDIO_InitStructure.SDIO_ClockDiv = SD_SDIO_INIT_CLK_DIV; + SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising; + SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; + SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; + SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b; + SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; + SDIO_Init(&SDIO_InitStructure); + + /*!< Set Power State to ON */ + SDIO_SetPowerState(SDIO_PowerState_ON); + + /*!< Enable SDIO Clock */ + SDIO_ClockCmd(ENABLE); + + /*!< CMD0: GO_IDLE_STATE ---------------------------------------------------*/ + /*!< No CMD response required */ + SDIO_CmdInitStructure.SDIO_Argument = 0x0; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdError(); + + if (errorstatus != SD_OK) + { + TRACE("SD_CMD_GO_IDLE_STATE status=%d", errorstatus); + /*!< CMD Response TimeOut (wait for CMDSENT flag) */ + return(errorstatus); + } + + /*!< CMD8: SEND_IF_COND ----------------------------------------------------*/ + /*!< Send CMD8 to verify SD card interface operating condition */ + /*!< Argument: - [31:12]: Reserved (shall be set to '0') + - [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V) + - [7:0]: Check Pattern (recommended 0xAA) */ + /*!< CMD Response: R7 */ + SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN; + SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp7Error(); + + if (errorstatus == SD_OK) + { + CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0; /*!< SD Card 2.0 */ + SDType = SD_HIGH_CAPACITY; + } + else + { + /*!< CMD55 */ + SDIO_CmdInitStructure.SDIO_Argument = 0x00; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + } + /*!< CMD55 */ + SDIO_CmdInitStructure.SDIO_Argument = 0x00; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + /*!< If errorstatus is Command TimeOut, it is a MMC card */ + /*!< If errorstatus is SD_OK it is a SD card: SD card 2.0 (voltage range mismatch) + or SD card 1.x */ + if (errorstatus == SD_OK) + { + /*!< SD CARD */ + /*!< Send ACMD41 SD_APP_OP_COND with Argument 0x80100000 */ + while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL)) + { + + /*!< SEND CMD55 APP_CMD with RCA as 0 */ + SDIO_CmdInitStructure.SDIO_Argument = 0x00; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + if (errorstatus != SD_OK) + { + TRACE("SD_CMD_APP_CMD status=%d", errorstatus); + return(errorstatus); + } + SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp3Error(); + if (errorstatus != SD_OK) + { + TRACE("SD_CMD_SD_APP_OP_COND status=%d", errorstatus); + return(errorstatus); + } + + response = SDIO_GetResponse(SDIO_RESP1); + validvoltage = (((response >> 31) == 1) ? 1 : 0); + count++; + } + if (count >= SD_MAX_VOLT_TRIAL) + { + errorstatus = SD_INVALID_VOLTRANGE; + return(errorstatus); + } + + if (response &= SD_HIGH_CAPACITY) + { + CardType = SDIO_HIGH_CAPACITY_SD_CARD; + } + + }/*!< else MMC Card */ + + return(errorstatus); +} + +/** + * @brief Turns the SDIO output signals off. + * @param None + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_PowerOFF(void) +{ + SD_Error errorstatus = SD_OK; + + /*!< Set Power State to OFF */ + SDIO_SetPowerState(SDIO_PowerState_OFF); + + return(errorstatus); +} + +/** + * @brief Intialises all cards or single card as the case may be Card(s) come + * into standby state. + * @param None + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_InitializeCards(void) +{ + SD_Error errorstatus = SD_OK; + uint16_t rca = 0x01; + + if (SDIO_GetPowerState() == SDIO_PowerState_OFF) + { + errorstatus = SD_REQUEST_NOT_APPLICABLE; + return(errorstatus); + } + + if (SDIO_SECURE_DIGITAL_IO_CARD != CardType) + { + /*!< Send CMD2 ALL_SEND_CID */ + SDIO_CmdInitStructure.SDIO_Argument = 0x0; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp2Error(); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1); + CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2); + CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3); + CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4); + } + if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType) + || (SDIO_HIGH_CAPACITY_SD_CARD == CardType)) + { + /*!< Send CMD3 SET_REL_ADDR with argument 0 */ + /*!< SD Card publishes its RCA. */ + SDIO_CmdInitStructure.SDIO_Argument = 0x00; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + } + + if (SDIO_SECURE_DIGITAL_IO_CARD != CardType) + { + RCA = rca; + + /*!< Send CMD9 SEND_CSD with argument as card's RCA */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16); + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp2Error(); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1); + CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2); + CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3); + CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4); + } + + errorstatus = SD_OK; /*!< All cards get intialized */ + + return(errorstatus); +} + +/** + * @brief Returns information about specific card. + * @param cardinfo: pointer to a SD_CardInfo structure that contains all SD card + * information. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo) +{ + SD_Error errorstatus = SD_OK; + uint8_t tmp = 0; + + cardinfo->CardType = (uint8_t)CardType; + cardinfo->RCA = (uint16_t)RCA; + + /*!< Byte 0 */ + tmp = (uint8_t)((CSD_Tab[0] & 0xFF000000) >> 24); + cardinfo->SD_csd.CSDStruct = (tmp & 0xC0) >> 6; + cardinfo->SD_csd.SysSpecVersion = (tmp & 0x3C) >> 2; + cardinfo->SD_csd.Reserved1 = tmp & 0x03; + + /*!< Byte 1 */ + tmp = (uint8_t)((CSD_Tab[0] & 0x00FF0000) >> 16); + cardinfo->SD_csd.TAAC = tmp; + + /*!< Byte 2 */ + tmp = (uint8_t)((CSD_Tab[0] & 0x0000FF00) >> 8); + cardinfo->SD_csd.NSAC = tmp; + + /*!< Byte 3 */ + tmp = (uint8_t)(CSD_Tab[0] & 0x000000FF); + cardinfo->SD_csd.MaxBusClkFrec = tmp; + + /*!< Byte 4 */ + tmp = (uint8_t)((CSD_Tab[1] & 0xFF000000) >> 24); + cardinfo->SD_csd.CardComdClasses = tmp << 4; + + /*!< Byte 5 */ + tmp = (uint8_t)((CSD_Tab[1] & 0x00FF0000) >> 16); + cardinfo->SD_csd.CardComdClasses |= (tmp & 0xF0) >> 4; + cardinfo->SD_csd.RdBlockLen = tmp & 0x0F; + + /*!< Byte 6 */ + tmp = (uint8_t)((CSD_Tab[1] & 0x0000FF00) >> 8); + cardinfo->SD_csd.PartBlockRead = (tmp & 0x80) >> 7; + cardinfo->SD_csd.WrBlockMisalign = (tmp & 0x40) >> 6; + cardinfo->SD_csd.RdBlockMisalign = (tmp & 0x20) >> 5; + cardinfo->SD_csd.DSRImpl = (tmp & 0x10) >> 4; + cardinfo->SD_csd.Reserved2 = 0; /*!< Reserved */ + + if ((CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1) || (CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0)) + { + cardinfo->SD_csd.DeviceSize = (tmp & 0x03) << 10; + + /*!< Byte 7 */ + tmp = (uint8_t)(CSD_Tab[1] & 0x000000FF); + cardinfo->SD_csd.DeviceSize |= (tmp) << 2; + + /*!< Byte 8 */ + tmp = (uint8_t)((CSD_Tab[2] & 0xFF000000) >> 24); + cardinfo->SD_csd.DeviceSize |= (tmp & 0xC0) >> 6; + + cardinfo->SD_csd.MaxRdCurrentVDDMin = (tmp & 0x38) >> 3; + cardinfo->SD_csd.MaxRdCurrentVDDMax = (tmp & 0x07); + + /*!< Byte 9 */ + tmp = (uint8_t)((CSD_Tab[2] & 0x00FF0000) >> 16); + cardinfo->SD_csd.MaxWrCurrentVDDMin = (tmp & 0xE0) >> 5; + cardinfo->SD_csd.MaxWrCurrentVDDMax = (tmp & 0x1C) >> 2; + cardinfo->SD_csd.DeviceSizeMul = (tmp & 0x03) << 1; + /*!< Byte 10 */ + tmp = (uint8_t)((CSD_Tab[2] & 0x0000FF00) >> 8); + cardinfo->SD_csd.DeviceSizeMul |= (tmp & 0x80) >> 7; + + cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) ; + cardinfo->CardCapacity *= (1 << (cardinfo->SD_csd.DeviceSizeMul + 2)); + cardinfo->CardBlockSize = 1 << (cardinfo->SD_csd.RdBlockLen); + cardinfo->CardCapacity *= cardinfo->CardBlockSize; + } + else if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + { + /*!< Byte 7 */ + tmp = (uint8_t)(CSD_Tab[1] & 0x000000FF); + cardinfo->SD_csd.DeviceSize = (tmp & 0x3F) << 16; + + /*!< Byte 8 */ + tmp = (uint8_t)((CSD_Tab[2] & 0xFF000000) >> 24); + + cardinfo->SD_csd.DeviceSize |= (tmp << 8); + + /*!< Byte 9 */ + tmp = (uint8_t)((CSD_Tab[2] & 0x00FF0000) >> 16); + + cardinfo->SD_csd.DeviceSize |= (tmp); + + /*!< Byte 10 */ + tmp = (uint8_t)((CSD_Tab[2] & 0x0000FF00) >> 8); + + cardinfo->CardCapacity = (uint64_t)(cardinfo->SD_csd.DeviceSize + 1) * (uint64_t)(512 * 1024); + cardinfo->CardBlockSize = 512; + } + + + cardinfo->SD_csd.EraseGrSize = (tmp & 0x40) >> 6; + cardinfo->SD_csd.EraseGrMul = (tmp & 0x3F) << 1; + + /*!< Byte 11 */ + tmp = (uint8_t)(CSD_Tab[2] & 0x000000FF); + cardinfo->SD_csd.EraseGrMul |= (tmp & 0x80) >> 7; + cardinfo->SD_csd.WrProtectGrSize = (tmp & 0x7F); + + /*!< Byte 12 */ + tmp = (uint8_t)((CSD_Tab[3] & 0xFF000000) >> 24); + cardinfo->SD_csd.WrProtectGrEnable = (tmp & 0x80) >> 7; + cardinfo->SD_csd.ManDeflECC = (tmp & 0x60) >> 5; + cardinfo->SD_csd.WrSpeedFact = (tmp & 0x1C) >> 2; + cardinfo->SD_csd.MaxWrBlockLen = (tmp & 0x03) << 2; + + /*!< Byte 13 */ + tmp = (uint8_t)((CSD_Tab[3] & 0x00FF0000) >> 16); + cardinfo->SD_csd.MaxWrBlockLen |= (tmp & 0xC0) >> 6; + cardinfo->SD_csd.WriteBlockPaPartial = (tmp & 0x20) >> 5; + cardinfo->SD_csd.Reserved3 = 0; + cardinfo->SD_csd.ContentProtectAppli = (tmp & 0x01); + + /*!< Byte 14 */ + tmp = (uint8_t)((CSD_Tab[3] & 0x0000FF00) >> 8); + cardinfo->SD_csd.FileFormatGrouop = (tmp & 0x80) >> 7; + cardinfo->SD_csd.CopyFlag = (tmp & 0x40) >> 6; + cardinfo->SD_csd.PermWrProtect = (tmp & 0x20) >> 5; + cardinfo->SD_csd.TempWrProtect = (tmp & 0x10) >> 4; + cardinfo->SD_csd.FileFormat = (tmp & 0x0C) >> 2; + cardinfo->SD_csd.ECC = (tmp & 0x03); + + /*!< Byte 15 */ + tmp = (uint8_t)(CSD_Tab[3] & 0x000000FF); + cardinfo->SD_csd.CSD_CRC = (tmp & 0xFE) >> 1; + cardinfo->SD_csd.Reserved4 = 1; + + + /*!< Byte 0 */ + tmp = (uint8_t)((CID_Tab[0] & 0xFF000000) >> 24); + cardinfo->SD_cid.ManufacturerID = tmp; + + /*!< Byte 1 */ + tmp = (uint8_t)((CID_Tab[0] & 0x00FF0000) >> 16); + cardinfo->SD_cid.OEM_AppliID = tmp << 8; + + /*!< Byte 2 */ + tmp = (uint8_t)((CID_Tab[0] & 0x000000FF00) >> 8); + cardinfo->SD_cid.OEM_AppliID |= tmp; + + /*!< Byte 3 */ + tmp = (uint8_t)(CID_Tab[0] & 0x000000FF); + cardinfo->SD_cid.ProdName1 = tmp << 24; + + /*!< Byte 4 */ + tmp = (uint8_t)((CID_Tab[1] & 0xFF000000) >> 24); + cardinfo->SD_cid.ProdName1 |= tmp << 16; + + /*!< Byte 5 */ + tmp = (uint8_t)((CID_Tab[1] & 0x00FF0000) >> 16); + cardinfo->SD_cid.ProdName1 |= tmp << 8; + + /*!< Byte 6 */ + tmp = (uint8_t)((CID_Tab[1] & 0x0000FF00) >> 8); + cardinfo->SD_cid.ProdName1 |= tmp; + + /*!< Byte 7 */ + tmp = (uint8_t)(CID_Tab[1] & 0x000000FF); + cardinfo->SD_cid.ProdName2 = tmp; + + /*!< Byte 8 */ + tmp = (uint8_t)((CID_Tab[2] & 0xFF000000) >> 24); + cardinfo->SD_cid.ProdRev = tmp; + + /*!< Byte 9 */ + tmp = (uint8_t)((CID_Tab[2] & 0x00FF0000) >> 16); + cardinfo->SD_cid.ProdSN = tmp << 24; + + /*!< Byte 10 */ + tmp = (uint8_t)((CID_Tab[2] & 0x0000FF00) >> 8); + cardinfo->SD_cid.ProdSN |= tmp << 16; + + /*!< Byte 11 */ + tmp = (uint8_t)(CID_Tab[2] & 0x000000FF); + cardinfo->SD_cid.ProdSN |= tmp << 8; + + /*!< Byte 12 */ + tmp = (uint8_t)((CID_Tab[3] & 0xFF000000) >> 24); + cardinfo->SD_cid.ProdSN |= tmp; + + /*!< Byte 13 */ + tmp = (uint8_t)((CID_Tab[3] & 0x00FF0000) >> 16); + cardinfo->SD_cid.Reserved1 |= (tmp & 0xF0) >> 4; + cardinfo->SD_cid.ManufactDate = (tmp & 0x0F) << 8; + + /*!< Byte 14 */ + tmp = (uint8_t)((CID_Tab[3] & 0x0000FF00) >> 8); + cardinfo->SD_cid.ManufactDate |= tmp; + + /*!< Byte 15 */ + tmp = (uint8_t)(CID_Tab[3] & 0x000000FF); + cardinfo->SD_cid.CID_CRC = (tmp & 0xFE) >> 1; + cardinfo->SD_cid.Reserved2 = 1; + + return(errorstatus); +} + +/** + * @brief Enables wide bus opeartion for the requeseted card if supported by + * card. + * @param WideMode: Specifies the SD card wide bus mode. + * This parameter can be one of the following values: + * @arg SDIO_BusWide_8b: 8-bit data transfer (Only for MMC) + * @arg SDIO_BusWide_4b: 4-bit data transfer + * @arg SDIO_BusWide_1b: 1-bit data transfer + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_GetCardStatus(SD_CardStatus *cardstatus) +{ + SD_Error errorstatus = SD_OK; + uint8_t tmp = 0; + + errorstatus = SD_SendSDStatus((uint32_t *)SDSTATUS_Tab); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + /*!< Byte 0 */ + tmp = (uint8_t)((SDSTATUS_Tab[0] & 0xC0) >> 6); + cardstatus->DAT_BUS_WIDTH = tmp; + + /*!< Byte 0 */ + tmp = (uint8_t)((SDSTATUS_Tab[0] & 0x20) >> 5); + cardstatus->SECURED_MODE = tmp; + + /*!< Byte 2 */ + tmp = (uint8_t)((SDSTATUS_Tab[2] & 0xFF)); + cardstatus->SD_CARD_TYPE = tmp << 8; + + /*!< Byte 3 */ + tmp = (uint8_t)((SDSTATUS_Tab[3] & 0xFF)); + cardstatus->SD_CARD_TYPE |= tmp; + + /*!< Byte 4 */ + tmp = (uint8_t)(SDSTATUS_Tab[4] & 0xFF); + cardstatus->SIZE_OF_PROTECTED_AREA = tmp << 24; + + /*!< Byte 5 */ + tmp = (uint8_t)(SDSTATUS_Tab[5] & 0xFF); + cardstatus->SIZE_OF_PROTECTED_AREA |= tmp << 16; + + /*!< Byte 6 */ + tmp = (uint8_t)(SDSTATUS_Tab[6] & 0xFF); + cardstatus->SIZE_OF_PROTECTED_AREA |= tmp << 8; + + /*!< Byte 7 */ + tmp = (uint8_t)(SDSTATUS_Tab[7] & 0xFF); + cardstatus->SIZE_OF_PROTECTED_AREA |= tmp; + + /*!< Byte 8 */ + tmp = (uint8_t)((SDSTATUS_Tab[8] & 0xFF)); + cardstatus->SPEED_CLASS = tmp; + + /*!< Byte 9 */ + tmp = (uint8_t)((SDSTATUS_Tab[9] & 0xFF)); + cardstatus->PERFORMANCE_MOVE = tmp; + + /*!< Byte 10 */ + tmp = (uint8_t)((SDSTATUS_Tab[10] & 0xF0) >> 4); + cardstatus->AU_SIZE = tmp; + + /*!< Byte 11 */ + tmp = (uint8_t)(SDSTATUS_Tab[11] & 0xFF); + cardstatus->ERASE_SIZE = tmp << 8; + + /*!< Byte 12 */ + tmp = (uint8_t)(SDSTATUS_Tab[12] & 0xFF); + cardstatus->ERASE_SIZE |= tmp; + + /*!< Byte 13 */ + tmp = (uint8_t)((SDSTATUS_Tab[13] & 0xFC) >> 2); + cardstatus->ERASE_TIMEOUT = tmp; + + /*!< Byte 13 */ + tmp = (uint8_t)((SDSTATUS_Tab[13] & 0x3)); + cardstatus->ERASE_OFFSET = tmp; + + return(errorstatus); +} + +/** + * @brief Enables wide bus opeartion for the requeseted card if supported by + * card. + * @param WideMode: Specifies the SD card wide bus mode. + * This parameter can be one of the following values: + * @arg SDIO_BusWide_8b: 8-bit data transfer (Only for MMC) + * @arg SDIO_BusWide_4b: 4-bit data transfer + * @arg SDIO_BusWide_1b: 1-bit data transfer + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_EnableWideBusOperation(uint32_t WideMode) +{ + SD_Error errorstatus = SD_OK; + + /*!< MMC Card doesn't support this feature */ + if (SDIO_MULTIMEDIA_CARD == CardType) + { + errorstatus = SD_UNSUPPORTED_FEATURE; + return(errorstatus); + } + else if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_HIGH_CAPACITY_SD_CARD == CardType)) + { + if (SDIO_BusWide_8b == WideMode) + { + errorstatus = SD_UNSUPPORTED_FEATURE; + return(errorstatus); + } + else if (SDIO_BusWide_4b == WideMode) + { + errorstatus = SDEnWideBus(ENABLE); + + if (SD_OK == errorstatus) + { + /*!< Configure the SDIO peripheral */ + SDIO_InitStructure.SDIO_ClockDiv = SD_SDIO_TRANSFER_CLK_DIV; + SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising; + SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; + SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; + SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_4b; + SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; + SDIO_Init(&SDIO_InitStructure); + } + } + else + { + errorstatus = SDEnWideBus(DISABLE); + + if (SD_OK == errorstatus) + { + /*!< Configure the SDIO peripheral */ + SDIO_InitStructure.SDIO_ClockDiv = SD_SDIO_TRANSFER_CLK_DIV; + SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising; + SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; + SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; + SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b; + SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; + SDIO_Init(&SDIO_InitStructure); + } + } + } + + return(errorstatus); +} + +/** + * @brief Selects od Deselects the corresponding card. + * @param addr: Address of the Card to be selected. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_SelectDeselect(uint32_t addr) +{ + SD_Error errorstatus = SD_OK; + + /*!< Send CMD7 SDIO_SEL_DESEL_CARD */ + SDIO_CmdInitStructure.SDIO_Argument = addr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEL_DESEL_CARD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SEL_DESEL_CARD); + + return(errorstatus); +} + +/** + * @brief Allows to read one block from a specified address in a card. The Data + * transfer can be managed by DMA mode or Polling mode. + * @note This operation should be followed by two functions to check if the + * DMA Controller and SD Card status. + * - SD_ReadWaitOperation(): this function insure that the DMA + * controller has finished all data transfer. + * - SD_GetStatus(): to check that the SD Card has finished the + * data transfer and it is ready for data. + * @param readbuff: pointer to the buffer that will contain the received data + * @param ReadAddr: Address from where data are to be read. + * @param BlockSize: the SD card Data block size. The Block size should be 512. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize) +{ + SD_Error errorstatus = SD_OK; +#if defined (SD_POLLING_MODE) + uint32_t count = 0, *tempbuff = (uint32_t *)readbuff; +#endif + + TransferError = SD_OK; + TransferEnd = 0; + StopCondition = 0; + + SDIO->DCTRL = 0x0; + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + { + BlockSize = 512; + ReadAddr /= 512; + } + + /* Set Block Size for Card */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = BlockSize; + SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + /*!< Send CMD17 READ_SINGLE_BLOCK */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + +#if defined (SD_POLLING_MODE) + /*!< In case of single block transfer, no need of stop transfer at all.*/ + /*!< Polling mode */ + while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR))) + { + if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET) + { + for (count = 0; count < 8; count++) + { + *(tempbuff + count) = SDIO_ReadData(); + } + tempbuff += 8; + } + } + + if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT); + errorstatus = SD_DATA_TIMEOUT; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL); + errorstatus = SD_DATA_CRC_FAIL; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_RXOVERR); + errorstatus = SD_RX_OVERRUN; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_STBITERR); + errorstatus = SD_START_BIT_ERR; + return(errorstatus); + } + count = SD_DATATIMEOUT; + while ((SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET) && (count > 0)) + { + *tempbuff = SDIO_ReadData(); + tempbuff++; + count--; + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + +#elif defined (SD_DMA_MODE) + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SDIO_DMACmd(ENABLE); + SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize); +#endif + + return(errorstatus); +} + +/** + * @brief Allows to read blocks from a specified address in a card. The Data + * transfer can be managed by DMA mode or Polling mode. + * @note This operation should be followed by two functions to check if the + * DMA Controller and SD Card status. + * - SD_ReadWaitOperation(): this function insure that the DMA + * controller has finished all data transfer. + * - SD_GetStatus(): to check that the SD Card has finished the + * data transfer and it is ready for data. + * @param readbuff: pointer to the buffer that will contain the received data. + * @param ReadAddr: Address from where data are to be read. + * @param BlockSize: the SD card Data block size. The Block size should be 512. + * @param NumberOfBlocks: number of blocks to be read. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks) +{ + SD_Error errorstatus = SD_OK; + TransferError = SD_OK; + TransferEnd = 0; + StopCondition = 1; + + SDIO->DCTRL = 0x0; + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + { + BlockSize = 512; + ReadAddr /= 512; + } + + /*!< Set Block Size for Card */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize; + SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + /*!< Send CMD18 READ_MULT_BLOCK with argument data address */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_MULT_BLOCK; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_READ_MULT_BLOCK); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SDIO_DMACmd(ENABLE); + SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, (NumberOfBlocks * BlockSize)); + + return(errorstatus); +} + +// FIXED Version where ReadAddr is in BLOCKS NOT BYTES, permits SDHC media >4GB + +SD_Error SD_ReadMultiBlocksFIXED(uint8_t *readbuff, uint32_t ReadAddr, uint32_t BlockSize, uint32_t NumberOfBlocks) +{ + SD_Error errorstatus = SD_OK; + TransferError = SD_OK; + TransferEnd = 0; + StopCondition = 1; + + SDIO->DCTRL = 0x0; + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + BlockSize = 512; + else + ReadAddr *= BlockSize; // Convert to Bytes for NON SDHC + + /*!< Set Block Size for Card */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize; + SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + /*!< Send CMD18 READ_MULT_BLOCK with argument data address */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_MULT_BLOCK; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_READ_MULT_BLOCK); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SDIO_DMACmd(ENABLE); + SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, (NumberOfBlocks * BlockSize)); + + return(errorstatus); +} + +/** + * @brief This function waits until the SDIO DMA data transfer is finished. + * This function should be called after SDIO_ReadMultiBlocks() function + * to insure that all data sent by the card are already transferred by + * the DMA controller. + * @param None. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_WaitReadOperation(void) +{ + SD_Error errorstatus = SD_OK; + uint32_t timeout; + + timeout = SD_DATATIMEOUT; + + while ((DMAEndOfTransfer == 0x00) && (TransferEnd == 0) && (TransferError == SD_OK) && (timeout > 0)) + { + timeout--; + } + + DMAEndOfTransfer = 0x00; + + timeout = SD_DATATIMEOUT; + + while(((SDIO->STA & SDIO_FLAG_RXACT)) && (timeout > 0)) + { + timeout--; + } + + if (StopCondition == 1) + { + errorstatus = SD_StopTransfer(); + } + + if ((timeout == 0) && (errorstatus == SD_OK)) + { + errorstatus = SD_DATA_TIMEOUT; + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + if (TransferError != SD_OK) + { + return(TransferError); + } + else + { + return(errorstatus); + } +} + +/** + * @brief Allows to write one block starting from a specified address in a card. + * The Data transfer can be managed by DMA mode or Polling mode. + * @note This operation should be followed by two functions to check if the + * DMA Controller and SD Card status. + * - SD_ReadWaitOperation(): this function insure that the DMA + * controller has finished all data transfer. + * - SD_GetStatus(): to check that the SD Card has finished the + * data transfer and it is ready for data. + * @param writebuff: pointer to the buffer that contain the data to be transferred. + * @param WriteAddr: Address from where data are to be read. + * @param BlockSize: the SD card Data block size. The Block size should be 512. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize) +{ + SD_Error errorstatus = SD_OK; + +#if defined (SD_POLLING_MODE) + uint32_t bytestransferred = 0, count = 0, restwords = 0; + uint32_t *tempbuff = (uint32_t *)writebuff; +#endif + + TransferError = SD_OK; + TransferEnd = 0; + StopCondition = 0; + + SDIO->DCTRL = 0x0; + + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + { + BlockSize = 512; + WriteAddr /= 512; + } + + /* Set Block Size for Card */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + /*!< Send CMD24 WRITE_SINGLE_BLOCK */ + SDIO_CmdInitStructure.SDIO_Argument = WriteAddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = BlockSize; + SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + /*!< In case of single data block transfer no need of stop command at all */ +#if defined (SD_POLLING_MODE) + while (!(SDIO->STA & (SDIO_FLAG_DBCKEND | SDIO_FLAG_TXUNDERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_STBITERR))) + { + if (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOHE) != RESET) + { + if ((512 - bytestransferred) < 32) + { + restwords = ((512 - bytestransferred) % 4 == 0) ? ((512 - bytestransferred) / 4) : (( 512 - bytestransferred) / 4 + 1); + for (count = 0; count < restwords; count++, tempbuff++, bytestransferred += 4) + { + SDIO_WriteData(*tempbuff); + } + } + else + { + for (count = 0; count < 8; count++) + { + SDIO_WriteData(*(tempbuff + count)); + } + tempbuff += 8; + bytestransferred += 32; + } + } + } + if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT); + errorstatus = SD_DATA_TIMEOUT; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL); + errorstatus = SD_DATA_CRC_FAIL; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_TXUNDERR); + errorstatus = SD_TX_UNDERRUN; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_STBITERR); + errorstatus = SD_START_BIT_ERR; + return(errorstatus); + } +#elif defined (SD_DMA_MODE) + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, BlockSize); + SDIO_DMACmd(ENABLE); +#endif + + return(errorstatus); +} + +/** + * @brief Allows to write blocks starting from a specified address in a card. + * The Data transfer can be managed by DMA mode only. + * @note This operation should be followed by two functions to check if the + * DMA Controller and SD Card status. + * - SD_ReadWaitOperation(): this function insure that the DMA + * controller has finished all data transfer. + * - SD_GetStatus(): to check that the SD Card has finished the + * data transfer and it is ready for data. + * @param WriteAddr: Address from where data are to be read. + * @param writebuff: pointer to the buffer that contain the data to be transferred. + * @param BlockSize: the SD card Data block size. The Block size should be 512. + * @param NumberOfBlocks: number of blocks to be written. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks) +{ + SD_Error errorstatus = SD_OK; + + TransferError = SD_OK; + TransferEnd = 0; + StopCondition = 1; + + SDIO->DCTRL = 0x0; + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + { + BlockSize = 512; + WriteAddr /= 512; + } + + /* Set Block Size for Card */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + /*!< To improve performance */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) (RCA << 16); + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + /*!< To improve performance */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)NumberOfBlocks; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCK_COUNT; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCK_COUNT); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + + /*!< Send CMD25 WRITE_MULT_BLOCK with argument data address */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_MULT_BLOCK; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_WRITE_MULT_BLOCK); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize; + SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SDIO_DMACmd(ENABLE); + SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize)); + + return(errorstatus); +} + +// FIXED Version where WriteAddr is in BLOCKS NOT BYTES, permits SDHC media >4GB + +SD_Error SD_WriteMultiBlocksFIXED(uint8_t *writebuff, uint32_t WriteAddr, uint32_t BlockSize, uint32_t NumberOfBlocks) +{ + SD_Error errorstatus = SD_OK; + + TransferError = SD_OK; + TransferEnd = 0; + StopCondition = 1; + + SDIO->DCTRL = 0x0; + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + BlockSize = 512; + else + WriteAddr *= BlockSize; // Convert to Bytes for NON SDHC + + /* Set Block Size for Card */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + /*!< To improve performance */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) (RCA << 16); + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + /*!< To improve performance */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)NumberOfBlocks; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCK_COUNT; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCK_COUNT); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + + /*!< Send CMD25 WRITE_MULT_BLOCK with argument data address */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_MULT_BLOCK; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_WRITE_MULT_BLOCK); + + if (SD_OK != errorstatus) + { + return(errorstatus); + } + + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize; + SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SDIO_DMACmd(ENABLE); + SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize)); + + return(errorstatus); +} + +/** + * @brief This function waits until the SDIO DMA data transfer is finished. + * This function should be called after SDIO_WriteBlock() and + * SDIO_WriteMultiBlocks() function to insure that all data sent by the + * card are already transferred by the DMA controller. + * @param None. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_WaitWriteOperation(void) +{ + SD_Error errorstatus = SD_OK; + uint32_t timeout; + + timeout = SD_DATATIMEOUT; + + while ((DMAEndOfTransfer == 0x00) && (TransferEnd == 0) && (TransferError == SD_OK) && (timeout > 0)) + { + timeout--; + } + + DMAEndOfTransfer = 0x00; + + timeout = SD_DATATIMEOUT; + + while(((SDIO->STA & SDIO_FLAG_TXACT)) && (timeout > 0)) + { + timeout--; + } + + if (StopCondition == 1) + { + errorstatus = SD_StopTransfer(); + } + + if ((timeout == 0) && (errorstatus == SD_OK)) + { + errorstatus = SD_DATA_TIMEOUT; + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + if (TransferError != SD_OK) + { + return(TransferError); + } + else + { + return(errorstatus); + } +} + +/** + * @brief Gets the cuurent data transfer state. + * @param None + * @retval SDTransferState: Data Transfer state. + * This value can be: + * - SD_TRANSFER_OK: No data transfer is acting + * - SD_TRANSFER_BUSY: Data transfer is acting + */ +SDTransferState SD_GetTransferState(void) +{ + if (SDIO->STA & (SDIO_FLAG_TXACT | SDIO_FLAG_RXACT)) + { + return(SD_TRANSFER_BUSY); + } + else + { + return(SD_TRANSFER_OK); + } +} + +/** + * @brief Aborts an ongoing data transfer. + * @param None + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_StopTransfer(void) +{ + SD_Error errorstatus = SD_OK; + + /*!< Send CMD12 STOP_TRANSMISSION */ + SDIO_CmdInitStructure.SDIO_Argument = 0x0; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_STOP_TRANSMISSION; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_STOP_TRANSMISSION); + + return(errorstatus); +} + +/** + * @brief Allows to erase memory area specified for the given card. + * @param startaddr: the start address. + * @param endaddr: the end address. + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr) +{ + SD_Error errorstatus = SD_OK; + uint32_t delay = 0; + __IO uint32_t maxdelay = 0; + uint8_t cardstate = 0; + + /*!< Check if the card coomnd class supports erase command */ + if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0) + { + errorstatus = SD_REQUEST_NOT_APPLICABLE; + return(errorstatus); + } + + maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2); + + if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) + { + errorstatus = SD_LOCK_UNLOCK_FAILED; + return(errorstatus); + } + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) + { + startaddr /= 512; + endaddr /= 512; + } + + /*!< According to sd-card spec 1.0 ERASE_GROUP_START (CMD32) and erase_group_end(CMD33) */ + if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_HIGH_CAPACITY_SD_CARD == CardType)) + { + /*!< Send CMD32 SD_ERASE_GRP_START with argument as addr */ + SDIO_CmdInitStructure.SDIO_Argument = startaddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START); + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + /*!< Send CMD33 SD_ERASE_GRP_END with argument as addr */ + SDIO_CmdInitStructure.SDIO_Argument = endaddr; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_END); + if (errorstatus != SD_OK) + { + return(errorstatus); + } + } + + /*!< Send CMD38 ERASE */ + SDIO_CmdInitStructure.SDIO_Argument = 0; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_ERASE); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + for (delay = 0; delay < maxdelay; delay++) + {} + + /*!< Wait till the card is in programming state */ + errorstatus = IsCardProgramming(&cardstate); + delay = SD_DATATIMEOUT; + while ((delay > 0) && (errorstatus == SD_OK) && ((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate))) + { + errorstatus = IsCardProgramming(&cardstate); + delay--; + } + + return(errorstatus); +} + +/** + * @brief Returns the current card's status. + * @param pcardstatus: pointer to the buffer that will contain the SD card + * status (Card Status register). + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_SendStatus(uint32_t *pcardstatus) +{ + SD_Error errorstatus = SD_OK; + + if (pcardstatus == NULL) + { + errorstatus = SD_INVALID_PARAMETER; + return(errorstatus); + } + + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_STATUS; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SEND_STATUS); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + *pcardstatus = SDIO_GetResponse(SDIO_RESP1); + + return(errorstatus); +} + +/** + * @brief Returns the current SD card's status. + * @param psdstatus: pointer to the buffer that will contain the SD card status + * (SD Status register). + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_SendSDStatus(uint32_t *psdstatus) +{ + SD_Error errorstatus = SD_OK; + uint32_t count = 0; + + if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) + { + errorstatus = SD_LOCK_UNLOCK_FAILED; + return(errorstatus); + } + + /*!< Set block size for card if it is not equal to current block size for card. */ + SDIO_CmdInitStructure.SDIO_Argument = 64; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + /*!< CMD55 */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = 64; + SDIO_DataInitStructure.SDIO_DataBlockSize = SDIO_DataBlockSize_64b; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + /*!< Send ACMD13 SD_APP_STAUS with argument as card's RCA.*/ + SDIO_CmdInitStructure.SDIO_Argument = 0; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_STAUS; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + errorstatus = CmdResp1Error(SD_CMD_SD_APP_STAUS); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR))) + { + if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET) + { + for (count = 0; count < 8; count++) + { + *(psdstatus + count) = SDIO_ReadData(); + } + psdstatus += 8; + } + } + + if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT); + errorstatus = SD_DATA_TIMEOUT; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL); + errorstatus = SD_DATA_CRC_FAIL; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_RXOVERR); + errorstatus = SD_RX_OVERRUN; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_STBITERR); + errorstatus = SD_START_BIT_ERR; + return(errorstatus); + } + + count = SD_DATATIMEOUT; + while ((SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET) && (count > 0)) + { + *psdstatus = SDIO_ReadData(); + psdstatus++; + count--; + } + /*!< Clear all the static status flags*/ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + return(errorstatus); +} + +/** + * @brief Allows to process all the interrupts that are high. + * @param None + * @retval SD_Error: SD Card Error code. + */ +SD_Error SD_ProcessIRQ(void) +{ + if (SDIO_GetITStatus(SDIO_IT_DATAEND) != RESET) + { + TransferError = SD_OK; + SDIO_ClearITPendingBit(SDIO_IT_DATAEND); + TransferEnd = 1; + } + else if (SDIO_GetITStatus(SDIO_IT_DCRCFAIL) != RESET) + { + SDIO_ClearITPendingBit(SDIO_IT_DCRCFAIL); + TransferError = SD_DATA_CRC_FAIL; + } + else if (SDIO_GetITStatus(SDIO_IT_DTIMEOUT) != RESET) + { + SDIO_ClearITPendingBit(SDIO_IT_DTIMEOUT); + TransferError = SD_DATA_TIMEOUT; + } + else if (SDIO_GetITStatus(SDIO_IT_RXOVERR) != RESET) + { + SDIO_ClearITPendingBit(SDIO_IT_RXOVERR); + TransferError = SD_RX_OVERRUN; + } + else if (SDIO_GetITStatus(SDIO_IT_TXUNDERR) != RESET) + { + SDIO_ClearITPendingBit(SDIO_IT_TXUNDERR); + TransferError = SD_TX_UNDERRUN; + } + else if (SDIO_GetITStatus(SDIO_IT_STBITERR) != RESET) + { + SDIO_ClearITPendingBit(SDIO_IT_STBITERR); + TransferError = SD_START_BIT_ERR; + } + + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | + SDIO_IT_TXFIFOHE | SDIO_IT_RXFIFOHF | SDIO_IT_TXUNDERR | + SDIO_IT_RXOVERR | SDIO_IT_STBITERR, DISABLE); + return(TransferError); +} + +/** + * @brief This function waits until the SDIO DMA data transfer is finished. + * @param None. + * @retval None. + */ +void SD_ProcessDMAIRQ(void) +{ + if(DMA2->LISR & SD_SDIO_DMA_FLAG_TCIF) + { + DMAEndOfTransfer = 0x01; + DMA_ClearFlag(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_TCIF|SD_SDIO_DMA_FLAG_FEIF); + } +} + +/** + * @brief Checks for error conditions for CMD0. + * @param None + * @retval SD_Error: SD Card Error code. + */ +static SD_Error CmdError(void) +{ + SD_Error errorstatus = SD_OK; + uint32_t timeout; + + timeout = SDIO_CMD0TIMEOUT; /*!< 10000 */ + + while ((timeout > 0) && (SDIO_GetFlagStatus(SDIO_FLAG_CMDSENT) == RESET)) + { + timeout--; + } + + if (timeout == 0) + { + errorstatus = SD_CMD_RSP_TIMEOUT; + return(errorstatus); + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + return(errorstatus); +} + +/** + * @brief Checks for error conditions for R7 response. + * @param None + * @retval SD_Error: SD Card Error code. + */ +static SD_Error CmdResp7Error(void) +{ + SD_Error errorstatus = SD_OK; + uint32_t status; + uint32_t timeout = SDIO_CMD0TIMEOUT; + + status = SDIO->STA; + + while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT)) && (timeout > 0)) + { + timeout--; + status = SDIO->STA; + } + + if ((timeout == 0) || (status & SDIO_FLAG_CTIMEOUT)) + { + /*!< Card is not V2.0 complient or card does not support the set voltage range */ + errorstatus = SD_CMD_RSP_TIMEOUT; + SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT); + return(errorstatus); + } + + if (status & SDIO_FLAG_CMDREND) + { + /*!< Card is SD V2.0 compliant */ + errorstatus = SD_OK; + SDIO_ClearFlag(SDIO_FLAG_CMDREND); + return(errorstatus); + } + return(errorstatus); +} + +/** + * @brief Checks for error conditions for R1 response. + * @param cmd: The sent command index. + * @retval SD_Error: SD Card Error code. + */ +static SD_Error CmdResp1Error(uint8_t cmd) +{ + SD_Error errorstatus = SD_OK; + uint32_t status; + uint32_t response_r1; + + status = SDIO->STA; + + while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT))) + { + status = SDIO->STA; + } + + if (status & SDIO_FLAG_CTIMEOUT) + { + errorstatus = SD_CMD_RSP_TIMEOUT; + SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT); + return(errorstatus); + } + else if (status & SDIO_FLAG_CCRCFAIL) + { + errorstatus = SD_CMD_CRC_FAIL; + SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL); + return(errorstatus); + } + + /*!< Check response received is of desired command */ + if (SDIO_GetCommandResponse() != cmd) + { + errorstatus = SD_ILLEGAL_CMD; + return(errorstatus); + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + /*!< We have received response, retrieve it for analysis */ + response_r1 = SDIO_GetResponse(SDIO_RESP1); + + if ((response_r1 & SD_OCR_ERRORBITS) == SD_ALLZERO) + { + return(errorstatus); + } + + if (response_r1 & SD_OCR_ADDR_OUT_OF_RANGE) + { + return(SD_ADDR_OUT_OF_RANGE); + } + + if (response_r1 & SD_OCR_ADDR_MISALIGNED) + { + return(SD_ADDR_MISALIGNED); + } + + if (response_r1 & SD_OCR_BLOCK_LEN_ERR) + { + return(SD_BLOCK_LEN_ERR); + } + + if (response_r1 & SD_OCR_ERASE_SEQ_ERR) + { + return(SD_ERASE_SEQ_ERR); + } + + if (response_r1 & SD_OCR_BAD_ERASE_PARAM) + { + return(SD_BAD_ERASE_PARAM); + } + + if (response_r1 & SD_OCR_WRITE_PROT_VIOLATION) + { + return(SD_WRITE_PROT_VIOLATION); + } + + if (response_r1 & SD_OCR_LOCK_UNLOCK_FAILED) + { + return(SD_LOCK_UNLOCK_FAILED); + } + + if (response_r1 & SD_OCR_COM_CRC_FAILED) + { + return(SD_COM_CRC_FAILED); + } + + if (response_r1 & SD_OCR_ILLEGAL_CMD) + { + return(SD_ILLEGAL_CMD); + } + + if (response_r1 & SD_OCR_CARD_ECC_FAILED) + { + return(SD_CARD_ECC_FAILED); + } + + if (response_r1 & SD_OCR_CC_ERROR) + { + return(SD_CC_ERROR); + } + + if (response_r1 & SD_OCR_GENERAL_UNKNOWN_ERROR) + { + return(SD_GENERAL_UNKNOWN_ERROR); + } + + if (response_r1 & SD_OCR_STREAM_READ_UNDERRUN) + { + return(SD_STREAM_READ_UNDERRUN); + } + + if (response_r1 & SD_OCR_STREAM_WRITE_OVERRUN) + { + return(SD_STREAM_WRITE_OVERRUN); + } + + if (response_r1 & SD_OCR_CID_CSD_OVERWRIETE) + { + return(SD_CID_CSD_OVERWRITE); + } + + if (response_r1 & SD_OCR_WP_ERASE_SKIP) + { + return(SD_WP_ERASE_SKIP); + } + + if (response_r1 & SD_OCR_CARD_ECC_DISABLED) + { + return(SD_CARD_ECC_DISABLED); + } + + if (response_r1 & SD_OCR_ERASE_RESET) + { + return(SD_ERASE_RESET); + } + + if (response_r1 & SD_OCR_AKE_SEQ_ERROR) + { + return(SD_AKE_SEQ_ERROR); + } + return(errorstatus); +} + +/** + * @brief Checks for error conditions for R3 (OCR) response. + * @param None + * @retval SD_Error: SD Card Error code. + */ +static SD_Error CmdResp3Error(void) +{ + SD_Error errorstatus = SD_OK; + uint32_t status; + + status = SDIO->STA; + + while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT))) + { + status = SDIO->STA; + } + + if (status & SDIO_FLAG_CTIMEOUT) + { + errorstatus = SD_CMD_RSP_TIMEOUT; + SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT); + return(errorstatus); + } + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + return(errorstatus); +} + +/** + * @brief Checks for error conditions for R2 (CID or CSD) response. + * @param None + * @retval SD_Error: SD Card Error code. + */ +static SD_Error CmdResp2Error(void) +{ + SD_Error errorstatus = SD_OK; + uint32_t status; + + status = SDIO->STA; + + while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CTIMEOUT | SDIO_FLAG_CMDREND))) + { + status = SDIO->STA; + } + + if (status & SDIO_FLAG_CTIMEOUT) + { + errorstatus = SD_CMD_RSP_TIMEOUT; + SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT); + return(errorstatus); + } + else if (status & SDIO_FLAG_CCRCFAIL) + { + errorstatus = SD_CMD_CRC_FAIL; + SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL); + return(errorstatus); + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + return(errorstatus); +} + +/** + * @brief Checks for error conditions for R6 (RCA) response. + * @param cmd: The sent command index. + * @param prca: pointer to the variable that will contain the SD card relative + * address RCA. + * @retval SD_Error: SD Card Error code. + */ +static SD_Error CmdResp6Error(uint8_t cmd, uint16_t *prca) +{ + SD_Error errorstatus = SD_OK; + uint32_t status; + uint32_t response_r1; + + status = SDIO->STA; + + while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CTIMEOUT | SDIO_FLAG_CMDREND))) + { + status = SDIO->STA; + } + + if (status & SDIO_FLAG_CTIMEOUT) + { + errorstatus = SD_CMD_RSP_TIMEOUT; + SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT); + return(errorstatus); + } + else if (status & SDIO_FLAG_CCRCFAIL) + { + errorstatus = SD_CMD_CRC_FAIL; + SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL); + return(errorstatus); + } + + /*!< Check response received is of desired command */ + if (SDIO_GetCommandResponse() != cmd) + { + errorstatus = SD_ILLEGAL_CMD; + return(errorstatus); + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + /*!< We have received response, retrieve it. */ + response_r1 = SDIO_GetResponse(SDIO_RESP1); + + if (SD_ALLZERO == (response_r1 & (SD_R6_GENERAL_UNKNOWN_ERROR | SD_R6_ILLEGAL_CMD | SD_R6_COM_CRC_FAILED))) + { + *prca = (uint16_t) (response_r1 >> 16); + return(errorstatus); + } + + if (response_r1 & SD_R6_GENERAL_UNKNOWN_ERROR) + { + return(SD_GENERAL_UNKNOWN_ERROR); + } + + if (response_r1 & SD_R6_ILLEGAL_CMD) + { + return(SD_ILLEGAL_CMD); + } + + if (response_r1 & SD_R6_COM_CRC_FAILED) + { + return(SD_COM_CRC_FAILED); + } + + return(errorstatus); +} + +/** + * @brief Enables or disables the SDIO wide bus mode. + * @param NewState: new state of the SDIO wide bus mode. + * This parameter can be: ENABLE or DISABLE. + * @retval SD_Error: SD Card Error code. + */ +static SD_Error SDEnWideBus(FunctionalState NewState) +{ + SD_Error errorstatus = SD_OK; + + uint32_t scr[2] = {0, 0}; + + if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) + { + errorstatus = SD_LOCK_UNLOCK_FAILED; + return(errorstatus); + } + + /*!< Get SCR Register */ + errorstatus = FindSCR(RCA, scr); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + /*!< If wide bus operation to be enabled */ + if (NewState == ENABLE) + { + /*!< If requested card supports wide bus operation */ + if ((scr[1] & SD_WIDE_BUS_SUPPORT) != SD_ALLZERO) + { + /*!< Send CMD55 APP_CMD with argument as card's RCA.*/ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + /*!< Send ACMD6 APP_CMD with argument as 2 for wide bus mode */ + SDIO_CmdInitStructure.SDIO_Argument = 0x2; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_SD_SET_BUSWIDTH; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_APP_SD_SET_BUSWIDTH); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + return(errorstatus); + } + else + { + errorstatus = SD_REQUEST_NOT_APPLICABLE; + return(errorstatus); + } + } /*!< If wide bus operation to be disabled */ + else + { + /*!< If requested card supports 1 bit mode operation */ + if ((scr[1] & SD_SINGLE_BUS_SUPPORT) != SD_ALLZERO) + { + /*!< Send CMD55 APP_CMD with argument as card's RCA.*/ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + /*!< Send ACMD6 APP_CMD with argument as 2 for wide bus mode */ + SDIO_CmdInitStructure.SDIO_Argument = 0x00; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_SD_SET_BUSWIDTH; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_APP_SD_SET_BUSWIDTH); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + return(errorstatus); + } + else + { + errorstatus = SD_REQUEST_NOT_APPLICABLE; + return(errorstatus); + } + } +} + +/** + * @brief Checks if the SD card is in programming state. + * @param pstatus: pointer to the variable that will contain the SD card state. + * @retval SD_Error: SD Card Error code. + */ +static SD_Error IsCardProgramming(uint8_t *pstatus) +{ + SD_Error errorstatus = SD_OK; + __IO uint32_t respR1 = 0, status = 0; + + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_STATUS; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + status = SDIO->STA; + while (!(status & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CMDREND | SDIO_FLAG_CTIMEOUT))) + { + status = SDIO->STA; + } + + if (status & SDIO_FLAG_CTIMEOUT) + { + errorstatus = SD_CMD_RSP_TIMEOUT; + SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT); + return(errorstatus); + } + else if (status & SDIO_FLAG_CCRCFAIL) + { + errorstatus = SD_CMD_CRC_FAIL; + SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL); + return(errorstatus); + } + + status = (uint32_t)SDIO_GetCommandResponse(); + + /*!< Check response received is of desired command */ + if (status != SD_CMD_SEND_STATUS) + { + errorstatus = SD_ILLEGAL_CMD; + return(errorstatus); + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + + /*!< We have received response, retrieve it for analysis */ + respR1 = SDIO_GetResponse(SDIO_RESP1); + + /*!< Find out card status */ + *pstatus = (uint8_t) ((respR1 >> 9) & 0x0000000F); + + if ((respR1 & SD_OCR_ERRORBITS) == SD_ALLZERO) + { + return(errorstatus); + } + + if (respR1 & SD_OCR_ADDR_OUT_OF_RANGE) + { + return(SD_ADDR_OUT_OF_RANGE); + } + + if (respR1 & SD_OCR_ADDR_MISALIGNED) + { + return(SD_ADDR_MISALIGNED); + } + + if (respR1 & SD_OCR_BLOCK_LEN_ERR) + { + return(SD_BLOCK_LEN_ERR); + } + + if (respR1 & SD_OCR_ERASE_SEQ_ERR) + { + return(SD_ERASE_SEQ_ERR); + } + + if (respR1 & SD_OCR_BAD_ERASE_PARAM) + { + return(SD_BAD_ERASE_PARAM); + } + + if (respR1 & SD_OCR_WRITE_PROT_VIOLATION) + { + return(SD_WRITE_PROT_VIOLATION); + } + + if (respR1 & SD_OCR_LOCK_UNLOCK_FAILED) + { + return(SD_LOCK_UNLOCK_FAILED); + } + + if (respR1 & SD_OCR_COM_CRC_FAILED) + { + return(SD_COM_CRC_FAILED); + } + + if (respR1 & SD_OCR_ILLEGAL_CMD) + { + return(SD_ILLEGAL_CMD); + } + + if (respR1 & SD_OCR_CARD_ECC_FAILED) + { + return(SD_CARD_ECC_FAILED); + } + + if (respR1 & SD_OCR_CC_ERROR) + { + return(SD_CC_ERROR); + } + + if (respR1 & SD_OCR_GENERAL_UNKNOWN_ERROR) + { + return(SD_GENERAL_UNKNOWN_ERROR); + } + + if (respR1 & SD_OCR_STREAM_READ_UNDERRUN) + { + return(SD_STREAM_READ_UNDERRUN); + } + + if (respR1 & SD_OCR_STREAM_WRITE_OVERRUN) + { + return(SD_STREAM_WRITE_OVERRUN); + } + + if (respR1 & SD_OCR_CID_CSD_OVERWRIETE) + { + return(SD_CID_CSD_OVERWRITE); + } + + if (respR1 & SD_OCR_WP_ERASE_SKIP) + { + return(SD_WP_ERASE_SKIP); + } + + if (respR1 & SD_OCR_CARD_ECC_DISABLED) + { + return(SD_CARD_ECC_DISABLED); + } + + if (respR1 & SD_OCR_ERASE_RESET) + { + return(SD_ERASE_RESET); + } + + if (respR1 & SD_OCR_AKE_SEQ_ERROR) + { + return(SD_AKE_SEQ_ERROR); + } + + return(errorstatus); +} + +/** + * @brief Find the SD card SCR register value. + * @param rca: selected card address. + * @param pscr: pointer to the buffer that will contain the SCR value. + * @retval SD_Error: SD Card Error code. + */ +static SD_Error FindSCR(uint16_t rca, uint32_t *pscr) +{ + uint32_t index = 0; + SD_Error errorstatus = SD_OK; + uint32_t tempscr[2] = {0, 0}; + + /*!< Set Block Size To 8 Bytes */ + /*!< Send CMD55 APP_CMD with argument as card's RCA */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)8; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + /*!< Send CMD55 APP_CMD with argument as card's RCA */ + SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) RCA << 16; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_APP_CMD); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; + SDIO_DataInitStructure.SDIO_DataLength = 8; + SDIO_DataInitStructure.SDIO_DataBlockSize = SDIO_DataBlockSize_8b; + SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO; + SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; + SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; + SDIO_DataConfig(&SDIO_DataInitStructure); + + + /*!< Send ACMD51 SD_APP_SEND_SCR with argument as 0 */ + SDIO_CmdInitStructure.SDIO_Argument = 0x0; + SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_SEND_SCR; + SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; + SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; + SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; + SDIO_SendCommand(&SDIO_CmdInitStructure); + + errorstatus = CmdResp1Error(SD_CMD_SD_APP_SEND_SCR); + + if (errorstatus != SD_OK) + { + return(errorstatus); + } + + while (!(SDIO->STA & (SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR))) + { + if (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET) + { + *(tempscr + index) = SDIO_ReadData(); + index++; + } + } + + if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT); + errorstatus = SD_DATA_TIMEOUT; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL); + errorstatus = SD_DATA_CRC_FAIL; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_RXOVERR); + errorstatus = SD_RX_OVERRUN; + return(errorstatus); + } + else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET) + { + SDIO_ClearFlag(SDIO_FLAG_STBITERR); + errorstatus = SD_START_BIT_ERR; + return(errorstatus); + } + + /*!< Clear all the static flags */ + SDIO_ClearFlag(SDIO_STATIC_FLAGS); + + *(pscr + 1) = ((tempscr[0] & SD_0TO7BITS) << 24) | ((tempscr[0] & SD_8TO15BITS) << 8) | ((tempscr[0] & SD_16TO23BITS) >> 8) | ((tempscr[0] & SD_24TO31BITS) >> 24); + + *(pscr) = ((tempscr[1] & SD_0TO7BITS) << 24) | ((tempscr[1] & SD_8TO15BITS) << 8) | ((tempscr[1] & SD_16TO23BITS) >> 8) | ((tempscr[1] & SD_24TO31BITS) >> 24); + + return(errorstatus); +} + +/** + * @brief Converts the number of bytes in power of two and returns the power. + * @param NumberOfBytes: number of bytes. + * @retval None + */ +uint8_t convert_from_bytes_to_power_of_two(uint16_t NumberOfBytes) +{ + uint8_t count = 0; + + while (NumberOfBytes != 1) + { + NumberOfBytes >>= 1; + count++; + } + return(count); +} + +void SDIO_IRQHandler(void) +{ + SD_ProcessIRQ(); +} + +void SD_SDIO_DMA_IRQHANDLER(void) +{ + SD_ProcessDMAIRQ(); +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/Horus/sdio_sd.h b/radio/src/targets/Horus/sdio_sd.h new file mode 100644 index 000000000..5d3671b78 --- /dev/null +++ b/radio/src/targets/Horus/sdio_sd.h @@ -0,0 +1,410 @@ +/** + ****************************************************************************** + * @file stm32f4_discovery_sdio_sd.h + * @author MCD Application Team + * @version V1.0.2 + * @date 05-March-2012 + * @brief This file contains all the functions prototypes for the SD Card + * stm324xg_eval_sdio_sd driver firmware library. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __SDIO_SD_H +#define __SDIO_SD_H + +#include "board_horus.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +// #include "stm32f4_discovery.h" + +/** @addtogroup Utilities + * @{ + */ + +/** @addtogroup STM32_EVAL + * @{ + */ + +/** @addtogroup STM32F4_DISCOVERY + * @{ + */ + +/** @addtogroup STM32F4_DISCOVERY_SDIO_SD + * @{ + */ + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Exported_Types + * @{ + */ +typedef enum +{ +/** + * @brief SDIO specific error defines + */ + SD_CMD_CRC_FAIL = (1), /*!< Command response received (but CRC check failed) */ + SD_DATA_CRC_FAIL = (2), /*!< Data bock sent/received (CRC check Failed) */ + SD_CMD_RSP_TIMEOUT = (3), /*!< Command response timeout */ + SD_DATA_TIMEOUT = (4), /*!< Data time out */ + SD_TX_UNDERRUN = (5), /*!< Transmit FIFO under-run */ + SD_RX_OVERRUN = (6), /*!< Receive FIFO over-run */ + SD_START_BIT_ERR = (7), /*!< Start bit not detected on all data signals in widE bus mode */ + SD_CMD_OUT_OF_RANGE = (8), /*!< CMD's argument was out of range.*/ + SD_ADDR_MISALIGNED = (9), /*!< Misaligned address */ + SD_BLOCK_LEN_ERR = (10), /*!< Transferred block length is not allowed for the card or the number of transferred bytes does not match the block length */ + SD_ERASE_SEQ_ERR = (11), /*!< An error in the sequence of erase command occurs.*/ + SD_BAD_ERASE_PARAM = (12), /*!< An Invalid selection for erase groups */ + SD_WRITE_PROT_VIOLATION = (13), /*!< Attempt to program a write protect block */ + SD_LOCK_UNLOCK_FAILED = (14), /*!< Sequence or password error has been detected in unlock command or if there was an attempt to access a locked card */ + SD_COM_CRC_FAILED = (15), /*!< CRC check of the previous command failed */ + SD_ILLEGAL_CMD = (16), /*!< Command is not legal for the card state */ + SD_CARD_ECC_FAILED = (17), /*!< Card internal ECC was applied but failed to correct the data */ + SD_CC_ERROR = (18), /*!< Internal card controller error */ + SD_GENERAL_UNKNOWN_ERROR = (19), /*!< General or Unknown error */ + SD_STREAM_READ_UNDERRUN = (20), /*!< The card could not sustain data transfer in stream read operation. */ + SD_STREAM_WRITE_OVERRUN = (21), /*!< The card could not sustain data programming in stream mode */ + SD_CID_CSD_OVERWRITE = (22), /*!< CID/CSD overwrite error */ + SD_WP_ERASE_SKIP = (23), /*!< only partial address space was erased */ + SD_CARD_ECC_DISABLED = (24), /*!< Command has been executed without using internal ECC */ + SD_ERASE_RESET = (25), /*!< Erase sequence was cleared before executing because an out of erase sequence command was received */ + SD_AKE_SEQ_ERROR = (26), /*!< Error in sequence of authentication. */ + SD_INVALID_VOLTRANGE = (27), + SD_ADDR_OUT_OF_RANGE = (28), + SD_SWITCH_ERROR = (29), + SD_SDIO_DISABLED = (30), + SD_SDIO_FUNCTION_BUSY = (31), + SD_SDIO_FUNCTION_FAILED = (32), + SD_SDIO_UNKNOWN_FUNCTION = (33), + +/** + * @brief Standard error defines + */ + SD_INTERNAL_ERROR, + SD_NOT_CONFIGURED, + SD_REQUEST_PENDING, + SD_REQUEST_NOT_APPLICABLE, + SD_INVALID_PARAMETER, + SD_UNSUPPORTED_FEATURE, + SD_UNSUPPORTED_HW, + SD_ERROR, + SD_OK = 0 +} SD_Error; + +/** + * @brief SDIO Transfer state + */ +typedef enum +{ + SD_TRANSFER_OK = 0, + SD_TRANSFER_BUSY = 1, + SD_TRANSFER_ERROR +} SDTransferState; + +/** + * @brief SD Card States + */ +typedef enum +{ + SD_CARD_READY = ((uint32_t)0x00000001), + SD_CARD_IDENTIFICATION = ((uint32_t)0x00000002), + SD_CARD_STANDBY = ((uint32_t)0x00000003), + SD_CARD_TRANSFER = ((uint32_t)0x00000004), + SD_CARD_SENDING = ((uint32_t)0x00000005), + SD_CARD_RECEIVING = ((uint32_t)0x00000006), + SD_CARD_PROGRAMMING = ((uint32_t)0x00000007), + SD_CARD_DISCONNECTED = ((uint32_t)0x00000008), + SD_CARD_ERROR = ((uint32_t)0x000000FF) +}SDCardState; + + +/** + * @brief Card Specific Data: CSD Register + */ +typedef struct +{ + __IO uint8_t CSDStruct; /*!< CSD structure */ + __IO uint8_t SysSpecVersion; /*!< System specification version */ + __IO uint8_t Reserved1; /*!< Reserved */ + __IO uint8_t TAAC; /*!< Data read access-time 1 */ + __IO uint8_t NSAC; /*!< Data read access-time 2 in CLK cycles */ + __IO uint8_t MaxBusClkFrec; /*!< Max. bus clock frequency */ + __IO uint16_t CardComdClasses; /*!< Card command classes */ + __IO uint8_t RdBlockLen; /*!< Max. read data block length */ + __IO uint8_t PartBlockRead; /*!< Partial blocks for read allowed */ + __IO uint8_t WrBlockMisalign; /*!< Write block misalignment */ + __IO uint8_t RdBlockMisalign; /*!< Read block misalignment */ + __IO uint8_t DSRImpl; /*!< DSR implemented */ + __IO uint8_t Reserved2; /*!< Reserved */ + __IO uint32_t DeviceSize; /*!< Device Size */ + __IO uint8_t MaxRdCurrentVDDMin; /*!< Max. read current @ VDD min */ + __IO uint8_t MaxRdCurrentVDDMax; /*!< Max. read current @ VDD max */ + __IO uint8_t MaxWrCurrentVDDMin; /*!< Max. write current @ VDD min */ + __IO uint8_t MaxWrCurrentVDDMax; /*!< Max. write current @ VDD max */ + __IO uint8_t DeviceSizeMul; /*!< Device size multiplier */ + __IO uint8_t EraseGrSize; /*!< Erase group size */ + __IO uint8_t EraseGrMul; /*!< Erase group size multiplier */ + __IO uint8_t WrProtectGrSize; /*!< Write protect group size */ + __IO uint8_t WrProtectGrEnable; /*!< Write protect group enable */ + __IO uint8_t ManDeflECC; /*!< Manufacturer default ECC */ + __IO uint8_t WrSpeedFact; /*!< Write speed factor */ + __IO uint8_t MaxWrBlockLen; /*!< Max. write data block length */ + __IO uint8_t WriteBlockPaPartial; /*!< Partial blocks for write allowed */ + __IO uint8_t Reserved3; /*!< Reserded */ + __IO uint8_t ContentProtectAppli; /*!< Content protection application */ + __IO uint8_t FileFormatGrouop; /*!< File format group */ + __IO uint8_t CopyFlag; /*!< Copy flag (OTP) */ + __IO uint8_t PermWrProtect; /*!< Permanent write protection */ + __IO uint8_t TempWrProtect; /*!< Temporary write protection */ + __IO uint8_t FileFormat; /*!< File Format */ + __IO uint8_t ECC; /*!< ECC code */ + __IO uint8_t CSD_CRC; /*!< CSD CRC */ + __IO uint8_t Reserved4; /*!< always 1*/ +} SD_CSD; + +/** + * @brief Card Identification Data: CID Register + */ +typedef struct +{ + __IO uint8_t ManufacturerID; /*!< ManufacturerID */ + __IO uint16_t OEM_AppliID; /*!< OEM/Application ID */ + __IO uint32_t ProdName1; /*!< Product Name part1 */ + __IO uint8_t ProdName2; /*!< Product Name part2*/ + __IO uint8_t ProdRev; /*!< Product Revision */ + __IO uint32_t ProdSN; /*!< Product Serial Number */ + __IO uint8_t Reserved1; /*!< Reserved1 */ + __IO uint16_t ManufactDate; /*!< Manufacturing Date */ + __IO uint8_t CID_CRC; /*!< CID CRC */ + __IO uint8_t Reserved2; /*!< always 1 */ +} SD_CID; + +/** + * @brief SD Card Status + */ +typedef struct +{ + __IO uint8_t DAT_BUS_WIDTH; + __IO uint8_t SECURED_MODE; + __IO uint16_t SD_CARD_TYPE; + __IO uint32_t SIZE_OF_PROTECTED_AREA; + __IO uint8_t SPEED_CLASS; + __IO uint8_t PERFORMANCE_MOVE; + __IO uint8_t AU_SIZE; + __IO uint16_t ERASE_SIZE; + __IO uint8_t ERASE_TIMEOUT; + __IO uint8_t ERASE_OFFSET; +} SD_CardStatus; + + +/** + * @brief SD Card information + */ +typedef struct +{ + SD_CSD SD_csd; + SD_CID SD_cid; + uint64_t CardCapacity; /*!< Card Capacity */ + uint32_t CardBlockSize; /*!< Card Block Size */ + uint16_t RCA; + uint8_t CardType; +} SD_CardInfo; + +/** + * @} + */ + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Exported_Constants + * @{ + */ + +/** + * @brief SDIO Commands Index + */ +#define SD_CMD_GO_IDLE_STATE ((uint8_t)0) +#define SD_CMD_SEND_OP_COND ((uint8_t)1) +#define SD_CMD_ALL_SEND_CID ((uint8_t)2) +#define SD_CMD_SET_REL_ADDR ((uint8_t)3) /*!< SDIO_SEND_REL_ADDR for SD Card */ +#define SD_CMD_SET_DSR ((uint8_t)4) +#define SD_CMD_SDIO_SEN_OP_COND ((uint8_t)5) +#define SD_CMD_HS_SWITCH ((uint8_t)6) +#define SD_CMD_SEL_DESEL_CARD ((uint8_t)7) +#define SD_CMD_HS_SEND_EXT_CSD ((uint8_t)8) +#define SD_CMD_SEND_CSD ((uint8_t)9) +#define SD_CMD_SEND_CID ((uint8_t)10) +#define SD_CMD_READ_DAT_UNTIL_STOP ((uint8_t)11) /*!< SD Card doesn't support it */ +#define SD_CMD_STOP_TRANSMISSION ((uint8_t)12) +#define SD_CMD_SEND_STATUS ((uint8_t)13) +#define SD_CMD_HS_BUSTEST_READ ((uint8_t)14) +#define SD_CMD_GO_INACTIVE_STATE ((uint8_t)15) +#define SD_CMD_SET_BLOCKLEN ((uint8_t)16) +#define SD_CMD_READ_SINGLE_BLOCK ((uint8_t)17) +#define SD_CMD_READ_MULT_BLOCK ((uint8_t)18) +#define SD_CMD_HS_BUSTEST_WRITE ((uint8_t)19) +#define SD_CMD_WRITE_DAT_UNTIL_STOP ((uint8_t)20) /*!< SD Card doesn't support it */ +#define SD_CMD_SET_BLOCK_COUNT ((uint8_t)23) /*!< SD Card doesn't support it */ +#define SD_CMD_WRITE_SINGLE_BLOCK ((uint8_t)24) +#define SD_CMD_WRITE_MULT_BLOCK ((uint8_t)25) +#define SD_CMD_PROG_CID ((uint8_t)26) /*!< reserved for manufacturers */ +#define SD_CMD_PROG_CSD ((uint8_t)27) +#define SD_CMD_SET_WRITE_PROT ((uint8_t)28) +#define SD_CMD_CLR_WRITE_PROT ((uint8_t)29) +#define SD_CMD_SEND_WRITE_PROT ((uint8_t)30) +#define SD_CMD_SD_ERASE_GRP_START ((uint8_t)32) /*!< To set the address of the first write + block to be erased. (For SD card only) */ +#define SD_CMD_SD_ERASE_GRP_END ((uint8_t)33) /*!< To set the address of the last write block of the + continuous range to be erased. (For SD card only) */ +#define SD_CMD_ERASE_GRP_START ((uint8_t)35) /*!< To set the address of the first write block to be erased. + (For MMC card only spec 3.31) */ + +#define SD_CMD_ERASE_GRP_END ((uint8_t)36) /*!< To set the address of the last write block of the + continuous range to be erased. (For MMC card only spec 3.31) */ + +#define SD_CMD_ERASE ((uint8_t)38) +#define SD_CMD_FAST_IO ((uint8_t)39) /*!< SD Card doesn't support it */ +#define SD_CMD_GO_IRQ_STATE ((uint8_t)40) /*!< SD Card doesn't support it */ +#define SD_CMD_LOCK_UNLOCK ((uint8_t)42) +#define SD_CMD_APP_CMD ((uint8_t)55) +#define SD_CMD_GEN_CMD ((uint8_t)56) +#define SD_CMD_NO_CMD ((uint8_t)64) + +/** + * @brief Following commands are SD Card Specific commands. + * SDIO_APP_CMD should be sent before sending these commands. + */ +#define SD_CMD_APP_SD_SET_BUSWIDTH ((uint8_t)6) /*!< For SD Card only */ +#define SD_CMD_SD_APP_STAUS ((uint8_t)13) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SEND_NUM_WRITE_BLOCKS ((uint8_t)22) /*!< For SD Card only */ +#define SD_CMD_SD_APP_OP_COND ((uint8_t)41) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SET_CLR_CARD_DETECT ((uint8_t)42) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SEND_SCR ((uint8_t)51) /*!< For SD Card only */ +#define SD_CMD_SDIO_RW_DIRECT ((uint8_t)52) /*!< For SD I/O Card only */ +#define SD_CMD_SDIO_RW_EXTENDED ((uint8_t)53) /*!< For SD I/O Card only */ + +/** + * @brief Following commands are SD Card Specific security commands. + * SDIO_APP_CMD should be sent before sending these commands. + */ +#define SD_CMD_SD_APP_GET_MKB ((uint8_t)43) /*!< For SD Card only */ +#define SD_CMD_SD_APP_GET_MID ((uint8_t)44) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SET_CER_RN1 ((uint8_t)45) /*!< For SD Card only */ +#define SD_CMD_SD_APP_GET_CER_RN2 ((uint8_t)46) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SET_CER_RES2 ((uint8_t)47) /*!< For SD Card only */ +#define SD_CMD_SD_APP_GET_CER_RES1 ((uint8_t)48) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SECURE_READ_MULTIPLE_BLOCK ((uint8_t)18) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SECURE_WRITE_MULTIPLE_BLOCK ((uint8_t)25) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SECURE_ERASE ((uint8_t)38) /*!< For SD Card only */ +#define SD_CMD_SD_APP_CHANGE_SECURE_AREA ((uint8_t)49) /*!< For SD Card only */ +#define SD_CMD_SD_APP_SECURE_WRITE_MKB ((uint8_t)48) /*!< For SD Card only */ + +/* Uncomment the following line to select the SDIO Data transfer mode */ +#if !defined (SD_DMA_MODE) && !defined (SD_POLLING_MODE) +#define SD_DMA_MODE ((uint32_t)0x00000000) +/*#define SD_POLLING_MODE ((uint32_t)0x00000002)*/ +#endif + +/** + * @brief SD detection on its memory slot + */ +#define SD_PRESENT ((uint8_t)0x01) +#define SD_NOT_PRESENT ((uint8_t)0x00) + +/** + * @brief Supported SD Memory Cards + */ +#define SDIO_STD_CAPACITY_SD_CARD_V1_1 ((uint32_t)0x00000000) +#define SDIO_STD_CAPACITY_SD_CARD_V2_0 ((uint32_t)0x00000001) +#define SDIO_HIGH_CAPACITY_SD_CARD ((uint32_t)0x00000002) +#define SDIO_MULTIMEDIA_CARD ((uint32_t)0x00000003) +#define SDIO_SECURE_DIGITAL_IO_CARD ((uint32_t)0x00000004) +#define SDIO_HIGH_SPEED_MULTIMEDIA_CARD ((uint32_t)0x00000005) +#define SDIO_SECURE_DIGITAL_IO_COMBO_CARD ((uint32_t)0x00000006) +#define SDIO_HIGH_CAPACITY_MMC_CARD ((uint32_t)0x00000007) + +/** + * @} + */ + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Exported_Macros + * @{ + */ +/** + * @} + */ + +/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Exported_Functions + * @{ + */ +void SD_DeInit(void); +SD_Error SD_Init(void); +SDTransferState SD_GetStatus(void); +SDCardState SD_GetState(void); +uint8_t SD_Detect(void); +SD_Error SD_PowerON(void); +SD_Error SD_PowerOFF(void); +SD_Error SD_InitializeCards(void); +SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo); +SD_Error SD_GetCardStatus(SD_CardStatus *cardstatus); +SD_Error SD_EnableWideBusOperation(uint32_t WideMode); +SD_Error SD_SelectDeselect(uint32_t addr); +SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize); +SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks); +SD_Error SD_ReadMultiBlocksFIXED(uint8_t *readbuff, uint32_t ReadAddr, uint32_t BlockSize, uint32_t NumberOfBlocks); +SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize); +SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks); +SD_Error SD_WriteMultiBlocksFIXED(uint8_t *writebuff, uint32_t WriteAddr, uint32_t BlockSize, uint32_t NumberOfBlocks); +SDTransferState SD_GetTransferState(void); +SD_Error SD_StopTransfer(void); +SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr); +SD_Error SD_SendStatus(uint32_t *pcardstatus); +SD_Error SD_SendSDStatus(uint32_t *psdstatus); +SD_Error SD_ProcessIRQSrc(void); +void SD_ProcessDMAIRQ(void); +SD_Error SD_WaitReadOperation(void); +SD_Error SD_WaitWriteOperation(void); +#ifdef __cplusplus +} +#endif + +#endif /* __STM32F4_DISCOVERY_SDIO_SD_H */ +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/Horus/sdram_driver.c b/radio/src/targets/Horus/sdram_driver.c new file mode 100755 index 000000000..b9ddcc505 --- /dev/null +++ b/radio/src/targets/Horus/sdram_driver.c @@ -0,0 +1,445 @@ +#include "board_horus.h" + +#define SDRAM_MEMORY_WIDTH FMC_SDMemory_Width_16b + +#define SDRAM_CAS_LATENCY FMC_CAS_Latency_3 + +#define SDCLOCK_PERIOD FMC_SDClock_Period_2 + +#define SDRAM_TIMEOUT ((uint32_t)0xFFFF) + +/** + * @brief FMC SDRAM Mode definition register defines + */ +#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000) +#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001) +#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002) +#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004) +#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000) +#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008) +#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020) +#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030) +#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000) +#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000) +#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200) + +/** + * @brief FMC SDRAM Memory Read Burst feature + */ +#define SDRAM_READBURST FMC_Read_Burst_Disable + +static void __Delay(__IO uint32_t nCount) +{ + __IO uint32_t index = 0; + for(index = (100000 * nCount); index != 0; index--) + { + } +} + +/** + * @brief Configures all SDRAM memory I/Os pins. + * @param None. + * @retval None. + */ +void SDRAM_GPIOConfig(void) +{ + GPIO_InitTypeDef GPIO_InitStructure; + + /* Enable GPIOs clock */ + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | + RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOG|RCC_AHB1Periph_GPIOH, ENABLE); + + /*-- GPIOs Configuration -----------------------------------------------------*/ + /* + +-------------------+--------------------+--------------------+--------------------+ + + SDRAM pins assignment + + +-------------------+--------------------+--------------------+--------------------+ + | PD0 <-> FMC_D2 | PE0 <-> FMC_NBL0 | PF0 <-> FMC_A0 | PG0 <-> FMC_A10 | + | PD1 <-> FMC_D3 | PE1 <-> FMC_NBL1 | PF1 <-> FMC_A1 | PG1 <-> FMC_A11 | + | PD8 <-> FMC_D13 | PE7 <-> FMC_D4 | PF2 <-> FMC_A2 | PG8 <-> FMC_SDCLK | + | PD9 <-> FMC_D14 | PE8 <-> FMC_D5 | PF3 <-> FMC_A3 | PG15 <-> FMC_NCAS | + | PD10 <-> FMC_D15 | PE9 <-> FMC_D6 | PF4 <-> FMC_A4 |--------------------+ + | PD14 <-> FMC_D0 | PE10 <-> FMC_D7 | PF5 <-> FMC_A5 | + | PD15 <-> FMC_D1 | PE11 <-> FMC_D8 | PF11 <-> FMC_NRAS | + +-------------------| PE12 <-> FMC_D9 | PF12 <-> FMC_A6 | + | PE13 <-> FMC_D10 | PF13 <-> FMC_A7 | + | PE14 <-> FMC_D11 | PF14 <-> FMC_A8 | + | PE15 <-> FMC_D12 | PF15 <-> FMC_A9 | + +-------------------+--------------------+--------------------+ + | PB5 <-> FMC_SDCKE1| + | PB6 <-> FMC_SDNE1 | + | PH5 <-> FMC_SDNWE |/////// + +-------------------+ + + */ + + /* Common GPIO configuration */ + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + + /* GPIOB configuration */ + GPIO_PinAFConfig(GPIOB, GPIO_PinSource5 , GPIO_AF_FMC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 ; + + GPIO_Init(GPIOB, &GPIO_InitStructure); + + /* GPIOC configuration */ + GPIO_PinAFConfig(GPIOH, GPIO_PinSource5 , GPIO_AF_FMC); + + GPIO_PinAFConfig(GPIOH, GPIO_PinSource6 , GPIO_AF_FMC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6; + + GPIO_Init(GPIOH, &GPIO_InitStructure); + + /* GPIOD configuration */ + GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource10, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_FMC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8 | + GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 | + GPIO_Pin_15; + + GPIO_Init(GPIOD, &GPIO_InitStructure); + + /* GPIOE configuration */ + GPIO_PinAFConfig(GPIOE, GPIO_PinSource0 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource1 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource7 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource8 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource9 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource10 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource11 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource12 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource13 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource14 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource15 , GPIO_AF_FMC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_7 | + GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | + GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | + GPIO_Pin_14 | GPIO_Pin_15; + + GPIO_Init(GPIOE, &GPIO_InitStructure); + + /* GPIOF configuration */ + GPIO_PinAFConfig(GPIOF, GPIO_PinSource0 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource1 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource2 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource3 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource4 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource5 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource11 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource12 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource13 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource14 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource15 , GPIO_AF_FMC); + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | + GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | + GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | + GPIO_Pin_14 | GPIO_Pin_15; + + GPIO_Init(GPIOF, &GPIO_InitStructure); + + /* GPIOG configuration */ + GPIO_PinAFConfig(GPIOG, GPIO_PinSource0 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource1 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource4 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource5 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource8 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource15 , GPIO_AF_FMC); + + + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | + GPIO_Pin_5 | GPIO_Pin_8 | GPIO_Pin_15; + + GPIO_Init(GPIOG, &GPIO_InitStructure); +} + +/** + * @brief Executes the SDRAM memory initialization sequence. + * @param None. + * @retval None. + */ +void SDRAM_InitSequence(void) +{ + FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStructure; + uint32_t tmpr = 0; + uint32_t timeout = SDRAM_TIMEOUT; + + /* Step 3 --------------------------------------------------------------------*/ + /* Configure a clock configuration enable command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + /* Wait until the SDRAM controller is ready */ + while((FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) && (timeout > 0)) + { + timeout--; + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Step 4 --------------------------------------------------------------------*/ + /* Insert 100 ms delay */ + __Delay(10); + + /* Step 5 --------------------------------------------------------------------*/ + /* Configure a PALL (precharge all) command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + + /* Wait until the SDRAM controller is ready */ + timeout = SDRAM_TIMEOUT; + while((FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) && (timeout > 0)) + { + timeout--; + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + +/* Step 6 --------------------------------------------------------------------*/ + /* Configure a Auto-Refresh command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 8; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + + /* Wait until the SDRAM controller is ready */ + timeout = SDRAM_TIMEOUT; + while((FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) && (timeout > 0)) + { + timeout--; + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + +/* Step 7 --------------------------------------------------------------------*/ + /* Program the external memory mode register */ + tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | + SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | + SDRAM_MODEREG_CAS_LATENCY_3 | + SDRAM_MODEREG_OPERATING_MODE_STANDARD | + SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; + + /* Configure a load Mode register command*/ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr; + + /* Wait until the SDRAM controller is ready */ + timeout = SDRAM_TIMEOUT; + while((FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) && (timeout > 0)) + { + timeout--; + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Step 8 --------------------------------------------------------------------*/ + + /* Set the refresh rate counter */ + /* (15.62 us x Freq) - 20 */ + /* Set the device refresh counter */ + FMC_SetRefreshCount(1385); + + /* Wait until the SDRAM controller is ready */ + timeout = SDRAM_TIMEOUT; + while((FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) && (timeout > 0)) + { + timeout--; + } +} + +/** + * @brief Executes the SDRAM memory initialization sequence. + * @param None. + * @retval None. + */ +void SDRAMTest_InitSequence(void) +{ + FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStructure; + uint32_t tmpr = 0; + +/* Step 3 --------------------------------------------------------------------*/ + /* Configure a clock configuration enable command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET) + { + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + +/* Step 4 --------------------------------------------------------------------*/ + /* Insert 100 ms delay */ + __Delay(10); + +/* Step 5 --------------------------------------------------------------------*/ + /* Configure a PALL (precharge all) command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET) + { + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + +/* Step 6 --------------------------------------------------------------------*/ + /* Configure a Auto-Refresh command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 4; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET) + { + } + /* Send the first command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET) + { + } + /* Send the second command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + +/* Step 7 --------------------------------------------------------------------*/ + /* Program the external memory mode register */ + tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 | + SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | + SDRAM_MODEREG_CAS_LATENCY_3 | + SDRAM_MODEREG_OPERATING_MODE_STANDARD | + SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; + + /* Configure a load Mode register command*/ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank2; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET) + { + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + +/* Step 8 --------------------------------------------------------------------*/ + + /* Set the refresh rate counter */ + /* (15.62 us x Freq) - 20 */ + /* Set the device refresh counter */ + FMC_SetRefreshCount(1386); + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank2_SDRAM, FMC_FLAG_Busy) != RESET) + { + } +} + +void SDRAM_Init(void) +{ + FMC_SDRAMInitTypeDef FMC_SDRAMInitStructure; + FMC_SDRAMTimingInitTypeDef FMC_SDRAMTimingInitStructure; + + /* GPIO configuration for FMC SDRAM bank */ + SDRAM_GPIOConfig(); + + /* Enable FMC clock */ + RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE); + + /* FMC Configuration ---------------------------------------------------------*/ + /* FMC SDRAM Bank configuration */ + /* Timing configuration for 90 Mhz of SD clock frequency (168Mhz/2) */ + /* TMRD: 2 Clock cycles */ + FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2; + /* TXSR: min=70ns (7x11.11ns) */ + FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7; + /* TRAS: min=42ns (4x11.11ns) max=120k (ns) */ + FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4; + /* TRC: min=70 (7x11.11ns) */ + FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 7; + /* TWR: min=1+ 7ns (1+1x11.11ns) */ + FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2; + /* TRP: 20ns => 2x11.11ns */ + FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2; + /* TRCD: 20ns => 2x11.11ns */ + FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2; + + /* FMC SDRAM control configuration */ + FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank2_SDRAM; + /* Row addressing: [7:0] */ + FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b; + /* Column addressing: [11:0] */ + FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_12b; + FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = SDRAM_MEMORY_WIDTH; + FMC_SDRAMInitStructure.FMC_InternalBankNumber = FMC_InternalBank_Number_4; + FMC_SDRAMInitStructure.FMC_CASLatency = SDRAM_CAS_LATENCY; + FMC_SDRAMInitStructure.FMC_WriteProtection = FMC_Write_Protection_Disable; + FMC_SDRAMInitStructure.FMC_SDClockPeriod = SDCLOCK_PERIOD; + FMC_SDRAMInitStructure.FMC_ReadBurst = SDRAM_READBURST; + FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_0; + FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct = &FMC_SDRAMTimingInitStructure; + + /* FMC SDRAM bank initialization */ + FMC_SDRAMInit(&FMC_SDRAMInitStructure); + + /* FMC SDRAM device initialization sequence */ + SDRAMTest_InitSequence(); +} + +void SDRAM_WriteBuffer(uint32_t* pBuffer, uint32_t uwWriteAddress, uint32_t uwBufferSize) +{ + __IO uint32_t write_pointer = (uint32_t)uwWriteAddress; + + FMC_SDRAMWriteProtectionConfig(FMC_Bank1_SDRAM, DISABLE); + + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) + { + } + + for (; uwBufferSize != 0; uwBufferSize--) + { + + *(uint32_t *) (SDRAM_BANK_ADDR + write_pointer) = *pBuffer++; + + write_pointer += 4; + } + +} + + +void SDRAM_ReadBuffer(uint32_t* pBuffer, uint32_t uwReadAddress, uint32_t uwBufferSize) +{ + __IO uint32_t write_pointer = (uint32_t)uwReadAddress; + + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) + { + } + + for(; uwBufferSize != 0x00; uwBufferSize--) + { + *pBuffer++ = *(__IO uint32_t *)(SDRAM_BANK_ADDR + write_pointer ); + + write_pointer += 4; + } +} diff --git a/radio/src/targets/Horus/serial2_driver.cpp b/radio/src/targets/Horus/serial2_driver.cpp new file mode 100644 index 000000000..12d4bf94f --- /dev/null +++ b/radio/src/targets/Horus/serial2_driver.cpp @@ -0,0 +1,166 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +uint8_t serial2Mode = 0; +Fifo<512> serial2TxFifo; +extern Fifo<512> telemetryFifo; +extern Fifo<32> sbusFifo; + +void uart3Setup(unsigned int baudrate) +{ + USART_InitTypeDef USART_InitStructure; + GPIO_InitTypeDef GPIO_InitStructure; + + RCC_AHB1PeriphClockCmd(SERIAL_RCC_AHB1Periph_GPIO, ENABLE); + + GPIO_PinAFConfig(SERIAL_GPIO, SERIAL_GPIO_PinSource_RX, SERIAL_GPIO_AF); + GPIO_PinAFConfig(SERIAL_GPIO, SERIAL_GPIO_PinSource_TX, SERIAL_GPIO_AF); + + GPIO_InitStructure.GPIO_Pin = SERIAL_GPIO_PIN_TX | SERIAL_GPIO_PIN_RX; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(SERIAL_GPIO, &GPIO_InitStructure); + + RCC_APB1PeriphClockCmd(SERIAL_RCC_APB1Periph_USART, ENABLE); + + USART_InitStructure.USART_BaudRate = baudrate; + USART_InitStructure.USART_WordLength = USART_WordLength_8b; + USART_InitStructure.USART_StopBits = USART_StopBits_1; + USART_InitStructure.USART_Parity = USART_Parity_No; + USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; + USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; + + USART_Init(SERIAL_USART, &USART_InitStructure); + USART_Cmd(SERIAL_USART, ENABLE); + + USART_ITConfig(SERIAL_USART, USART_IT_RXNE, ENABLE); + USART_ITConfig(SERIAL_USART, USART_IT_TXE, DISABLE); + + NVIC_SetPriority(SERIAL_USART_IRQn, 7); + NVIC_EnableIRQ(SERIAL_USART_IRQn); +} + +void serial2Init(unsigned int mode, unsigned int protocol) +{ + USART_DeInit(SERIAL_USART); + + serial2Mode = mode; + + switch (mode) { + case UART_MODE_TELEMETRY_MIRROR: + uart3Setup(FRSKY_SPORT_BAUDRATE); + break; +#if !defined(USB_SERIAL) && (defined(DEBUG) || defined(CLI)) + case UART_MODE_DEBUG: + uart3Setup(DEBUG_BAUDRATE); + break; +#endif + case UART_MODE_TELEMETRY: + if (protocol == PROTOCOL_FRSKY_D_SECONDARY) { + uart3Setup(FRSKY_D_BAUDRATE); + } + break; + } +} + +void serial2Putc(char c) +{ + while (serial2TxFifo.isFull()); + serial2TxFifo.push(c); + USART_ITConfig(SERIAL_USART, USART_IT_TXE, ENABLE); +} + +void serial2SbusInit() +{ + uart3Setup(100000); + SERIAL_USART->CR1 |= USART_CR1_M | USART_CR1_PCE ; +} + +void serial2Stop() +{ + USART_DeInit(SERIAL_USART); +} + +uint8_t serial2TracesEnabled() +{ + return 1; // (serial2Mode == 0); +} + +#if !defined(SIMU) +extern "C" void SERIAL_USART_IRQHandler(void) +{ + // Send + if (USART_GetITStatus(SERIAL_USART, USART_IT_TXE) != RESET) { + uint8_t txchar; + if (serial2TxFifo.pop(txchar)) { + /* Write one byte to the transmit data register */ + USART_SendData(SERIAL_USART, txchar); + } + else { + USART_ITConfig(SERIAL_USART, USART_IT_TXE, DISABLE); + } + } + + // Receive + uint32_t status = SERIAL_USART->SR; + while (status & (USART_FLAG_RXNE | USART_FLAG_ERRORS)) { + uint8_t data = SERIAL_USART->DR; + + if (!(status & USART_FLAG_ERRORS)) { + switch (serial2Mode) { + case UART_MODE_TELEMETRY: + telemetryFifo.push(data); + break; + case UART_MODE_SBUS_TRAINER: + sbusFifo.push(data); + break; +#if defined(CLI) + case UART_MODE_DEBUG: + cliRxFifo.push(data); + break; +#endif + } + } + + status = SERIAL_USART->SR; + } +} +#endif + diff --git a/radio/src/targets/Horus/stm32f4_flash.ld b/radio/src/targets/Horus/stm32f4_flash.ld new file mode 100644 index 000000000..15da897a1 --- /dev/null +++ b/radio/src/targets/Horus/stm32f4_flash.ld @@ -0,0 +1,170 @@ +/* +***************************************************************************** +** +** File : stm32f4_flash.ld +** +** Abstract : Linker script for STM32F407 Device with +** 0.5MByte FLASH, 128KByte SRAM, 64KByte CCM +** +** Set heap size, stack size and stack location according +** to application requirements. +** +** Set memory bank area and size if external memory is used. +** +** Target : STMicroelectronics STM32 +** +***************************************************************************** +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_estack = 0x20020000; /* end of 128K SRAM */ +_heap_end = 0x20020000; /* end of 128K SRAM */ + +/* Generate a link error if heap and stack don't fit into RAM */ +_Min_Heap_Size = 0; /* required amount of heap */ +_Main_Stack_Size = 1024; /* required amount of stack for interrupt stack (Main stack) */ + +/* Main stack end */ +_main_stack_start = _estack - _Main_Stack_Size; + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K + CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K + MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K + SDRAM(xrw) : ORIGIN = 0xD0000000, LENGTH = 8192K +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + + /* The program code and other data goes into FLASH */ + .text : + { + FILL(0xFFFF) + + CREATE_OBJECT_SYMBOLS + + KEEP(*(.isr_vector)) /* Startup code */ + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array*)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = .; + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >RAM + + /* Uninitialized data section */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + __bss_end__ = _ebss; + } >RAM + + PROVIDE ( end = _ebss ); + PROVIDE ( _end = _ebss ); + + /* User_heap_stack section, used to check that there is enough RAM left */ + ._user_heap_stack : + { + . = ALIGN(4); + . = . + _Min_Heap_Size; + . = . + _Main_Stack_Size; + . = ALIGN(4); + } >RAM + + /* MEMORY_bank1 section, code must be located here explicitly */ + /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ + .memory_b1_text : + { + *(.mb1text) /* .mb1text sections (code) */ + *(.mb1text*) /* .mb1text* sections (code) */ + *(.mb1rodata) /* read-only data (constants) */ + *(.mb1rodata*) + } >MEMORY_B1 + + .sdram (NOLOAD) : + { + *(.sdram) + *(.sdram*) + *(.sdram_rodata) + *(.sdram_rodata*) + } >SDRAM + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/radio/src/targets/Horus/system_stm32f4xx.c b/radio/src/targets/Horus/system_stm32f4xx.c new file mode 100644 index 000000000..835635ce7 --- /dev/null +++ b/radio/src/targets/Horus/system_stm32f4xx.c @@ -0,0 +1,569 @@ +/** + ****************************************************************************** + * @file system_stm32f4xx.c + * @author MCD Application Team + * @version V1.1.0 + * @date 21-March-2015 + * @brief CMSIS Cortex-M4 Device Peripheral Access Layer System Source File. + * This file contains the system clock configuration for STM32F4xx devices, + * and is generated by the clock configuration tool + * stm32f4xx_Clock_Configuration_V1.1.0.xls + * + * 1. This file provides two functions and one global variable to be called from + * user application: + * - SystemInit(): Setups the system clock (System clock source, PLL Multiplier + * and Divider factors, AHB/APBx prescalers and Flash settings), + * depending on the configuration made in the clock xls tool. + * This function is called at startup just after reset and + * before branch to main program. This call is made inside + * the "startup_stm32f4xx.s" file. + * + * - SystemCoreClock variable: Contains the core clock (HCLK), it can be used + * by the user application to setup the SysTick + * timer or configure other parameters. + * + * - SystemCoreClockUpdate(): Updates the variable SystemCoreClock and must + * be called whenever the core clock is changed + * during program execution. + * + * 2. After each device reset the HSI (16 MHz) is used as system clock source. + * Then SystemInit() function is called, in "startup_stm32f4xx.s" file, to + * configure the system clock before to branch to main program. + * + * 3. If the system clock source selected by user fails to startup, the SystemInit() + * function will do nothing and HSI still used as system clock source. User can + * add some code to deal with this issue inside the SetSysClock() function. + * + * 4. The default value of HSE crystal is set to 25MHz, refer to "HSE_VALUE" define + * in "stm32f4xx.h" file. When HSE is used as system clock source, directly or + * through PLL, and you are using different crystal you have to adapt the HSE + * value to your own configuration. + * + * 5. This file configures the system clock as follows: + *============================================================================= + *============================================================================= + * Supported STM32F40xx/41xx/427x/437x devices + *----------------------------------------------------------------------------- + * System Clock source | PLL (HSE) + *----------------------------------------------------------------------------- + * SYSCLK(Hz) | 168000000 + *----------------------------------------------------------------------------- + * HCLK(Hz) | 168000000 + *----------------------------------------------------------------------------- + * AHB Prescaler | 1 + *----------------------------------------------------------------------------- + * APB1 Prescaler | 4 + *----------------------------------------------------------------------------- + * APB2 Prescaler | 2 + *----------------------------------------------------------------------------- + * HSE Frequency(Hz) | 12000000 + *----------------------------------------------------------------------------- + * PLL_M | 12 + *----------------------------------------------------------------------------- + * PLL_N | 336 + *----------------------------------------------------------------------------- + * PLL_P | 2 + *----------------------------------------------------------------------------- + * PLL_Q | 7 + *----------------------------------------------------------------------------- + * PLLI2S_N | NA + *----------------------------------------------------------------------------- + * PLLI2S_R | NA + *----------------------------------------------------------------------------- + * I2S input clock | NA + *----------------------------------------------------------------------------- + * VDD(V) | 3.3 + *----------------------------------------------------------------------------- + * Main regulator output voltage | Scale1 mode + *----------------------------------------------------------------------------- + * Flash Latency(WS) | 5 + *----------------------------------------------------------------------------- + * Prefetch Buffer | ON + *----------------------------------------------------------------------------- + * Instruction cache | ON + *----------------------------------------------------------------------------- + * Data cache | ON + *----------------------------------------------------------------------------- + * Require 48MHz for USB OTG FS, | Enabled + * SDIO and RNG clock | + *----------------------------------------------------------------------------- + *============================================================================= + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2013 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/** @addtogroup CMSIS + * @{ + */ + +/** @addtogroup stm32f4xx_system + * @{ + */ + +/** @addtogroup STM32F4xx_System_Private_Includes + * @{ + */ + +#include "stm32f4xx.h" + +/** + * @} + */ + +/** @addtogroup STM32F4xx_System_Private_TypesDefinitions + * @{ + */ + +/** + * @} + */ + +/** @addtogroup STM32F4xx_System_Private_Defines + * @{ + */ + +/************************* Miscellaneous Configuration ************************/ +/*!< Uncomment the following line if you need to use external SRAM mounted + on STM324xG_EVAL/STM324x7I_EVAL board as data memory */ +/* #define DATA_IN_ExtSRAM */ + +/*!< Uncomment the following line if you need to relocate your vector Table in + Internal SRAM. */ +/* #define VECT_TAB_SRAM */ +#define VECT_TAB_OFFSET 0x00 /*!< Vector Table base offset field. + This value must be a multiple of 0x200. */ +/******************************************************************************/ + +/************************* PLL Parameters *************************************/ +/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */ +#define PLL_M 12 +#define PLL_N 336 + +/* SYSCLK = PLL_VCO / PLL_P */ +#define PLL_P 2 + +/* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */ +#define PLL_Q 7 + +/******************************************************************************/ + +/** + * @} + */ + +/** @addtogroup STM32F4xx_System_Private_Macros + * @{ + */ + +/** + * @} + */ + +/** @addtogroup STM32F4xx_System_Private_Variables + * @{ + */ + + uint32_t SystemCoreClock = 180000000; + + __I uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; + +/** + * @} + */ + +/** @addtogroup STM32F4xx_System_Private_FunctionPrototypes + * @{ + */ + +static void SetSysClock(void); +#ifdef DATA_IN_ExtSRAM + static void SystemInit_ExtMemCtl(void); +#endif /* DATA_IN_ExtSRAM */ + +/** + * @} + */ + +/** @addtogroup STM32F4xx_System_Private_Functions + * @{ + */ + +/** + * @brief Setup the microcontroller system + * Initialize the Embedded Flash Interface, the PLL and update the + * SystemFrequency variable. + * @param None + * @retval None + */ +void SystemInit(void) +{ + /* FPU settings ------------------------------------------------------------*/ + #if (__FPU_PRESENT == 1) && (__FPU_USED == 1) + SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */ + #endif + /* Reset the RCC clock configuration to the default reset state ------------*/ + /* Set HSION bit */ + RCC->CR |= (uint32_t)0x00000001; + + /* Reset CFGR register */ + RCC->CFGR = 0x00000000; + + /* Reset HSEON, CSSON and PLLON bits */ + RCC->CR &= (uint32_t)0xFEF6FFFF; + + /* Reset PLLCFGR register */ + RCC->PLLCFGR = 0x24003010; + + /* Reset HSEBYP bit */ + RCC->CR &= (uint32_t)0xFFFBFFFF; + + /* Disable all interrupts */ + RCC->CIR = 0x00000000; + + void SDRAM_Init(); + void FMC_SDRAMWriteProtectionConfig(uint32_t SDRAM_Bank, FunctionalState NewState); + + SDRAM_Init(); // calls SDRAM_GPIOConfig() + FMC_SDRAMWriteProtectionConfig(((uint32_t)0x00000001), DISABLE); + +#ifdef DATA_IN_ExtSRAM + SystemInit_ExtMemCtl(); +#endif /* DATA_IN_ExtSRAM */ + + /* Configure the System clock source, PLL Multiplier and Divider factors, + AHB/APBx prescalers and Flash settings ----------------------------------*/ + SetSysClock(); + + /* Configure the Vector Table location add offset address ------------------*/ +#ifdef VECT_TAB_SRAM + SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */ +#else + // SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */ + extern uint32_t g_pfnVectors ; + SCB->VTOR = (uint32_t) &g_pfnVectors ; /* Vector Table Relocation in Internal FLASH */ +#endif +} + +/** + * @brief Update SystemCoreClock variable according to Clock Register Values. + * The SystemCoreClock variable contains the core clock (HCLK), it can + * be used by the user application to setup the SysTick timer or configure + * other parameters. + * + * @note Each time the core clock (HCLK) changes, this function must be called + * to update SystemCoreClock variable value. Otherwise, any configuration + * based on this variable will be incorrect. + * + * @note - The system frequency computed by this function is not the real + * frequency in the chip. It is calculated based on the predefined + * constant and the selected clock source: + * + * - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*) + * + * - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(**) + * + * - If SYSCLK source is PLL, SystemCoreClock will contain the HSE_VALUE(**) + * or HSI_VALUE(*) multiplied/divided by the PLL factors. + * + * (*) HSI_VALUE is a constant defined in stm32f4xx.h file (default value + * 16 MHz) but the real value may vary depending on the variations + * in voltage and temperature. + * + * (**) HSE_VALUE is a constant defined in stm32f4xx.h file (default value + * 25 MHz), user has to ensure that HSE_VALUE is same as the real + * frequency of the crystal used. Otherwise, this function may + * have wrong result. + * + * - The result of this function could be not correct when using fractional + * value for HSE crystal. + * + * @param None + * @retval None + */ +void SystemCoreClockUpdate(void) +{ + uint32_t tmp = 0, pllvco = 0, pllp = 2, pllsource = 0, pllm = 2; + + /* Get SYSCLK source -------------------------------------------------------*/ + tmp = RCC->CFGR & RCC_CFGR_SWS; + + switch (tmp) + { + case 0x00: /* HSI used as system clock source */ + SystemCoreClock = HSI_VALUE; + break; + case 0x04: /* HSE used as system clock source */ + SystemCoreClock = HSE_VALUE; + break; + case 0x08: /* PLL used as system clock source */ + + /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N + SYSCLK = PLL_VCO / PLL_P + */ + pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) >> 22; + pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM; + + if (pllsource != 0) + { + /* HSE used as PLL clock source */ + pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); + } + else + { + /* HSI used as PLL clock source */ + pllvco = (HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); + } + + pllp = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >>16) + 1 ) *2; + SystemCoreClock = pllvco/pllp; + break; + default: + SystemCoreClock = HSI_VALUE; + break; + } + /* Compute HCLK frequency --------------------------------------------------*/ + /* Get HCLK prescaler */ + tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)]; + /* HCLK frequency */ + SystemCoreClock >>= tmp; +} + +/** + * @brief Configures the System clock source, PLL Multiplier and Divider factors, + * AHB/APBx prescalers and Flash settings + * @Note This function should be called only once the RCC clock configuration + * is reset to the default reset state (done in SystemInit() function). + * @param None + * @retval None + */ +static void SetSysClock(void) +{ +/******************************************************************************/ +/* PLL (clocked by HSE) used as System clock source */ +/******************************************************************************/ + __IO uint32_t StartUpCounter = 0, HSEStatus = 0; + + /* Enable HSE */ + RCC->CR |= ((uint32_t)RCC_CR_HSEON); + + /* Wait till HSE is ready and if Time out is reached exit */ + do + { + HSEStatus = RCC->CR & RCC_CR_HSERDY; + StartUpCounter++; + } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); + + if ((RCC->CR & RCC_CR_HSERDY) != RESET) + { + HSEStatus = (uint32_t)0x01; + } + else + { + HSEStatus = (uint32_t)0x00; + } + + if (HSEStatus == (uint32_t)0x01) + { + /* Select regulator voltage output Scale 1 mode, System frequency up to 168 MHz */ + RCC->APB1ENR |= RCC_APB1ENR_PWREN; + PWR->CR |= PWR_CR_VOS; + + /* HCLK = SYSCLK / 1*/ + RCC->CFGR |= RCC_CFGR_HPRE_DIV1; + + /* PCLK2 = HCLK / 2*/ + RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; + + /* PCLK1 = HCLK / 4*/ + RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; + + /* Configure the main PLL */ + RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | + (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24); + + /* Enable the main PLL */ + RCC->CR |= RCC_CR_PLLON; + + /* Wait till the main PLL is ready */ + while((RCC->CR & RCC_CR_PLLRDY) == 0) + { + } + + /* Configure Flash prefetch, Instruction cache, Data cache and wait state */ + FLASH->ACR = FLASH_ACR_PRFTEN |FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS; + + /* Select the main PLL as system clock source */ + RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); + RCC->CFGR |= RCC_CFGR_SW_PLL; + + /* Wait till the main PLL is used as system clock source */ + while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL); + { + } + } + else + { /* If HSE fails to start-up, the application will have wrong clock + configuration. User can add here some code to deal with this error */ + } + +} + +/** + * @brief Setup the external memory controller. Called in startup_stm32f4xx.s + * before jump to __main + * @param None + * @retval None + */ +#ifdef DATA_IN_ExtSRAM +/** + * @brief Setup the external memory controller. + * Called in startup_stm32f4xx.s before jump to main. + * This function configures the external SRAM mounted on STM324xG_EVAL/STM324x7I_EVAL board + * This SRAM will be used as program data memory (including heap and stack). + * @param None + * @retval None + */ +void SystemInit_ExtMemCtl(void) +{ +/*-- GPIOs Configuration -----------------------------------------------------*/ +/* + +-------------------+--------------------+------------------+------------------+ + + SRAM pins assignment + + +-------------------+--------------------+------------------+------------------+ + | PD0 <-> FSMC_D2 | PE0 <-> FSMC_NBL0 | PF0 <-> FSMC_A0 | PG0 <-> FSMC_A10 | + | PD1 <-> FSMC_D3 | PE1 <-> FSMC_NBL1 | PF1 <-> FSMC_A1 | PG1 <-> FSMC_A11 | + | PD4 <-> FSMC_NOE | PE2 <-> FSMC_A23 | PF2 <-> FSMC_A2 | PG2 <-> FSMC_A12 | + | PD5 <-> FSMC_NWE | PE3 <-> FSMC_A19 | PF3 <-> FSMC_A3 | PG3 <-> FSMC_A13 | + | PD8 <-> FSMC_D13 | PE4 <-> FSMC_A20 | PF4 <-> FSMC_A4 | PG4 <-> FSMC_A14 | + | PD9 <-> FSMC_D14 | PE5 <-> FSMC_A21 | PF5 <-> FSMC_A5 | PG5 <-> FSMC_A15 | + | PD10 <-> FSMC_D15 | PE6 <-> FSMC_A22 | PF12 <-> FSMC_A6 | PG9 <-> FSMC_NE2 | + | PD11 <-> FSMC_A16 | PE7 <-> FSMC_D4 | PF13 <-> FSMC_A7 |------------------+ + | PD12 <-> FSMC_A17 | PE8 <-> FSMC_D5 | PF14 <-> FSMC_A8 | + | PD13 <-> FSMC_A18 | PE9 <-> FSMC_D6 | PF15 <-> FSMC_A9 | + | PD14 <-> FSMC_D0 | PE10 <-> FSMC_D7 |------------------+ + | PD15 <-> FSMC_D1 | PE11 <-> FSMC_D8 | + +-------------------| PE12 <-> FSMC_D9 | + | PE13 <-> FSMC_D10 | + | PE14 <-> FSMC_D11 | + | PE15 <-> FSMC_D12 | + +--------------------+ +*/ + /* Enable GPIOD, GPIOE, GPIOF and GPIOG interface clock */ + RCC->AHB1ENR |= 0x00000078; + + /* Connect PDx pins to FSMC Alternate function */ + GPIOD->AFR[0] = 0x00cc00cc; + GPIOD->AFR[1] = 0xcccccccc; + /* Configure PDx pins in Alternate function mode */ + GPIOD->MODER = 0xaaaa0a0a; + /* Configure PDx pins speed to 100 MHz */ + GPIOD->OSPEEDR = 0xffff0f0f; + /* Configure PDx pins Output type to push-pull */ + GPIOD->OTYPER = 0x00000000; + /* No pull-up, pull-down for PDx pins */ + GPIOD->PUPDR = 0x00000000; + + /* Connect PEx pins to FSMC Alternate function */ + GPIOE->AFR[0] = 0xcccccccc; + GPIOE->AFR[1] = 0xcccccccc; + /* Configure PEx pins in Alternate function mode */ + GPIOE->MODER = 0xaaaaaaaa; + /* Configure PEx pins speed to 100 MHz */ + GPIOE->OSPEEDR = 0xffffffff; + /* Configure PEx pins Output type to push-pull */ + GPIOE->OTYPER = 0x00000000; + /* No pull-up, pull-down for PEx pins */ + GPIOE->PUPDR = 0x00000000; + + /* Connect PFx pins to FSMC Alternate function */ + GPIOF->AFR[0] = 0x00cccccc; + GPIOF->AFR[1] = 0xcccc0000; + /* Configure PFx pins in Alternate function mode */ + GPIOF->MODER = 0xaa000aaa; + /* Configure PFx pins speed to 100 MHz */ + GPIOF->OSPEEDR = 0xff000fff; + /* Configure PFx pins Output type to push-pull */ + GPIOF->OTYPER = 0x00000000; + /* No pull-up, pull-down for PFx pins */ + GPIOF->PUPDR = 0x00000000; + + /* Connect PGx pins to FSMC Alternate function */ + GPIOG->AFR[0] = 0x00cccccc; + GPIOG->AFR[1] = 0x000000c0; + /* Configure PGx pins in Alternate function mode */ + GPIOG->MODER = 0x00080aaa; + /* Configure PGx pins speed to 100 MHz */ + GPIOG->OSPEEDR = 0x000c0fff; + /* Configure PGx pins Output type to push-pull */ + GPIOG->OTYPER = 0x00000000; + /* No pull-up, pull-down for PGx pins */ + GPIOG->PUPDR = 0x00000000; + +/*-- FSMC Configuration ------------------------------------------------------*/ + /* Enable the FSMC interface clock */ + RCC->AHB3ENR |= 0x00000001; + + /* Configure and enable Bank1_SRAM2 */ + FSMC_Bank1->BTCR[2] = 0x00001011; + FSMC_Bank1->BTCR[3] = 0x00000201; + FSMC_Bank1E->BWTR[2] = 0x0fffffff; +/* + Bank1_SRAM2 is configured as follow: + + p.FSMC_AddressSetupTime = 1; + p.FSMC_AddressHoldTime = 0; + p.FSMC_DataSetupTime = 2; + p.FSMC_BusTurnAroundDuration = 0; + p.FSMC_CLKDivision = 0; + p.FSMC_DataLatency = 0; + p.FSMC_AccessMode = FSMC_AccessMode_A; + + FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM2; + FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; + FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM; + FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; + FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; + FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable; + FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low; + FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable; + FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState; + FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable; + FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable; + FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; + FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; + FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p; + FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p; +*/ +} +#endif /* DATA_IN_ExtSRAM */ + + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ + diff --git a/radio/src/targets/Horus/telemetry_driver.cpp b/radio/src/targets/Horus/telemetry_driver.cpp new file mode 100644 index 000000000..998204738 --- /dev/null +++ b/radio/src/targets/Horus/telemetry_driver.cpp @@ -0,0 +1,137 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +extern Fifo<512> telemetryFifo; + +void telemetryPortInit(uint32_t baudrate) +{ + if (baudrate == 0) { + USART_DeInit(TELEMETRY_USART); + return; + } + + USART_InitTypeDef USART_InitStructure; + GPIO_InitTypeDef GPIO_InitStructure; + + RCC_AHB1PeriphClockCmd(TELEMETRY_RCC_AHB1Periph_GPIO, ENABLE); + + GPIO_PinAFConfig(TELEMETRY_GPIO, TELEMETRY_GPIO_PinSource_RX, TELEMETRY_GPIO_AF); + GPIO_PinAFConfig(TELEMETRY_GPIO, TELEMETRY_GPIO_PinSource_TX, TELEMETRY_GPIO_AF); + + GPIO_InitStructure.GPIO_Pin = TELEMETRY_GPIO_PIN_TX | TELEMETRY_GPIO_PIN_RX; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(TELEMETRY_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = TELEMETRY_GPIO_PIN_DIR; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(TELEMETRY_GPIO_DIR, &GPIO_InitStructure); + GPIO_ResetBits(TELEMETRY_GPIO_DIR, TELEMETRY_GPIO_PIN_DIR); + + RCC_APB1PeriphClockCmd(TELEMETRY_RCC_APB1Periph_USART, ENABLE); + + USART_InitStructure.USART_BaudRate = baudrate; + USART_InitStructure.USART_WordLength = USART_WordLength_8b; + USART_InitStructure.USART_StopBits = USART_StopBits_1; + USART_InitStructure.USART_Parity = USART_Parity_No; + USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; + USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; + + USART_Init(TELEMETRY_USART, &USART_InitStructure); + USART_Cmd(TELEMETRY_USART, ENABLE); + USART_ITConfig(TELEMETRY_USART, USART_IT_RXNE, ENABLE); + + NVIC_SetPriority(TELEMETRY_USART_IRQn, 6); + NVIC_EnableIRQ(TELEMETRY_USART_IRQn); +} + +struct SportTxBuffer +{ + uint8_t *ptr; + uint32_t count; +} sportTxBuffer; + +void sportSendBuffer(uint8_t *buffer, uint32_t count) +{ + sportTxBuffer.ptr = buffer ; + sportTxBuffer.count = count ; + TELEMETRY_USART->CR1 &= ~USART_CR1_RE ; // turn off receiver + TELEMETRY_USART->CR1 |= USART_CR1_TXEIE ; +} + +#if !defined(SIMU) +#define USART_FLAG_ERRORS (USART_FLAG_ORE | USART_FLAG_NE | USART_FLAG_FE | USART_FLAG_PE) + +extern "C" void TELEMETRY_USART_IRQHandler() +{ + uint32_t status; + uint8_t data; + + status = TELEMETRY_USART->SR; + + if (status & USART_SR_TXE) { + if (sportTxBuffer.count) { + TELEMETRY_USART->DR = *sportTxBuffer.ptr++; + if (--sportTxBuffer.count == 0) { + TELEMETRY_USART->CR1 &= ~USART_CR1_TXEIE; // stop Tx interrupt + TELEMETRY_USART->CR1 |= USART_CR1_TCIE; // enable complete interrupt + } + } + } + + if ((status & USART_SR_TC) && (TELEMETRY_USART->CR1 & USART_CR1_TCIE)) { + TELEMETRY_USART->CR1 &= ~USART_CR1_TCIE ; // stop Complete interrupt + TELEMETRY_USART->CR1 |= USART_CR1_RE ; + while (status & (USART_FLAG_RXNE)) { + status = TELEMETRY_USART->DR; + status = TELEMETRY_USART->SR ; + } + } + + while (status & (USART_FLAG_RXNE | USART_FLAG_ERRORS)) { + data = TELEMETRY_USART->DR; + if (!(status & USART_FLAG_ERRORS)) { + telemetryFifo.push(data); + } + status = TELEMETRY_USART->SR; + } +} +#endif diff --git a/radio/src/targets/Horus/trainer_driver.cpp b/radio/src/targets/Horus/trainer_driver.cpp new file mode 100644 index 000000000..a3d593152 --- /dev/null +++ b/radio/src/targets/Horus/trainer_driver.cpp @@ -0,0 +1,171 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +extern Fifo<32> sbusFifo; + +#define setupTrainerPulses() setupPulsesPPM(TRAINER_MODULE) + +// Trainer PPM output PB4, Timer 3 channel 1 +void init_trainer_ppm() +{ + trainerPulsesData.ppm.ptr = trainerPulsesData.ppm.pulses; + + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN ; + configure_pins( TRAINER_GPIO_PIN_OUT, PIN_PERIPHERAL | PIN_PORTB | PIN_PER_2 | PIN_OS25 | PIN_PUSHPULL ) ; + configure_pins( TRAINER_GPIO_PIN_IN, PIN_PORTB | PIN_INPUT ) ; + RCC->APB1ENR |= RCC_APB1ENR_TIM3EN ; + TRAINER_TIMER->CR1 &= ~TIM_CR1_CEN ; + + // setupTrainerPulses() is also configuring registers, + // so it has to be called after the peripheral is enabled + setupTrainerPulses() ; + + TRAINER_TIMER->ARR = *trainerPulsesData.ppm.ptr++ ; + TRAINER_TIMER->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 2000000 - 1 ; // 0.5uS + TRAINER_TIMER->CCMR2 = TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4PE ; // PWM mode 1 + TRAINER_TIMER->BDTR = TIM_BDTR_MOE ; + TRAINER_TIMER->EGR = 1 ; + + TRAINER_TIMER->SR &= ~TIM_SR_UIF ; // Clear flag + TRAINER_TIMER->SR &= ~TIM_SR_CC1IF ; // Clear flag + TRAINER_TIMER->DIER |= TIM_DIER_CC1IE ; + TRAINER_TIMER->DIER |= TIM_DIER_UIE ; + + TRAINER_TIMER->CR1 = TIM_CR1_CEN ; + NVIC_EnableIRQ(TRAINER_TIMER_IRQn) ; + NVIC_SetPriority(TRAINER_TIMER_IRQn, 7); +} + +// TODO - testing +void stop_trainer_ppm() +{ + configure_pins( TRAINER_GPIO_PIN_OUT, PIN_INPUT | PIN_PORTB ) ; // Pin as input + TRAINER_TIMER->DIER = 0 ; // Stop Interrupt + TRAINER_TIMER->CR1 &= ~TIM_CR1_CEN ; // Stop counter + NVIC_DisableIRQ(TRAINER_TIMER_IRQn) ; // Stop Interrupt +} + +void set_trainer_ppm_parameters(uint32_t idleTime, uint32_t delay, uint32_t positive) +{ + TRAINER_TIMER->CCR1 = idleTime; + TRAINER_TIMER->CCR4 = delay; + TRAINER_TIMER->CCER = TIM_CCER_CC4E | (positive ? TIM_CCER_CC4P : 0); +} + +// Trainer capture, PC8, Timer 3 channel 3 +void init_trainer_capture() +{ + GPIO_InitTypeDef GPIO_InitStructure; + + RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); + GPIO_InitStructure.GPIO_Pin = TRAINER_GPIO_PIN_IN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(TRAINER_GPIO, &GPIO_InitStructure); + GPIO_PinAFConfig(TRAINER_GPIO, TRAINER_GPIO_PinSource_IN, TRAINER_GPIO_AF); + + RCC->APB1ENR |= RCC_APB1ENR_TIM3EN ; + TRAINER_TIMER->ARR = 0xFFFF ; + TRAINER_TIMER->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 2000000 - 1 ; // 0.5uS + TRAINER_TIMER->CR2 = 0 ; + TRAINER_TIMER->CCMR2 = TIM_CCMR2_IC3F_0 | TIM_CCMR2_IC3F_1 | TIM_CCMR2_CC3S_0 ; + TRAINER_TIMER->CCER = TIM_CCER_CC3E ; + TRAINER_TIMER->SR &= ~TIM_SR_CC3IF & ~TIM_SR_CC2IF & ~TIM_SR_UIF ; // Clear flags + TRAINER_TIMER->DIER |= TIM_DIER_CC3IE ; + TRAINER_TIMER->CR1 = TIM_CR1_CEN ; + NVIC_EnableIRQ(TRAINER_TIMER_IRQn) ; + NVIC_SetPriority(TRAINER_TIMER_IRQn, 7); +} + +void stop_trainer_capture() +{ + TRAINER_TIMER->CR1 &= ~TIM_CR1_CEN ; // Stop counter + TRAINER_TIMER->DIER = 0; // Stop Interrupt + NVIC_DisableIRQ(TRAINER_TIMER_IRQn) ; // Stop Interrupt +} + +#if !defined(SIMU) +extern "C" void TIM3_IRQHandler() +{ + uint16_t capture = 0; + bool doCapture = false ; + + // What mode? in or out? + if ( (TRAINER_TIMER->DIER & TIM_DIER_CC3IE ) && ( TRAINER_TIMER->SR & TIM_SR_CC3IF ) ) { + // capture mode on trainer jack + capture = TRAINER_TIMER->CCR3 ; + doCapture = true; + } + + if ( ( TRAINER_TIMER->DIER & TIM_DIER_CC2IE ) && ( TRAINER_TIMER->SR & TIM_SR_CC2IF ) ) { + // capture mode on heartbeat pin (external module) + capture = TRAINER_TIMER->CCR2 ; + doCapture = true ; + } + + if (doCapture) { + captureTrainerPulses(capture); + } + + // PPM out compare interrupt + if ( ( TRAINER_TIMER->DIER & TIM_DIER_CC1IE ) && ( TRAINER_TIMER->SR & TIM_SR_CC1IF ) ) { + // compare interrupt + TRAINER_TIMER->DIER &= ~TIM_DIER_CC1IE ; // stop this interrupt + TRAINER_TIMER->SR &= ~TIM_SR_CC1IF ; // Clear flag + + setupTrainerPulses() ; + + trainerPulsesData.ppm.ptr = trainerPulsesData.ppm.pulses; + TRAINER_TIMER->DIER |= TIM_DIER_UDE ; + TRAINER_TIMER->SR &= ~TIM_SR_UIF ; // Clear this flag + TRAINER_TIMER->DIER |= TIM_DIER_UIE ; // Enable this interrupt + } + + // PPM out update interrupt + if ( (TRAINER_TIMER->DIER & TIM_DIER_UIE) && ( TRAINER_TIMER->SR & TIM_SR_UIF ) ) { + TRAINER_TIMER->SR &= ~TIM_SR_UIF ; // Clear flag + TRAINER_TIMER->ARR = *trainerPulsesData.ppm.ptr++ ; + if ( *trainerPulsesData.ppm.ptr == 0 ) { + TRAINER_TIMER->SR &= ~TIM_SR_CC1IF ; // Clear this flag + TRAINER_TIMER->DIER |= TIM_DIER_CC1IE ; // Enable this interrupt + } + } +} +#endif diff --git a/radio/src/targets/Horus/usb_bsp.c b/radio/src/targets/Horus/usb_bsp.c new file mode 100644 index 000000000..1f78c4834 --- /dev/null +++ b/radio/src/targets/Horus/usb_bsp.c @@ -0,0 +1,149 @@ +/** + ****************************************************************************** + * @file usb_bsp.c + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief This file is responsible to offer board support package and is + * configurable by user. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ + +#include "usb_bsp.h" + +#include "board_horus.h" +#include "usbd_conf.h" + +extern uint32_t SystemCoreClock; + +/** +* @brief USB_OTG_BSP_Init +* Initilizes BSP configurations +* @param None +* @retval None +*/ + +void USB_OTG_BSP_Init(USB_OTG_CORE_HANDLE *pdev) +{ + GPIO_InitTypeDef GPIO_InitStructure; + + RCC_AHB1PeriphClockCmd(USB_RCC_AHB1Periph_GPIO, ENABLE); + + /* Configure DM and DP Pins */ + GPIO_InitStructure.GPIO_Pin = USB_GPIO_PIN_DM | USB_GPIO_PIN_DP; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(USB_GPIO, &GPIO_InitStructure); + + GPIO_PinAFConfig(USB_GPIO, USB_GPIO_PinSource_DM, USB_GPIO_AF); + GPIO_PinAFConfig(USB_GPIO, USB_GPIO_PinSource_DP, USB_GPIO_AF); + + /* Configure VBUS Pin */ + GPIO_InitStructure.GPIO_Pin = USB_GPIO_PIN_VBUS; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; + GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; + GPIO_Init(USB_GPIO, &GPIO_InitStructure); + + RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); + RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS, ENABLE) ; +} + +void USB_OTG_BSP_Deinit(USB_OTG_CORE_HANDLE *pdev) +{ + //nothing to do +} + +/** +* @brief USB_OTG_BSP_EnableInterrupt +* Enable USB Global interrupt +* @param None +* @retval None +*/ +void USB_OTG_BSP_EnableInterrupt(USB_OTG_CORE_HANDLE *pdev) +{ + NVIC_InitTypeDef NVIC_InitStructure; + + NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); + NVIC_InitStructure.NVIC_IRQChannel = OTG_FS_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure); +} + +/** +* @brief USB_OTG_BSP_DisableInterrupt +* Disable USB Global interrupt +* @param None +* @retval None +*/ +void USB_OTG_BSP_DisableInterrupt(USB_OTG_CORE_HANDLE *pdev) +{ + NVIC_InitTypeDef NVIC_InitStructure; + + NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); + NVIC_InitStructure.NVIC_IRQChannel = OTG_FS_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; + NVIC_InitStructure.NVIC_IRQChannelCmd = DISABLE; + NVIC_Init(&NVIC_InitStructure); +} + + +/** +* @brief USB_OTG_BSP_uDelay +* This function provides delay time in micro sec +* @param usec : Value of delay required in micro sec +* @retval None +*/ +void USB_OTG_BSP_uDelay (const uint32_t usec) +{ + uint32_t count = 0; + const uint32_t utime = ((SystemCoreClock/1000000) * usec); + do + { + if ( ++count > utime ) + { + return ; + } + } + while (1); +} + + +/** +* @brief USB_OTG_BSP_mDelay +* This function provides delay time in milli sec +* @param msec : Value of delay required in milli sec +* @retval None +*/ +void USB_OTG_BSP_mDelay (const uint32_t msec) +{ + USB_OTG_BSP_uDelay(msec * 1000); +} + + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/Horus/usb_conf.h b/radio/src/targets/Horus/usb_conf.h new file mode 100644 index 000000000..c285e607d --- /dev/null +++ b/radio/src/targets/Horus/usb_conf.h @@ -0,0 +1,171 @@ +/** + ****************************************************************************** + * @file usb_conf.h + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief General low level driver configuration + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __USB_CONF__H__ +#define __USB_CONF__H__ + +#include "STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h" + +/* USB Core and PHY interface configuration. + Tip: To avoid modifying these defines each time you need to change the USB + configuration, you can declare the needed define in your toolchain + compiler preprocessor. + */ +/****************** USB OTG FS PHY CONFIGURATION ******************************* +* The USB OTG FS Core supports one on-chip Full Speed PHY. +* +* The USE_EMBEDDED_PHY symbol is defined in the project compiler preprocessor +* when FS core is used. +*******************************************************************************/ +#ifndef USE_USB_OTG_FS + #define USE_USB_OTG_FS +#endif /* USE_USB_OTG_FS */ + +#ifdef USE_USB_OTG_FS + #define USB_OTG_FS_CORE +#endif + +/******************************************************************************* +* FIFO Size Configuration in Device mode +* +* (i) Receive data FIFO size = RAM for setup packets + +* OUT endpoint control information + +* data OUT packets + miscellaneous +* Space = ONE 32-bits words +* --> RAM for setup packets = 10 spaces +* (n is the nbr of CTRL EPs the device core supports) +* --> OUT EP CTRL info = 1 space +* (one space for status information written to the FIFO along with each +* received packet) +* --> data OUT packets = (Largest Packet Size / 4) + 1 spaces +* (MINIMUM to receive packets) +* --> OR data OUT packets = at least 2*(Largest Packet Size / 4) + 1 spaces +* (if high-bandwidth EP is enabled or multiple isochronous EPs) +* --> miscellaneous = 1 space per OUT EP +* (one space for transfer complete status information also pushed to the +* FIFO with each endpoint's last packet) +* +* (ii)MINIMUM RAM space required for each IN EP Tx FIFO = MAX packet size for +* that particular IN EP. More space allocated in the IN EP Tx FIFO results +* in a better performance on the USB and can hide latencies on the AHB. +* +* (iii) TXn min size = 16 words. (n : Transmit FIFO index) +* (iv) When a TxFIFO is not used, the Configuration should be as follows: +* case 1 : n > m and Txn is not used (n,m : Transmit FIFO indexes) +* --> Txm can use the space allocated for Txn. +* case2 : n < m and Txn is not used (n,m : Transmit FIFO indexes) +* --> Txn should be configured with the minimum space of 16 words +* (v) The FIFO is used optimally when used TxFIFOs are allocated in the top +* of the FIFO.Ex: use EP1 and EP2 as IN instead of EP1 and EP3 as IN ones. +* (vi) In HS case12 FIFO locations should be reserved for internal DMA registers +* so total FIFO size should be 1012 Only instead of 1024 +*******************************************************************************/ + +/****************** USB OTG FS CONFIGURATION **********************************/ +#ifdef USB_OTG_FS_CORE + #define RX_FIFO_FS_SIZE 128 + #define TX0_FIFO_FS_SIZE 64 + #define TX1_FIFO_FS_SIZE 128 + #define TX2_FIFO_FS_SIZE 0 + #define TX3_FIFO_FS_SIZE 0 + +// #define USB_OTG_FS_LOW_PWR_MGMT_SUPPORT +// #define USB_OTG_FS_SOF_OUTPUT_ENABLED +#endif + +/****************** USB OTG MISC CONFIGURATION ********************************/ +#define VBUS_SENSING_ENABLED + +/****************** USB OTG MODE CONFIGURATION ********************************/ +//#define USE_HOST_MODE +#define USE_DEVICE_MODE +//#define USE_OTG_MODE + +#ifndef USB_OTG_FS_CORE + #ifndef USB_OTG_HS_CORE + #error "USB_OTG_HS_CORE or USB_OTG_FS_CORE should be defined" + #endif +#endif + +#ifndef USE_DEVICE_MODE + #ifndef USE_HOST_MODE + #error "USE_DEVICE_MODE or USE_HOST_MODE should be defined" + #endif +#endif + +#ifndef USE_USB_OTG_HS + #ifndef USE_USB_OTG_FS + #error "USE_USB_OTG_HS or USE_USB_OTG_FS should be defined" + #endif +#else //USE_USB_OTG_HS + #ifndef USE_ULPI_PHY + #ifndef USE_EMBEDDED_PHY + #error "USE_ULPI_PHY or USE_EMBEDDED_PHY should be defined" + #endif + #endif +#endif + +/****************** C Compilers dependant keywords ****************************/ +/* In HS mode and when the DMA is used, all variables and data structures dealing + with the DMA during the transaction process should be 4-bytes aligned */ +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED + #if defined (__GNUC__) /* GNU Compiler */ + #define __ALIGN_END __attribute__ ((aligned (4))) + #define __ALIGN_BEGIN + #else + #define __ALIGN_END + #if defined (__CC_ARM) /* ARM Compiler */ + #define __ALIGN_BEGIN __align(4) + #elif defined (__ICCARM__) /* IAR Compiler */ + #define __ALIGN_BEGIN + #elif defined (__TASKING__) /* TASKING Compiler */ + #define __ALIGN_BEGIN __align(4) + #endif /* __CC_ARM */ + #endif /* __GNUC__ */ +#else + #define __ALIGN_BEGIN + #define __ALIGN_END +#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */ + +/* __packed keyword used to decrease the data type alignment to 1-byte */ +#if !defined(__packed) + #if defined (__CC_ARM) /* ARM Compiler */ + #define __packed __packed + #elif defined (__ICCARM__) /* IAR Compiler */ + #define __packed __packed + #elif defined ( __GNUC__ ) /* GNU Compiler */ + #define __packed __attribute__ ((__packed__)) + #elif defined (__TASKING__) /* TASKING Compiler */ + #define __packed __unaligned + #endif /* __CC_ARM */ +#endif + +#endif //__USB_CONF__H__ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ + diff --git a/radio/src/targets/Horus/usb_driver.c b/radio/src/targets/Horus/usb_driver.c new file mode 100644 index 000000000..71aaf8fb6 --- /dev/null +++ b/radio/src/targets/Horus/usb_driver.c @@ -0,0 +1,85 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "board_horus.h" +#include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_dcd_int.h" + +int usbPlugged(void) +{ + // debounce + static uint8_t debounced_state = 0; + static uint8_t last_state = 0; + + if (GPIO_ReadInputDataBit(USB_GPIO, USB_GPIO_PIN_VBUS)) { + if (last_state) { + debounced_state = 1; + } + last_state = 1; + } + else { + if (!last_state) { + debounced_state = 0; + } + last_state = 0; + } + return debounced_state; +} + +USB_OTG_CORE_HANDLE USB_OTG_dev; + +void OTG_FS_IRQHandler(void) +{ + USBD_OTG_ISR_Handler(&USB_OTG_dev); +} + +void usbInit(void) +{ +#if defined(USB_JOYSTICK) + // initialize USB as HID device + USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_HID_cb, &USR_cb); +#elif defined(USB_SERIAL) + // initialize USB as CDC device (virtual serial port) + USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_CDC_cb, &USR_cb); +#elif defined(USB_MASS_STORAGE) + // initialize USB as MSC device + USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_MSC_cb, &USR_cb); +#endif +} + +void usbDeInit(void) +{ + USBD_DeInit(&USB_OTG_dev); +} diff --git a/radio/src/targets/Horus/usbd_cdc.cpp b/radio/src/targets/Horus/usbd_cdc.cpp new file mode 100644 index 000000000..7b1a44634 --- /dev/null +++ b/radio/src/targets/Horus/usbd_cdc.cpp @@ -0,0 +1,222 @@ +/** + ****************************************************************************** + * @file usbd_cdc_vcp.c + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief Generic media access Layer. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED +#pragma data_alignment = 4 +#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */ + +#include "opentx.h" + +extern "C" { + +/* Includes ------------------------------------------------------------------*/ +#include "usb_conf.h" + +/* Private typedef -----------------------------------------------------------*/ +/* Private define ------------------------------------------------------------*/ +/* Private macro -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ + + +/* These are external variables imported from CDC core to be used for IN + transfer management. */ +extern uint8_t APP_Rx_Buffer []; /* Write CDC received data in this buffer. + These data will be sent over USB IN endpoint + in the CDC core functions. */ +extern volatile uint32_t APP_Rx_ptr_in; /* Increment this pointer or roll it back to + start address when writing received data + in the buffer APP_Rx_Buffer. */ +extern volatile uint32_t APP_Rx_ptr_out; + +/* Private function prototypes -----------------------------------------------*/ +static uint16_t VCP_Init (void); +static uint16_t VCP_DeInit (void); +static uint16_t VCP_Ctrl (uint32_t Cmd, uint8_t* Buf, uint32_t Len); +static uint16_t VCP_DataRx (uint8_t* Buf, uint32_t Len); + +// static uint16_t VCP_COMConfig(uint8_t Conf); + +CDC_IF_Prop_TypeDef VCP_fops = +{ + VCP_Init, + VCP_DeInit, + VCP_Ctrl, + 0, + VCP_DataRx +}; + +} // extern "C" + +bool cdcConnected = false; + +/* Private functions ---------------------------------------------------------*/ +/** + * @brief VCP_Init + * Initializes the Media on the STM32 + * @param None + * @retval Result of the opeartion (USBD_OK in all cases) + */ +static uint16_t VCP_Init(void) +{ + cdcConnected = true; + return USBD_OK; +} + +/** + * @brief VCP_DeInit + * DeInitializes the Media on the STM32 + * @param None + * @retval Result of the opeartion (USBD_OK in all cases) + */ +static uint16_t VCP_DeInit(void) +{ + cdcConnected = false; + return USBD_OK; +} + + +/** + * @brief VCP_Ctrl + * Manage the CDC class requests + * @param Cmd: Command code + * @param Buf: Buffer containing command data (request parameters) + * @param Len: Number of data to be sent (in bytes) + * @retval Result of the opeartion (USBD_OK in all cases) + */ +static uint16_t VCP_Ctrl (uint32_t Cmd, uint8_t* Buf, uint32_t Len) +{ + switch (Cmd) + { + case SEND_ENCAPSULATED_COMMAND: + /* Not needed for this driver */ + break; + + case GET_ENCAPSULATED_RESPONSE: + /* Not needed for this driver */ + break; + + case SET_COMM_FEATURE: + /* Not needed for this driver */ + break; + + case GET_COMM_FEATURE: + /* Not needed for this driver */ + break; + + case CLEAR_COMM_FEATURE: + /* Not needed for this driver */ + break; + + case SET_LINE_CODING: + /* Not needed for this driver */ + break; + + case GET_LINE_CODING: + /* Not needed for this driver */ + break; + + case SET_CONTROL_LINE_STATE: + /* Not needed for this driver */ + break; + + case SEND_BREAK: + /* Not needed for this driver */ + break; + + default: + break; + } + + return USBD_OK; +} + +// some debug vars +uint16_t usbWraps = 0; +uint16_t charsWritten = 0; + +void usbSerialPutc(uint8_t c) +{ + if (!cdcConnected) return; + + uint32_t txDataLen; + do { + txDataLen = APP_RX_DATA_SIZE + APP_Rx_ptr_in - APP_Rx_ptr_out; + if (txDataLen >= APP_RX_DATA_SIZE) { + txDataLen -= APP_RX_DATA_SIZE; + } + } while (txDataLen >= (APP_RX_DATA_SIZE - CDC_DATA_MAX_PACKET_SIZE)); + + APP_Rx_Buffer[APP_Rx_ptr_in] = c; + ++charsWritten; + /* To avoid buffer overflow */ + if (APP_Rx_ptr_in >= APP_RX_DATA_SIZE-1) { + APP_Rx_ptr_in = 0; + ++usbWraps; + } + else { + APP_Rx_ptr_in++; + } +} + +/** + * @brief VCP_DataRx + * Data received over USB OUT endpoint is available here + * + * @note + * This function will block any OUT packet reception on USB endpoint + * until exiting this function. If you exit this function before transfer + * is complete on CDC interface (ie. using DMA controller) it will result + * in receiving more data while previous ones are still not sent. + * + * @note + * This function is executed inside the USBD_OTG_ISR_Handler() interrupt handler! + + * @param Buf: Buffer of data to be received + * @param Len: Number of data received (in bytes) + * @retval Result of the opeartion: USBD_OK if all operations are OK else VCP_FAIL + */ +static uint16_t VCP_DataRx (uint8_t* Buf, uint32_t Len) +{ + // TODO: try implementing inbound flow control: + // if the cliRxFifo does not have enough free space to receive all + // available characters, return VCP_FAIL. Maybe that will throttle down + // the sender and we will receive the same packet at a later time. + +#if defined(CLI) + //copy data to the application FIFO + for (uint32_t i = 0; i < Len; i++) + { + cliRxFifo.push(Buf[i]); + } +#endif + + return USBD_OK; +} + + + +// /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/Horus/usbd_conf.h b/radio/src/targets/Horus/usbd_conf.h new file mode 100644 index 000000000..557973a26 --- /dev/null +++ b/radio/src/targets/Horus/usbd_conf.h @@ -0,0 +1,71 @@ +/** + ****************************************************************************** + * @file usbd_conf.h + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief USB Device configuration file + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __USBD_CONF__H__ +#define __USBD_CONF__H__ + +/* Includes ------------------------------------------------------------------*/ +#include "usb_conf.h" + +#define USBD_CFG_MAX_NUM 1 +#define USBD_ITF_MAX_NUM 1 +#define USB_MAX_STR_DESC_SIZ 64 + +#define USBD_SELF_POWERED + +/* Class Layer Parameter */ + +#define MSC_IN_EP 0x81 +#define MSC_OUT_EP 0x01 +#define MSC_MAX_PACKET 64 + +#define MSC_MEDIA_PACKET 4096 + +#define HID_IN_EP 0x81 +#define HID_OUT_EP 0x01 + +#define HID_IN_PACKET 9 +#define HID_OUT_PACKET 9 + +#define CDC_IN_EP 0x81 /* EP1 for data IN */ +#define CDC_OUT_EP 0x01 /* EP1 for data OUT */ +#define CDC_CMD_EP 0x82 /* EP2 for CDC commands */ + +/* CDC Endpoints parameters: you can fine tune these values depending on the needed baudrates and performance. */ +#define CDC_DATA_MAX_PACKET_SIZE 64 /* Endpoint IN & OUT Packet size */ +#define CDC_CMD_PACKET_SZE 8 /* Control Endpoint Packet size */ + +#define CDC_IN_FRAME_INTERVAL 5 /* Number of frames between IN transfers */ +#define APP_RX_DATA_SIZE 512 // USB serial port output buffer. TODO: tune this buffer size /* Total size of IN buffer: APP_RX_DATA_SIZE*8/MAX_BAUDARATE*1000 should be > CDC_IN_FRAME_INTERVAL */ +#define APP_FOPS VCP_fops + +#endif //__USBD_CONF__H__ + + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ + diff --git a/radio/src/targets/Horus/usbd_desc.c b/radio/src/targets/Horus/usbd_desc.c new file mode 100644 index 000000000..541200a16 --- /dev/null +++ b/radio/src/targets/Horus/usbd_desc.c @@ -0,0 +1,242 @@ +/** + ****************************************************************************** + * @file usbd_desc.c + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief This file provides the USBD descriptors and string formating method. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include +#include "usbd_core.h" +#include "usbd_desc.h" +#include "usbd_req.h" +#include "usbd_conf.h" +#include "usb_regs.h" +#include "board_horus.h" + +/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY + * @{ + */ + + +/** @defgroup USBD_DESC + * @brief USBD descriptors module + * @{ + */ + +/** @defgroup USBD_DESC_Private_TypesDefinitions + * @{ + */ +/** + * @} + */ + + +/** @defgroup USBD_DESC_Private_Defines + * @{ + */ + +#define USBD_VID 0x0483 + +#define USBD_LANGID_STRING 0x409 +#define USBD_MANUFACTURER_STRING "FrSky" +#define USBD_SERIALNUMBER_FS_STRING "00000000001B" + + +#if defined(BOOT) + #define USBD_PID 0x5720 + #define USBD_PRODUCT_FS_STRING "FrSky Taranis Bootloader" + #define USBD_CONFIGURATION_FS_STRING "MSC Config" + #define USBD_INTERFACE_FS_STRING "MSC Interface" +#elif defined(USB_JOYSTICK) + #define USBD_PID 0x5710 + #define USBD_PRODUCT_FS_STRING "FrSky Taranis Joystick" + #define USBD_CONFIGURATION_FS_STRING "HID Config" + #define USBD_INTERFACE_FS_STRING "HID Interface" +#elif defined(USB_SERIAL) + #define USBD_PID 0x5740 // do not change, this ID is used by the ST USB driver for Windows + #define USBD_PRODUCT_FS_STRING "FrSky Taranis Serial Port" + #define USBD_CONFIGURATION_FS_STRING "VSP Config" + #define USBD_INTERFACE_FS_STRING "VSP Interface" +#elif defined(USB_MASS_STORAGE) + #define USBD_PID 0x5720 + #define USBD_PRODUCT_FS_STRING "FrSky Taranis Mass Storage" + #define USBD_CONFIGURATION_FS_STRING "MSC Config" + #define USBD_INTERFACE_FS_STRING "MSC Interface" +#endif + +const USBD_DEVICE USR_desc = +{ + USBD_USR_DeviceDescriptor, + USBD_USR_LangIDStrDescriptor, + USBD_USR_ManufacturerStrDescriptor, + USBD_USR_ProductStrDescriptor, + USBD_USR_SerialStrDescriptor, + USBD_USR_ConfigStrDescriptor, + USBD_USR_InterfaceStrDescriptor, +}; + +/* USB Standard Device Descriptor */ +__ALIGN_BEGIN const uint8_t USBD_DeviceDesc[USB_SIZ_DEVICE_DESC] __ALIGN_END = +{ + USB_SIZ_DEVICE_DESC, /*bLength */ + USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/ + 0x00, /*bcdUSB */ + 0x02, + 0x00, /*bDeviceClass*/ + 0x00, /*bDeviceSubClass*/ + 0x00, /*bDeviceProtocol*/ + USB_OTG_MAX_EP0_SIZE, /*bMaxPacketSize*/ + LOBYTE(USBD_VID), /*idVendor*/ + HIBYTE(USBD_VID), /*idVendor*/ + LOBYTE(USBD_PID), /*idVendor*/ + HIBYTE(USBD_PID), /*idVendor*/ + 0x00, /*bcdDevice rel. 2.00*/ + 0x02, + USBD_IDX_MFC_STR, /*Index of manufacturer string*/ + USBD_IDX_PRODUCT_STR, /*Index of product string*/ + USBD_IDX_SERIAL_STR, /*Index of serial number string*/ + USBD_CFG_MAX_NUM /*bNumConfigurations*/ +}; /* USB_DeviceDescriptor */ + +/* USB Standard Device Descriptor */ +__ALIGN_BEGIN const uint8_t USBD_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] __ALIGN_END = +{ + USB_LEN_DEV_QUALIFIER_DESC, + USB_DESC_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x01, + 0x00, +}; + +/* USB Standard Device Descriptor */ +__ALIGN_BEGIN const uint8_t USBD_LangIDDesc[USB_SIZ_STRING_LANGID] __ALIGN_END = +{ + USB_SIZ_STRING_LANGID, + USB_DESC_TYPE_STRING, + LOBYTE(USBD_LANGID_STRING), + HIBYTE(USBD_LANGID_STRING), +}; + +/* +* @brief USBD_USR_DeviceDescriptor +* return the device descriptor +* @param speed : current device speed +* @param length : pointer to data length variable +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_USR_DeviceDescriptor( uint8_t speed , uint16_t *length) +{ + *length = sizeof(USBD_DeviceDesc); + memcpy(USBD_StrDesc, USBD_DeviceDesc, *length); + return USBD_StrDesc; +} + +/** +* @brief USBD_USR_LangIDStrDescriptor +* return the LangID string descriptor +* @param speed : current device speed +* @param length : pointer to data length variable +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_USR_LangIDStrDescriptor( uint8_t speed , uint16_t *length) +{ + *length = sizeof(USBD_LangIDDesc); + memcpy(USBD_StrDesc, USBD_LangIDDesc, *length); + return USBD_StrDesc; +} + + +/** +* @brief USBD_USR_ProductStrDescriptor +* return the product string descriptor +* @param speed : current device speed +* @param length : pointer to data length variable +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_USR_ProductStrDescriptor( uint8_t speed , uint16_t *length) +{ + USBD_GetString ((uint8_t*)USBD_PRODUCT_FS_STRING, USBD_StrDesc, length); + return USBD_StrDesc; +} + +/** +* @brief USBD_USR_ManufacturerStrDescriptor +* return the manufacturer string descriptor +* @param speed : current device speed +* @param length : pointer to data length variable +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_USR_ManufacturerStrDescriptor( uint8_t speed , uint16_t *length) +{ + USBD_GetString ((uint8_t*)USBD_MANUFACTURER_STRING, USBD_StrDesc, length); + return USBD_StrDesc; +} + +/** +* @brief USBD_USR_SerialStrDescriptor +* return the serial number string descriptor +* @param speed : current device speed +* @param length : pointer to data length variable +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_USR_SerialStrDescriptor( uint8_t speed , uint16_t *length) +{ + USBD_GetString ((uint8_t*)USBD_SERIALNUMBER_FS_STRING, USBD_StrDesc, length); + return USBD_StrDesc; +} + +/** +* @brief USBD_USR_ConfigStrDescriptor +* return the configuration string descriptor +* @param speed : current device speed +* @param length : pointer to data length variable +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_USR_ConfigStrDescriptor( uint8_t speed , uint16_t *length) +{ + USBD_GetString ((uint8_t *)USBD_CONFIGURATION_FS_STRING, USBD_StrDesc, length); + return USBD_StrDesc; +} + + +/** +* @brief USBD_USR_InterfaceStrDescriptor +* return the interface string descriptor +* @param speed : current device speed +* @param length : pointer to data length variable +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_USR_InterfaceStrDescriptor( uint8_t speed , uint16_t *length) +{ + USBD_GetString ((uint8_t*)USBD_INTERFACE_FS_STRING, USBD_StrDesc, length); + return USBD_StrDesc; +} + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ + diff --git a/radio/src/targets/Horus/usbd_desc.h b/radio/src/targets/Horus/usbd_desc.h new file mode 100644 index 000000000..7a9d5b564 --- /dev/null +++ b/radio/src/targets/Horus/usbd_desc.h @@ -0,0 +1,120 @@ +/** + ****************************************************************************** + * @file usbd_desc.h + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief header file for the usbd_desc.c file + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ + +#ifndef __USB_DESC_H +#define __USB_DESC_H + +/* Includes ------------------------------------------------------------------*/ +#include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_def.h" + +/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY + * @{ + */ + +/** @defgroup USB_DESC + * @brief general defines for the usb device library file + * @{ + */ + +/** @defgroup USB_DESC_Exported_Defines + * @{ + */ +#define USB_DEVICE_DESCRIPTOR_TYPE 0x01 +#define USB_CONFIGURATION_DESCRIPTOR_TYPE 0x02 +#define USB_STRING_DESCRIPTOR_TYPE 0x03 +#define USB_INTERFACE_DESCRIPTOR_TYPE 0x04 +#define USB_ENDPOINT_DESCRIPTOR_TYPE 0x05 +#define USB_SIZ_DEVICE_DESC 18 +#define USB_SIZ_STRING_LANGID 4 + +/** + * @} + */ + + +/** @defgroup USBD_DESC_Exported_TypesDefinitions + * @{ + */ +/** + * @} + */ + + + +/** @defgroup USBD_DESC_Exported_Macros + * @{ + */ +/** + * @} + */ + +/** @defgroup USBD_DESC_Exported_Variables + * @{ + */ +extern const uint8_t USBD_DeviceDesc [USB_SIZ_DEVICE_DESC]; +extern uint8_t USBD_StrDesc[USB_MAX_STR_DESC_SIZ]; +extern const uint8_t USBD_OtherSpeedCfgDesc[USB_LEN_CFG_DESC]; +extern const uint8_t USBD_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC]; +extern const uint8_t USBD_LangIDDesc[USB_SIZ_STRING_LANGID]; +extern const USBD_DEVICE USR_desc; +/** + * @} + */ + +/** @defgroup USBD_DESC_Exported_FunctionsPrototype + * @{ + */ + + +uint8_t * USBD_USR_DeviceDescriptor( uint8_t speed , uint16_t *length); +uint8_t * USBD_USR_LangIDStrDescriptor( uint8_t speed , uint16_t *length); +uint8_t * USBD_USR_ManufacturerStrDescriptor ( uint8_t speed , uint16_t *length); +uint8_t * USBD_USR_ProductStrDescriptor ( uint8_t speed , uint16_t *length); +uint8_t * USBD_USR_SerialStrDescriptor( uint8_t speed , uint16_t *length); +uint8_t * USBD_USR_ConfigStrDescriptor( uint8_t speed , uint16_t *length); +uint8_t * USBD_USR_InterfaceStrDescriptor( uint8_t speed , uint16_t *length); + +#ifdef USB_SUPPORT_USER_STRING_DESC +uint8_t * USBD_USR_USRStringDesc (uint8_t speed, uint8_t idx , uint16_t *length); +#endif /* USB_SUPPORT_USER_STRING_DESC */ + +/** + * @} + */ + +#endif /* __USBD_DESC_H */ + +/** + * @} + */ + +/** +* @} +*/ +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/Horus/usbd_hid_joystick.c b/radio/src/targets/Horus/usbd_hid_joystick.c new file mode 100644 index 000000000..33a6c3d85 --- /dev/null +++ b/radio/src/targets/Horus/usbd_hid_joystick.c @@ -0,0 +1,479 @@ +/** + ****************************************************************************** + * @file usbd_hid_core.c + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief This file provides the HID core functions. + * + * @verbatim + * + * =================================================================== + * HID Class Description + * =================================================================== + * This module manages the HID class V1.11 following the "Device Class Definition + * for Human Interface Devices (HID) Version 1.11 Jun 27, 2001". + * This driver implements the following aspects of the specification: + * - The Boot Interface Subclass + * - The Mouse protocol + * - Usage Page : Generic Desktop + * - Usage : Joystick) + * - Collection : Application + * + * @note In HS mode and when the DMA is used, all variables and data structures + * dealing with the DMA during the transaction process should be 32-bit aligned. + * + * + * @endverbatim + * + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "usbd_hid_core.h" +#include "usbd_req.h" +#include "usbd_desc.h" + +/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY + * @{ + */ + + +/** @defgroup USBD_HID + * @brief usbd core module + * @{ + */ + +/** @defgroup USBD_HID_Private_TypesDefinitions + * @{ + */ +/** + * @} + */ + + +/** @defgroup USBD_HID_Private_Defines + * @{ + */ + +/** + * @} + */ + + +/** @defgroup USBD_HID_Private_Macros + * @{ + */ +/** + * @} + */ + + + + +/** @defgroup USBD_HID_Private_FunctionPrototypes + * @{ + */ + + +static uint8_t USBD_HID_Init (void *pdev, + uint8_t cfgidx); + +static uint8_t USBD_HID_DeInit (void *pdev, + uint8_t cfgidx); + +static uint8_t USBD_HID_Setup (void *pdev, + USB_SETUP_REQ *req); + +static const uint8_t *USBD_HID_GetCfgDesc (uint8_t speed, uint16_t *length); + +static uint8_t USBD_HID_DataIn (void *pdev, uint8_t epnum); +/** + * @} + */ + + + +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED + #if defined ( __ICCARM__ ) /*!< IAR Compiler */ + #pragma data_alignment=4 + #endif +#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */ +/* + This USB HID endpoint report description defines a device with: + * 8 digital buttons + * 8 analog axes with 8bit resolution + + Repot packet described as C struct is: + + struct { + uint8_t buttons; //bit 0 - button 1, bit 1 - button 2, ..., mapped to channels 9-16, on if channel > 0 + uint8_t X; //analog value, mapped to channel 1 + uint8_t Y; //analog value, mapped to channel 2 + uint8_t Z; //analog value, mapped to channel 3 + uint8_t Rx; //analog value, mapped to channel 4 + uint8_t Ry //analog value, mapped to channel 5 + uint8_t Rz; //analog value, mapped to channel 6 + uint8_t S1; //analog value, mapped to channel 7 + uint8_t S2; //analog value, mapped to channel 8 + } +*/ +__ALIGN_BEGIN static const uint8_t HID_JOYSTICK_ReportDesc[] __ALIGN_END = +{ + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x05, // USAGE (Game Pad) + 0xa1, 0x01, // COLLECTION (Application) + 0xa1, 0x00, // COLLECTION (Physical) + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x08, // USAGE_MAXIMUM (Button 8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x08, // REPORT_COUNT (8) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x09, 0x32, // USAGE (Z) + 0x09, 0x33, // USAGE (Rx) + 0x09, 0x34, // USAGE (Ry) + 0x09, 0x35, // USAGE (Rz) + 0x09, 0x36, // USAGE (Slider) + 0x09, 0x36, // USAGE (Slider) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x08, // REPORT_COUNT (8) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION +}; + + +/** @defgroup USBD_HID_Private_Variables + * @{ + */ + +const USBD_Class_cb_TypeDef USBD_HID_cb = +{ + USBD_HID_Init, + USBD_HID_DeInit, + USBD_HID_Setup, + NULL, /*EP0_TxSent*/ + NULL, /*EP0_RxReady*/ + USBD_HID_DataIn, /*DataIn*/ + NULL, /*DataOut*/ + NULL, /*SOF */ + NULL, + NULL, + USBD_HID_GetCfgDesc, +#ifdef USB_OTG_HS_CORE + USBD_HID_GetCfgDesc, /* use same config as per FS */ +#endif +}; + +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED + #if defined ( __ICCARM__ ) /*!< IAR Compiler */ + #pragma data_alignment=4 + #endif +#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */ +__ALIGN_BEGIN static uint32_t USBD_HID_AltSet __ALIGN_END = 0; + +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED + #if defined ( __ICCARM__ ) /*!< IAR Compiler */ + #pragma data_alignment=4 + #endif +#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */ +__ALIGN_BEGIN static uint32_t USBD_HID_Protocol __ALIGN_END = 0; + +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED + #if defined ( __ICCARM__ ) /*!< IAR Compiler */ + #pragma data_alignment=4 + #endif +#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */ +__ALIGN_BEGIN static uint32_t USBD_HID_IdleState __ALIGN_END = 0; + +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED + #if defined ( __ICCARM__ ) /*!< IAR Compiler */ + #pragma data_alignment=4 + #endif +#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */ +/* USB HID device Configuration Descriptor */ +__ALIGN_BEGIN static const uint8_t USBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END = +{ + 0x09, /* bLength: Configuration Descriptor size */ + USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */ + USB_HID_CONFIG_DESC_SIZ, + /* wTotalLength: Bytes returned */ + 0x00, + 0x01, /*bNumInterfaces: 1 interface*/ + 0x01, /*bConfigurationValue: Configuration value*/ + 0x00, /*iConfiguration: Index of string descriptor describing + the configuration*/ + 0xE0, /*bmAttributes: bus powered and Support Remote Wake-up */ + 0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/ + + /************** Descriptor of Joystick Mouse interface ****************/ + /* 09 */ + 0x09, /*bLength: Interface Descriptor size*/ + USB_INTERFACE_DESCRIPTOR_TYPE,/*bDescriptorType: Interface descriptor type*/ + 0x00, /*bInterfaceNumber: Number of Interface*/ + 0x00, /*bAlternateSetting: Alternate setting*/ + 0x01, /*bNumEndpoints*/ + 0x03, /*bInterfaceClass: HID*/ + 0x00 /*0x01*/, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ + 0x00 /*0x02*/, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ + 0, /*iInterface: Index of string descriptor*/ + /******************** Descriptor of Joystick Mouse HID ********************/ + /* 18 */ + 0x09, /*bLength: HID Descriptor size*/ + HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ + 0x11, /*bcdHID: HID Class Spec release number*/ + 0x01, + 0x00, /*bCountryCode: Hardware target country*/ + 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ + 0x22, /*bDescriptorType*/ + sizeof(HID_JOYSTICK_ReportDesc),/*wItemLength: Total length of Report descriptor*/ + 0x00, + /******************** Descriptor of Mouse endpoint ********************/ + /* 27 */ + 0x07, /*bLength: Endpoint Descriptor size*/ + USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/ + + HID_IN_EP, /*bEndpointAddress: Endpoint Address (IN)*/ + 0x03, /*bmAttributes: Interrupt endpoint*/ + HID_IN_PACKET, /*wMaxPacketSize: 4 Byte max */ + 0x00, + 0x0A, /*bInterval: Polling Interval (10 ms)*/ + /* 34 */ +} ; + + + +static uint8_t ReportSent; + +/** + * @} + */ + +/** @defgroup USBD_HID_Private_Functions + * @{ + */ + +/** + * @brief USBD_HID_Init + * Initialize the HID interface + * @param pdev: device instance + * @param cfgidx: Configuration index + * @retval status + */ +static uint8_t USBD_HID_Init (void *pdev, + uint8_t cfgidx) +{ + + /* Open EP IN */ + DCD_EP_Open(pdev, + HID_IN_EP, + HID_IN_PACKET, + USB_OTG_EP_INT); + + /* Open EP OUT */ + DCD_EP_Open(pdev, + HID_OUT_EP, + HID_OUT_PACKET, + USB_OTG_EP_INT); + + ReportSent = 1; + return USBD_OK; +} + +/** + * @brief USBD_HID_Init + * DeInitialize the HID layer + * @param pdev: device instance + * @param cfgidx: Configuration index + * @retval status + */ +static uint8_t USBD_HID_DeInit (void *pdev, + uint8_t cfgidx) +{ + /* Close HID EPs */ + DCD_EP_Close (pdev , HID_IN_EP); + DCD_EP_Close (pdev , HID_OUT_EP); + + ReportSent = 1; + return USBD_OK; +} + +/** + * @brief USBD_HID_Setup + * Handle the HID specific requests + * @param pdev: instance + * @param req: usb requests + * @retval status + */ +static uint8_t USBD_HID_Setup (void *pdev, + USB_SETUP_REQ *req) +{ + uint16_t len = 0; + const uint8_t *pbuf = NULL; + + switch (req->bmRequest & USB_REQ_TYPE_MASK) + { + case USB_REQ_TYPE_CLASS : + switch (req->bRequest) + { + + + case HID_REQ_SET_PROTOCOL: + USBD_HID_Protocol = (uint8_t)(req->wValue); + break; + + case HID_REQ_GET_PROTOCOL: + USBD_CtlSendData (pdev, + (uint8_t *)&USBD_HID_Protocol, + 1); + break; + + case HID_REQ_SET_IDLE: + USBD_HID_IdleState = (uint8_t)(req->wValue >> 8); + break; + + case HID_REQ_GET_IDLE: + USBD_CtlSendData (pdev, + (uint8_t *)&USBD_HID_IdleState, + 1); + break; + + default: + USBD_CtlError (pdev, req); + return USBD_FAIL; + } + break; + + case USB_REQ_TYPE_STANDARD: + switch (req->bRequest) + { + case USB_REQ_GET_DESCRIPTOR: + if( req->wValue >> 8 == HID_REPORT_DESC) + { + len = MIN(sizeof(HID_JOYSTICK_ReportDesc) , req->wLength); + pbuf = HID_JOYSTICK_ReportDesc; // wiiccReportDescriptor; // + } + else if( req->wValue >> 8 == HID_DESCRIPTOR_TYPE) + { + +#ifdef USB_OTG_HS_INTERNAL_DMA_ENABLED + pbuf = USBD_HID_Desc; +#else + pbuf = USBD_HID_CfgDesc + 0x12; +#endif + len = MIN(USB_HID_DESC_SIZ , req->wLength); + } + + USBD_CtlSendData (pdev, + pbuf, + len); + + break; + + case USB_REQ_GET_INTERFACE : + USBD_CtlSendData (pdev, + (uint8_t *)&USBD_HID_AltSet, + 1); + break; + + case USB_REQ_SET_INTERFACE : + USBD_HID_AltSet = (uint8_t)(req->wValue); + break; + } + } + return USBD_OK; +} + + +/** + * @brief USBD_HID_SendReport + * Send HID Report + * @param pdev: device instance + * @param buff: pointer to report + * @retval status + */ +uint8_t USBD_HID_SendReport (USB_OTG_CORE_HANDLE *pdev, + uint8_t *report, + uint16_t len) +{ + if (pdev->dev.device_status == USB_OTG_CONFIGURED ) + { + if ( ReportSent ) + { + ReportSent = 0; + DCD_EP_Tx (pdev, HID_IN_EP, report, len); + } + } + return USBD_OK; +} + +/** + * @brief USBD_HID_GetCfgDesc + * return configuration descriptor + * @param speed : current device speed + * @param length : pointer data length + * @retval pointer to descriptor buffer + */ +static const uint8_t *USBD_HID_GetCfgDesc (uint8_t speed, uint16_t *length) +{ + *length = sizeof (USBD_HID_CfgDesc); + return USBD_HID_CfgDesc; +} + +/** + * @brief USBD_HID_DataIn + * handle data IN Stage + * @param pdev: device instance + * @param epnum: endpoint index + * @retval status + */ +static uint8_t USBD_HID_DataIn (void *pdev, + uint8_t epnum) +{ + ReportSent = 1; + /* Ensure that the FIFO is empty before a new transfer, this condition could + be caused by a new transfer before the end of the previous transfer */ + DCD_EP_Flush(pdev, HID_IN_EP); + return USBD_OK; +} + +/** + * @} + */ + + +/** + * @} + */ + + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/Horus/usbd_storage_msd.cpp b/radio/src/targets/Horus/usbd_storage_msd.cpp new file mode 100644 index 000000000..59fec18eb --- /dev/null +++ b/radio/src/targets/Horus/usbd_storage_msd.cpp @@ -0,0 +1,811 @@ +/** + ****************************************************************************** + * @file usbd_storage_msd.c + * @author MCD application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief This file provides the disk operations functions. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "../../opentx.h" +#include "../../thirdparty/FatFs/diskio.h" +#include "board_horus.h" + +#if defined(__cplusplus) && !defined(SIMU) +extern "C" { +#endif + +#include "usbd_msc_mem.h" +#include "usb_conf.h" + +#if defined(BOOT) +#define STORAGE_LUN_NBR 2 +#else +/* SD card only when not running bootloader */ +#define STORAGE_LUN_NBR 1 +#endif +#define BLOCKSIZE 512 + +/* USB Mass storage Standard Inquiry Data */ +const unsigned char STORAGE_Inquirydata[] = {//36 + + /* LUN 0 */ + 0x00, + 0x80, + 0x02, + 0x02, + (USBD_STD_INQUIRY_LENGTH - 5), + 0x00, + 0x00, + 0x00, + 'F', 'r', 'S', 'k', 'y', ' ', ' ', ' ', /* Manufacturer : 8 bytes */ + 'T', 'a', 'r', 'a', 'n', 'i', 's', ' ', /* Product : 16 Bytes */ + 'R', 'a', 'd', 'i', 'o', ' ', ' ', ' ', + '1', '.', '0', '0', /* Version : 4 Bytes */ +#if defined(BOOT) + /* LUN 1 */ + 0x00, + 0x80, + 0x02, + 0x02, + (USBD_STD_INQUIRY_LENGTH - 5), + 0x00, + 0x00, + 0x00, + 'F', 'r', 'S', 'k', 'y', ' ', ' ', ' ', /* Manufacturer : 8 bytes */ + 'T', 'a', 'r', 'a', 'n', 'i', 's', ' ', /* Product : 16 Bytes */ + 'R', 'a', 'd', 'i', 'o', ' ', ' ', ' ', + '1', '.', '0' ,'0', /* Version : 4 Bytes */ +#endif +}; + +#if defined(BOOT) +int32_t fat12Write( const uint8_t *buffer, uint16_t sector, uint16_t count ) ; +int32_t fat12Read( uint8_t *buffer, uint16_t sector, uint16_t count ) ; +#endif + +int8_t STORAGE_Init (uint8_t lun); + +int8_t STORAGE_GetCapacity (uint8_t lun, + uint32_t *block_num, + uint32_t *block_size); + +int8_t STORAGE_IsReady (uint8_t lun); + +int8_t STORAGE_IsWriteProtected (uint8_t lun); + +int8_t STORAGE_Read (uint8_t lun, + uint8_t *buf, + uint32_t blk_addr, + uint16_t blk_len); + +int8_t STORAGE_Write (uint8_t lun, + uint8_t *buf, + uint32_t blk_addr, + uint16_t blk_len); + +int8_t STORAGE_GetMaxLun (void); + +USBD_STORAGE_cb_TypeDef USBD_MICRO_SDIO_fops = +{ + STORAGE_Init, + STORAGE_GetCapacity, + STORAGE_IsReady, + STORAGE_IsWriteProtected, + STORAGE_Read, + STORAGE_Write, + STORAGE_GetMaxLun, + (int8_t *)STORAGE_Inquirydata, +}; + +USBD_STORAGE_cb_TypeDef *USBD_STORAGE_fops = &USBD_MICRO_SDIO_fops; + +__IO uint32_t count = 0; + +#if defined(__cplusplus) && !defined(SIMU) +} +#endif + +int8_t STORAGE_Init (uint8_t lun) +{ + NVIC_InitTypeDef NVIC_InitStructure; + NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure); + +/* TODO if no SD ... if( SD_Init() != 0) + { + return (-1); + } +*/ + return (0); +} + +/** + * @brief return medium capacity and block size + * @param lun : logical unit number + * @param block_num : number of physical block + * @param block_size : size of a physical block + * @retval Status + */ +int8_t STORAGE_GetCapacity (uint8_t lun, uint32_t *block_num, uint32_t *block_size) +{ +#if defined(BOOT) + if (lun == 1) { + *block_size = BLOCKSIZE; + *block_num = 3 + EESIZE/BLOCKSIZE + FLASHSIZE/BLOCKSIZE; + } + else +#endif + { + if (!SD_CARD_PRESENT()) + return -1; + + *block_size = BLOCKSIZE; + + static DWORD sector_count = 0; + if (sector_count == 0) { + if (disk_ioctl(0, GET_SECTOR_COUNT, §or_count) != RES_OK) { + sector_count = 0; + return -1; + } + } + + *block_num = sector_count; + } + + return 0; +} + +#if defined(BOOT) +uint8_t lunReady[2] ; +#else +uint8_t lunReady[1] ; +#endif + +void usbPluggedIn() +{ + if (lunReady[0] == 0) { + lunReady[0] = 1; + } +#if defined(BOOT) + if (lunReady[1] == 0) { + lunReady[1] = 1; + } +#endif +} + +/** + * @brief check whether the medium is ready + * @param lun : logical unit number + * @retval Status + */ +int8_t STORAGE_IsReady (uint8_t lun) +{ +#if defined(BOOT) + if (lun == 1) { + if (lunReady[1] == 0) { + return -1 ; + } + return 0 ; + } + else +#endif + { + if (lunReady[0] == 0) { + return -1 ; + } + return SD_CARD_PRESENT() ? 0 : -1; + } +} + +/** + * @brief check whether the medium is write-protected + * @param lun : logical unit number + * @retval Status + */ +int8_t STORAGE_IsWriteProtected (uint8_t lun) +{ + return 0; +} + +/** + * @brief Read data from the medium + * @param lun : logical unit number + * @param buf : Pointer to the buffer to save data + * @param blk_addr : address of 1st block to be read + * @param blk_len : nmber of blocks to be read + * @retval Status + */ + +int8_t SD_ReadSectors(uint8_t *buff, uint32_t sector, uint32_t count); + +int8_t STORAGE_Read (uint8_t lun, + uint8_t *buf, + uint32_t blk_addr, + uint16_t blk_len) +{ +#if defined(BOOT) + if (lun == 1) { + if (fat12Read(buf, blk_addr, blk_len) != 0) + return -1; + } + else +#endif + { + if (SD_ReadSectors(buf, blk_addr, blk_len) != 0) { + return -1; + } + } + + return 0; +} +/** + * @brief Write data to the medium + * @param lun : logical unit number + * @param buf : Pointer to the buffer to write from + * @param blk_addr : address of 1st block to be written + * @param blk_len : nmber of blocks to be read + * @retval Status + */ + +int8_t SD_WriteSectors(const uint8_t *buf, uint32_t sector, uint32_t count); + +int8_t STORAGE_Write (uint8_t lun, + uint8_t *buf, + uint32_t blk_addr, + uint16_t blk_len) +{ +#if defined(BOOT) + if (lun == 1) { + if (fat12Write(buf, blk_addr, blk_len) != 0) + return -1; + } + else +#endif + { + if (SD_WriteSectors(buf, blk_addr, blk_len) != 0) + return -1; + } + + return (0); +} + +/** + * @brief Return number of supported logical unit + * @param None + * @retval number of logical unit + */ + +int8_t STORAGE_GetMaxLun (void) +{ + return STORAGE_LUN_NBR - 1; +} + +#if defined(BOOT) +//------------------------------------------------------------------------------ +/** + * FAT12 boot sector partition. + */ +const char g_FATboot[BLOCKSIZE] = +{ + 0xeb, 0x3c, 0x90, // Jump instruction. + 0x39, 0x58, 0x20, 0x54, 0x45, 0x41, 0x4D, 0x00, // OEM Name + 0x00, 0x02, // Bytes per sector + 0x08, // Sectors per FS cluster. + 0x01, 0x00, // Reserved sector count + + 0x01, // Number of FATs + 0x10, 0x00, // Number of root directory entries + 3+(EESIZE/BLOCKSIZE), (FLASHSIZE/BLOCKSIZE)>>8, // Total sectors + 0xf8, // Media descriptor + 0x01, 0x00, // Sectors per FAT table + 0x20, 0x00, // Sectors per track + 0x40, 0x00, // Number of heads + 0x00, 0x00, 0x00, 0x00, // Number of hidden sectors + + 0x00, 0x00, 0x00, 0x00, // Large number of sectors. + 0x00, // Physical drive number + 0x00, // Reserved + 0x29, // Extended boot signature + 0xCE, 0xFA, 0x5C, 0xD1, // Disk ID (serial number) + 0x56, 0x49, 0x52, 0x54, 0x46, 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, // Volume label + 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, // FAT file system type + 0x00, 0x00, // OS boot code + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa +}; + +#if defined(REV4a) +const char g_FAT[BLOCKSIZE] = +{ + 0xF8, 0xFF, 0xFF, 0x03, 0x40, 0x00, 0x05, 0x60, 0x00, 0x07, 0x80, 0x00, 0x09, 0xA0, 0x00, 0x0B, + 0xC0, 0x00, 0x0D, 0xE0, 0x00, 0x0F, 0x00, 0x01, 0x11, 0xF0, 0xFF, 0x13, 0x40, 0x01, 0x15, 0x60, + 0x01, 0x17, 0x80, 0x01, 0x19, 0xA0, 0x01, 0x1B, 0xC0, 0x01, 0x1D, 0xE0, 0x01, 0x1F, 0x00, 0x02, + 0x21, 0x20, 0x02, 0x23, 0x40, 0x02, 0x25, 0x60, 0x02, 0x27, 0x80, 0x02, 0x29, 0xA0, 0x02, 0x2B, + 0xC0, 0x02, 0x2D, 0xE0, 0x02, 0x2F, 0x00, 0x03, 0x31, 0x20, 0x03, 0x33, 0x40, 0x03, 0x35, 0x60, + 0x03, 0x37, 0x80, 0x03, 0x39, 0xA0, 0x03, 0x3B, 0xC0, 0x03, 0x3D, 0xE0, 0x03, 0x3F, 0x00, 0x04, + 0x41, 0x20, 0x04, 0x43, 0x40, 0x04, 0x45, 0x60, 0x04, 0x47, 0x80, 0x04, 0x49, 0xA0, 0x04, 0x4B, + 0xC0, 0x04, 0x4D, 0xE0, 0x04, 0x4F, 0x00, 0x05, 0x51, 0x20, 0x05, 0x53, 0x40, 0x05, 0x55, 0x60, + 0x05, 0x57, 0x80, 0x05, 0x59, 0xA0, 0x05, 0x5B, 0xC0, 0x05, 0x5D, 0xE0, 0x05, 0x5F, 0x00, 0x06, + 0x61, 0x20, 0x06, 0x63, 0x40, 0x06, 0x65, 0x60, 0x06, 0x67, 0x80, 0x06, 0x69, 0xA0, 0x06, 0x6B, + 0xC0, 0x06, 0x6D, 0xE0, 0x06, 0x6F, 0x00, 0x07, 0x71, 0x20, 0x07, 0x73, 0x40, 0x07, 0x75, 0x60, + 0x07, 0x77, 0x80, 0x07, 0x79, 0xA0, 0x07, 0x7B, 0xC0, 0x07, 0x7D, 0xE0, 0x07, 0x7F, 0x00, 0x08, + 0x81, 0x20, 0x08, 0x83, 0x40, 0x08, 0x85, 0x60, 0x08, 0x87, 0x80, 0x08, 0x89, 0xA0, 0x08, 0x8B, + 0xC0, 0x08, 0x8D, 0xE0, 0x08, 0x8F, 0x00, 0x09, 0x91, 0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +#else +const char g_FAT[BLOCKSIZE] = +{ + 0xF8, 0xFF, 0xFF, 0x03, 0x40, 0x00, 0x05, 0x60, 0x00, 0x07, 0x80, 0x00, 0x09, 0xF0, 0xFF, 0x0B, + 0xC0, 0x00, 0x0D, 0xE0, 0x00, 0x0F, 0x00, 0x01, 0x11, 0x20, 0x01, 0x13, 0x40, 0x01, 0x15, 0x60, + 0x01, 0x17, 0x80, 0x01, 0x19, 0xA0, 0x01, 0x1B, 0xC0, 0x01, 0x1D, 0xE0, 0x01, 0x1F, 0x00, 0x02, + 0x21, 0x20, 0x02, 0x23, 0x40, 0x02, 0x25, 0x60, 0x02, 0x27, 0x80, 0x02, 0x29, 0xA0, 0x02, 0x2B, + 0xC0, 0x02, 0x2D, 0xE0, 0x02, 0x2F, 0x00, 0x03, 0x31, 0x20, 0x03, 0x33, 0x40, 0x03, 0x35, 0x60, + 0x03, 0x37, 0x80, 0x03, 0x39, 0xA0, 0x03, 0x3B, 0xC0, 0x03, 0x3D, 0xE0, 0x03, 0x3F, 0x00, 0x04, + 0x41, 0x20, 0x04, 0x43, 0x40, 0x04, 0x45, 0x60, 0x04, 0x47, 0x80, 0x04, 0x49, 0xA0, 0x04, 0x4B, + 0xC0, 0x04, 0x4D, 0xE0, 0x04, 0x4F, 0x00, 0x05, 0x51, 0x20, 0x05, 0x53, 0x40, 0x05, 0x55, 0x60, + 0x05, 0x57, 0x80, 0x05, 0x59, 0xA0, 0x05, 0x5B, 0xC0, 0x05, 0x5D, 0xE0, 0x05, 0x5F, 0x00, 0x06, + 0x61, 0x20, 0x06, 0x63, 0x40, 0x06, 0x65, 0x60, 0x06, 0x67, 0x80, 0x06, 0x69, 0xA0, 0x06, 0x6B, + 0xC0, 0x06, 0x6D, 0xE0, 0x06, 0x6F, 0x00, 0x07, 0x71, 0x20, 0x07, 0x73, 0x40, 0x07, 0x75, 0x60, + 0x07, 0x77, 0x80, 0x07, 0x79, 0xA0, 0x07, 0x7B, 0xC0, 0x07, 0x7D, 0xE0, 0x07, 0x7F, 0x00, 0x08, + 0x81, 0x20, 0x08, 0x83, 0x40, 0x08, 0x85, 0x60, 0x08, 0x87, 0x80, 0x08, 0x89, 0xF0, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +#endif + +// File Attributes +// 0 0x01 Read Only. +// 1 0x02 Hidden. +// 2 0x04 System. +// 3 0x08 Volume Label. +// 4 0x10 Subdirectory. +// 5 0x20 Archive. +// 6 0x40 Device. +// 7 0x80 Reserved. + +typedef struct +{ + uint8_t name[8]; + uint8_t ext[3]; + uint8_t attribute; + uint8_t reserved; + uint8_t create_time_ms; + uint16_t create_time; + uint16_t create_date; + uint16_t access_date; + uint16_t ea_index; + uint16_t modify_time; + uint16_t modify_date; + uint16_t start_cluster; + uint32_t file_size; +} FATDirEntry_t; + +// First 16 FAT root directory entries (1 sector) +const FATDirEntry_t g_DIRroot[16] = +{ + { + { 'T', 'A', 'R', 'A', 'N', 'I', 'S', ' '}, + { ' ', ' ', ' '}, + 0x08, // Volume + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { 'E', 'E', 'P', 'R', 'O', 'M', ' ', ' '}, + { 'B', 'I', 'N'}, + 0x20, // Archive + 0x00, + 0x3E, + 0xA301, + 0x3D55, + 0x3D55, + 0x0000, + 0xA302, + 0x3D55, + 0x0002, + EESIZE + }, + { + { 'F', 'I', 'R', 'M', 'W', 'A', 'R', 'E'}, + { 'B', 'I', 'N'}, + 0x20, // Archive + 0x00, + 0x3E, + 0xA301, + 0x3D55, + 0x3D55, + 0x0000, + 0xA302, + 0x3D55, + 0x0002 + (EESIZE/BLOCKSIZE)/8, + FLASHSIZE + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, + { + { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, + { ' ', ' ', ' '}, + 0x00, + 0x00, + 0x00, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x00000000 + }, +}; + +// count is number of 512 byte sectors +int32_t fat12Read( uint8_t *buffer, uint16_t sector, uint16_t count ) +{ + while ( count ) + { + if (sector == 0) { + memcpy(buffer, g_FATboot, BLOCKSIZE ) ; + } + else if (sector == 1/*Reserved sector count*/) { + // FAT table. + memcpy(buffer, g_FAT, BLOCKSIZE); + } + else if (sector == 2) { + memcpy(buffer, g_DIRroot, BLOCKSIZE ) ; + } + else if (sector < 3 + (EESIZE/BLOCKSIZE)) { + eepromReadBlock(buffer, (sector-3)*BLOCKSIZE, BLOCKSIZE); + } + else if (sector < 3 + (EESIZE/BLOCKSIZE) + (FLASHSIZE/BLOCKSIZE)) { + uint32_t address; + address = sector - 3 - (EESIZE/BLOCKSIZE); + address *= BLOCKSIZE; + address += FIRMWARE_ADDRESS; + memcpy(buffer, (uint8_t *)address, BLOCKSIZE); + } + buffer += BLOCKSIZE ; + sector++ ; + count-- ; + } + return 0 ; +} + +int32_t fat12Write(const uint8_t *buffer, uint16_t sector, uint16_t count) +{ + enum FatWriteOperation { + FATWRITE_NONE, + FATWRITE_EEPROM, + FATWRITE_FIRMWARE + }; + + static unsigned int operation = FATWRITE_NONE; + + TRACE("FAT12 Write(sector=%d, count=%d)", sector, count); + + if (sector < 3) { + // reserved, read-only + } + else if (sector < 3 + (EESIZE/BLOCKSIZE)) { + // eeprom + while (count) { + if (operation == FATWRITE_NONE && isEepromStart(buffer)) { + TRACE("EEPROM start found in sector %d", sector); + operation = FATWRITE_EEPROM; + } + if (operation == FATWRITE_EEPROM) { + eepromWriteBlock((uint8_t *)buffer, (sector-3)*BLOCKSIZE, BLOCKSIZE); + } + buffer += BLOCKSIZE; + sector++; + count--; + if (sector-3 >= (EESIZE/BLOCKSIZE)) { + TRACE("EEPROM end written at sector %d", sector-1); + operation = FATWRITE_NONE; + } + } + } + else if (sector < 3 + (EESIZE/BLOCKSIZE) + (FLASHSIZE/BLOCKSIZE)) { + // firmware + uint32_t address; + address = sector - 3 - (EESIZE/BLOCKSIZE); + address *= BLOCKSIZE; + address += FIRMWARE_ADDRESS; + while (count) { + for (uint32_t i=0; i= FIRMWARE_ADDRESS+BOOTLOADER_SIZE/*protect bootloader*/ && address <= FIRMWARE_ADDRESS+FLASHSIZE-FLASH_PAGESIZE) { + if (address == FIRMWARE_ADDRESS+BOOTLOADER_SIZE && isFirmwareStart(buffer)) { + TRACE("FIRMWARE start found in sector %d", sector); + operation = FATWRITE_FIRMWARE; + } + if (operation == FATWRITE_FIRMWARE) { + writeFlash((uint32_t *)address, (uint32_t *)buffer); + } + } + address += FLASH_PAGESIZE; + buffer += FLASH_PAGESIZE; + } + sector++; + count--; + if (sector-3-(EESIZE/BLOCKSIZE) >= (FLASHSIZE/BLOCKSIZE)) { + TRACE("FIRMWARE end written at sector %d", sector-1); + operation = FATWRITE_NONE; + } + } + } + return 0 ; +} +#endif diff --git a/radio/src/targets/Horus/usbd_usr.cpp b/radio/src/targets/Horus/usbd_usr.cpp new file mode 100644 index 000000000..a1d241f4c --- /dev/null +++ b/radio/src/targets/Horus/usbd_usr.cpp @@ -0,0 +1,128 @@ +/** + ****************************************************************************** + * @file usbd_usr.c + * @author MCD Application Team + * @version V1.1.0 + * @date 19-March-2012 + * @brief This file includes the user application layer + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2012 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "../../opentx.h" + +extern "C" { +#include "usbd_usr.h" + +/* Points to the DEVICE_PROP structure of current device */ +/* The purpose of this register is to speed up the execution */ + + +const USBD_Usr_cb_TypeDef USR_cb = +{ + USBD_USR_Init, + USBD_USR_DeviceReset, + USBD_USR_DeviceConfigured, + USBD_USR_DeviceSuspended, + USBD_USR_DeviceResumed, + + USBD_USR_DeviceConnected, + USBD_USR_DeviceDisconnected, +}; +} + +/** +* @brief Whatever the user application needs to do when USB is initialised +* @param None +* @retval None +*/ +void USBD_USR_Init(void) +{ + +} + +/** +* @brief Displays the message on LCD on device reset event +* @param speed : device speed +* @retval None +*/ +void USBD_USR_DeviceReset (uint8_t speed) +{ + +} + + +/** +* @brief Displays the message on LCD on device config event +* @param None +* @retval Staus +*/ +void USBD_USR_DeviceConfigured (void) +{ + +} +/** +* @brief Displays the message on LCD on device suspend event +* @param None +* @retval None +*/ +void USBD_USR_DeviceSuspended(void) +{ + +} + + +/** +* @brief Displays the message on LCD on device resume event +* @param None +* @retval None +*/ +void USBD_USR_DeviceResumed(void) +{ + +} + +/** +* @brief USBD_USR_DeviceConnected +* Displays the message on LCD on device connection Event +* @param None +* @retval Staus +*/ +void USBD_USR_DeviceConnected (void) +{ + +} + + +/** +* @brief USBD_USR_DeviceDisonnected +* Displays the message on LCD on device disconnection Event +* @param None +* @retval Staus +*/ +void USBD_USR_DeviceDisconnected (void) +{ +#if !defined(BOOT) && defined(USB_MASS_STORAGE) + NVIC_SystemReset(); +#endif +} + + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ From 3d74f2b437ee202cd9c5d41ec4b0c583c5ec7f54 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 4 Oct 2015 18:48:46 +0200 Subject: [PATCH 06/74] Horus drivers fixes --- radio/src/targets/Horus/board_horus.h | 8 ++++---- radio/src/targets/Horus/keys_driver.cpp | 5 ++--- radio/src/targets/Horus/stm32f4_flash.ld | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/radio/src/targets/Horus/board_horus.h b/radio/src/targets/Horus/board_horus.h index 150b4a22f..7fe8a0ce7 100644 --- a/radio/src/targets/Horus/board_horus.h +++ b/radio/src/targets/Horus/board_horus.h @@ -203,10 +203,10 @@ uint32_t readKeys(void); uint32_t readTrims(void); #define TRIMS_PRESSED() (readTrims()) #define KEYS_PRESSED() (readKeys()) -#define DBLKEYS_PRESSED_RGT_LFT(i) ((in & ((2< Date: Wed, 7 Oct 2015 20:23:42 +0200 Subject: [PATCH 07/74] Horus fonts / bitmaps tools updated --- radio/util/font2png.py | 28 ++++++++++++++++++++++------ radio/util/img2lbm.py | 19 ++++++++++++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/radio/util/font2png.py b/radio/util/font2png.py index 8d23f6806..86a6ce8bb 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -3,12 +3,16 @@ import sys from PyQt4 import Qt, QtGui +import glob try: app = Qt.QApplication(sys.argv) except: pass +for f in glob.glob("fonts/*.ttf"): + id = QtGui.QFontDatabase.addApplicationFont(f) + chars = u""" !"#$%&'()*+,-./0123456789:;<=>?°ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz~ ≥→←↑↓ △Ⓘ""" def getCharWidth(fontsize, metrics, index): @@ -18,8 +22,16 @@ def getCharWidth(fontsize, metrics, index): return 1 else: rect = metrics.boundingRect(chars[index]) - return rect.width() - + return rect.width() + 1 + +def getFontTopBottom(fontsize, metrics): + top, bottom = 0, 0 + for i, c in enumerate(chars): + rect = metrics.boundingRect(chars[i]) + top = min(top, rect.top()) + bottom = max(bottom, rect.bottom()) + return top, bottom + def getFontWidth(fontsize, metrics): width = 0 for i, c in enumerate(chars): @@ -28,11 +40,15 @@ def getFontWidth(fontsize, metrics): def createFontBitmap(filename, fontname, fontsize, foreground, background, coordsfile=True): coords = [ ] + # font = QtGui.QFont(QtGui.QFontDatabase.applicationFontFamilies(id)[0]) font = QtGui.QFont(fontname) - font.setPointSize(fontsize) + font.setPixelSize(fontsize) + font.setHintingPreference(QtGui.QFont.PreferNoHinting) metrics = QtGui.QFontMetrics(font) width = getFontWidth(fontsize, metrics) - image = QtGui.QImage(width, fontsize+4, QtGui.QImage.Format_RGB32) + top, bottom = getFontTopBottom(fontsize, metrics) + print top, bottom + image = QtGui.QImage(width, fontsize+1, QtGui.QImage.Format_RGB32) image.fill(background) painter = QtGui.QPainter() painter.begin(image) @@ -48,7 +64,7 @@ def createFontBitmap(filename, fontname, fontsize, foreground, background, coord painter.drawPoint(width, fontsize); else: rect = metrics.boundingRect(c) - painter.drawText(width-rect.left(), fontsize+1, c) + painter.drawText(width-rect.left(), fontsize-2, c) # fontsize-bottom+1 -17 / 7 width += getCharWidth(fontsize, metrics, i) coords.append(width) painter.end() @@ -62,4 +78,4 @@ def createFontBitmap(filename, fontname, fontsize, foreground, background, coord return coords if len(sys.argv) == 4: - createFontBitmap(sys.argv[3], sys.argv[1], int(sys.argv[2]), QtGui.QColor(0x00, 0x00, 0x00), QtGui.QColor(0xFF, 0xFF, 0xFF)) + createFontBitmap(sys.argv[3], sys.argv[1], float(sys.argv[2]), QtGui.QColor(0x00, 0x00, 0x00), QtGui.QColor(0xFF, 0xFF, 0xFF)) diff --git a/radio/util/img2lbm.py b/radio/util/img2lbm.py index 83e151d98..f6b92ca98 100755 --- a/radio/util/img2lbm.py +++ b/radio/util/img2lbm.py @@ -36,7 +36,7 @@ if what == "1bit": value += 1 << z f.write("0x%02x," % value) f.write("\n") -elif what == "16bits": +elif what == "4/4/4/4": colors = [] f.write("%d,%d,\n" % (width, height)) for y in range(height): @@ -44,6 +44,14 @@ elif what == "16bits": pixel = image.pixel(x, y) f.write("0x%1x%1x%1x%1x," % (Qt.qAlpha(pixel)/16, Qt.qRed(pixel)/16, Qt.qGreen(pixel)/16, Qt.qBlue(pixel)/16)) f.write("\n") +elif what == "5/6/5": + colors = [] + f.write("%d,%d,\n" % (width, height)) + for y in range(height): + for x in range(width): + pixel = image.pixel(x, y) + f.write("0x%04x," % (((Qt.qRed(pixel) >> 3) << 11) + ((Qt.qGreen(pixel) >> 2) << 5) + ((Qt.qBlue(pixel) >> 3) << 0))) + f.write("\n") elif what == "4bits": colors = [] writeSize(f, width, height) @@ -62,6 +70,15 @@ elif what == "4bits": value -= 1<<(4+i) f.write("0x%02x," % value) f.write("\n") +elif what == "8bits": + colors = [] + writeSize(f, width, height) + for y in range(height): + for x in range(width): + value = Qt.qGray(image.pixel(x, y)) + value = 0x0f - (value >> 4) + f.write("0x%02x," % value) + f.write("\n") elif what == "03x05": for y in range(0, height, 5): for x in range(width): From aeed0e34a9ce899023dfaf3bba6ac333d17e4b21 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 7 Oct 2015 20:24:15 +0200 Subject: [PATCH 08/74] Horus bitmaps --- radio/src/bitmaps/Horus/mask_ball.png | Bin 0 -> 254 bytes radio/src/bitmaps/Horus/mask_curves.png | Bin 0 -> 814 bytes radio/src/bitmaps/Horus/mask_flight_modes.png | Bin 0 -> 1081 bytes radio/src/bitmaps/Horus/mask_gvars.png | Bin 0 -> 807 bytes radio/src/bitmaps/Horus/mask_heli.png | Bin 0 -> 671 bytes radio/src/bitmaps/Horus/mask_inputs.png | Bin 0 -> 796 bytes .../src/bitmaps/Horus/mask_logical_switches.png | Bin 0 -> 1222 bytes radio/src/bitmaps/Horus/mask_lua_scripts.png | Bin 0 -> 980 bytes radio/src/bitmaps/Horus/mask_menu_model.png | Bin 0 -> 873 bytes radio/src/bitmaps/Horus/mask_mixer.png | Bin 0 -> 1090 bytes radio/src/bitmaps/Horus/mask_model_setup.png | Bin 0 -> 604 bytes radio/src/bitmaps/Horus/mask_outputs.png | Bin 0 -> 1063 bytes .../src/bitmaps/Horus/mask_special_functions.png | Bin 0 -> 811 bytes radio/src/bitmaps/Horus/mask_telemetry.png | Bin 0 -> 1095 bytes radio/src/bitmaps/Horus/splash.png | Bin 0 -> 7654 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_ball.png create mode 100644 radio/src/bitmaps/Horus/mask_curves.png create mode 100644 radio/src/bitmaps/Horus/mask_flight_modes.png create mode 100644 radio/src/bitmaps/Horus/mask_gvars.png create mode 100644 radio/src/bitmaps/Horus/mask_heli.png create mode 100644 radio/src/bitmaps/Horus/mask_inputs.png create mode 100644 radio/src/bitmaps/Horus/mask_logical_switches.png create mode 100644 radio/src/bitmaps/Horus/mask_lua_scripts.png create mode 100644 radio/src/bitmaps/Horus/mask_menu_model.png create mode 100644 radio/src/bitmaps/Horus/mask_mixer.png create mode 100644 radio/src/bitmaps/Horus/mask_model_setup.png create mode 100644 radio/src/bitmaps/Horus/mask_outputs.png create mode 100644 radio/src/bitmaps/Horus/mask_special_functions.png create mode 100644 radio/src/bitmaps/Horus/mask_telemetry.png create mode 100644 radio/src/bitmaps/Horus/splash.png diff --git a/radio/src/bitmaps/Horus/mask_ball.png b/radio/src/bitmaps/Horus/mask_ball.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a4a2c8376aeb66c783ad9d23f7ddc803425462 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@KN#5=*4F5rJ!QSPQfg+p*9+AaB zRo6k7(Me-=1yGQ^#M9T6{XQ4F5S!&YwVF#np>9tX#}JM4b0-<{u_$u5{+HOvy_C28 z!!8rKrbE+qXB;wnY+^lo&Aun)dnO#%ucTC9{M;p*H;t{F-PqYBAlvnvB!A)M>i!@v zh96#FVdQ&MBb@0JUCS#sB~S literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_curves.png b/radio/src/bitmaps/Horus/mask_curves.png new file mode 100644 index 0000000000000000000000000000000000000000..6e1e19e8ef21638a1e892f1318da8d100439ad20 GIT binary patch literal 814 zcmV+}1JV46P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00NdtL_t(Y$K{tlNODma#vjEr z;=;WN2|*g&9NHQJK^$C~f}$o@qopwvYH<`C-5d>xa1?QHaBzqS(#TC{yl`C{GRQy0 zmFJr}Ul;#>U!M-&3kNReyzl$tJ?A|S=UPY-3ot7dWIqP}`1pXLC{Ppykw|2szwM{M zB=Pq41_0=EI`adMv-|sdEG;d;>-ECvbj}BC7zR#GPS9*N;dZ;BY1(|6t5hnX*=zy; zg2CX=HN}X0f@1`zt5+qCl(3?TCG<4d_Gf*Wt@N* zhJkv$j_2oR)M_r2F~y3=m?*mpRn8Q zCe<3HX5*wzSg+Tk!{HzR%CgMY*VnP#{I%7qtE=pEI;OQ;E;s#F-`(8_=d-i3iPz|R zbM5Wz@%s9jFE1~Q#bR7rTQfD+Sa2ed5Q%PXZl=1!(P(sJ14@#_a=AP-&ICR2#W^sty&9Q6|9sexHh>5CBzGomsuw?epR3!+WAZquq=yH5oTv+zsF1sY;0_d z=H}+ud!bMu0JOQeN#C`GId-{R2#3R{R4M>~uC6XvEEe!Q|FgyAa=B10mvM4(!m!E7 zNkk$MRI610Ksueq<>e)N-rwH=0NHF-7MDnEx7!H-0ic$a7J7Mkp=>rw0BCD#i%GP% zw?_cz^z@X7h}PEDSR4TI`Fun~w7$MBi%X<-yWMOt&(F^|K0ZcIPY=vyGlIb&^LacT z0AO;vTrSZ@LZOf>{y9C0T3O`=I-O1tvC(LR!C+tnwF_=UTswzI>Rc`-l4fCHfryB9 zc6JB=C6h@e)AI5%nayVQZZerzoL;YIxW!^&ct=MERjXC0)ZN|P={;y}isA2n0m_T5~#`O!C@qP_6WJi8+pg)9C~eAsh~~$pGNu z;sV8D@k`=*8xfu3N~J+372)oO;1j*iOr>PGo(ZEc0kW@A3T-_QKfXcTX6ZxWf~@wlkqJ|C?w zi9?|f>~=e=>{Kd+OeXVX9vkKtfU34-E-o$t0Mu$V`uqEl&*yP{ea&zH@cDeAiOgm* zHa0exe`;z90AMf}MELad^q*gS4h{|o039A4GHhjKg)|xsRVtOAyZZ1v502wR9nJ6e z!{Km1p-}wX)raSK3=R&Wv$GQbkjZ3_N~I+E`WsW#a|nWfcs!1gkr8;kUI0KMk&rEv z|MXrRi9|3zKQFeunse`|tD%wVPf}8*baX00000NkvXXu0mjfe2L~- literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_gvars.png b/radio/src/bitmaps/Horus/mask_gvars.png new file mode 100644 index 0000000000000000000000000000000000000000..0b5015dac420e9d7e705ac87cfa48a3958f7dd4f GIT binary patch literal 807 zcmV+?1K9kDP)TDN`4N+m2VE&>2XM@JD3hka@6>FEi*UJn3JtyYmp zB*czaR#pgrdwYAdZJU0@{rx?+x3>v^Yinz+?t%k>02M`Hr_-U|;W!S1!60>AcQw~s zudlCARTZI7$af!&kB>u^W%T>~;k)|#fq#59vC(Lt+wBUPs;U?p8}sCLIvq5dO=z0- zzGqx}1B}ID?>cjOdYS-)v$3(^$!oP*oSmIzDwSfp-F9_FQK%@2_c{FwTr3v3xw$ET zM@B|?adF}1yu7?{d3l-fc$|$!L#Y2Um?Ul6rlx5EJe$puB>Vk7v)L>S!(hE$_tXvq zCdq??0|6{a606lJbGaNRCMIZE)?e!e2DjU7Mx#*y9*ssBi9~pOeEe?Rz+jR*JUsLm ztxza@So0_NOYeAUYRaQsUS57}9)Gsua=FZKI84JZgpO5J{cx%cbk&w+QP*{vrpfE; zYX*Zs0iMt2{Rh|UbsC1j)zww@dOeb4E|(L)kw}EMx3~U*ZQJI|%nX;7mV|kIb8|yk zmIb(JnzSs7j^hjletv#tI-Ta++#Fl2mgoEK?ykq-0C;qC^dZ>Yxg<$KEEdDn)snC4Cy*N5Clq~=N002ovPDHLkV1hJsbF}~f literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_heli.png b/radio/src/bitmaps/Horus/mask_heli.png new file mode 100644 index 0000000000000000000000000000000000000000..ecd93d2ff31a96f1a374f88861ba48f6b212557e GIT binary patch literal 671 zcmV;Q0$}}#P)Oc_2KMz+el0s4iUn3&+RsjpGT(AoG5_tu|7YKQPbP@6Zwn36kA+5s3 zN`-)he_(F4;q1|?(JME_9oS|kJ3qec?#yh3hzKRv7fP~UML*o{_lV;daU3IxBE)eF z05A*#tyT+$VZbm9Xqr|mAU(0&ZbytU0#Il$#%Qo^rA0KM$M) z7N^%VP3fb_WJ2|No$B>E4Tr-Uz-Tne{nP1G@`*iXj7jrf-|wmCc~BGuuIoxy?s~oA zbUNX5I^ldi-(1Pd<&yia*Q?}nT^EX?!1Fxp_xsz-B@#lX(?PS@{8)x+Hk;^lIuhe9 zxZQ4lJUxn--@$pxjH0Lzc)Q(VHk(Ovoby6p!H~d36^CJn!C>&$ic70bIoL1^0Kj6g zc(|a~>)~)XNTc8HKU^bX3Wfx3wOWP1)oK-uMnf94TCEURFy2!9jy+T?Cc`j9nx-Fv zrfG^W4F3$)G!2X~9FIqg$K#KI$Kx@M$0HbHa(7asNUYaun$PDnpU-KzT;AmB)oPXd zH=E7tMWVE_SZX6sYU@*4v-dCB+e*ar%76EE`VDb~kk7--2ebeH002ovPDHLk FV1mzjG+_V$ literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_inputs.png b/radio/src/bitmaps/Horus/mask_inputs.png new file mode 100644 index 0000000000000000000000000000000000000000..720099c470641dcfce53ac95a3cf6162fdad27ca GIT binary patch literal 796 zcmV+%1LOROP)q0;x$v zK~zY`)tA3Z>rfQOPea&qLPb>Q;OHPID)=whT^w|Aa&gfvBDx7KB7&0; z6hzeGAUKLp5via<28o2Cv9tU8ZT8S4Z98i;D{sMX4KF6`afE zeuU=pdAho~0zs>S`}+DwrBaxtSrxp#zV53i7K>pR2Ji3hHJRf$mEhjqUPJ_4*I8X% z<>TX{0-Q`Hec+9a4T7oyT(+K`9u5x=J;$e~Cttp<>lBN{#)Hc?Ha5oX?JWT3=jRL! z4Iv^-O-=C|Re%$T1PcobL?RInuBs|?b8}dhh3mTP?Ch|+yW3cBGMNP6?(U9>i3wlt z+uGXL+}z~z^ON7ID!44eFe;{@zrUZ;)6?dH0Sbiz>2#V{Eaq##%*+fA4-d@)mt~qJ zsZ`3hbG5Xzu(Y&9KA*1(T-WIojYcINkBg>h-Z#f_B$vyHZQJ#oGPU-2Z*PxyJnkFn z;o)H}FE5)j)YsS7jEs!OC%DUoSXn~b92M!=qMt>%nnRsXD1@RYsH#dh97fYLFTkif*gHBp3LJBZ zh_@~Rg|~)b(An7;2wW11P1@N acc(w_7+y_}g%8mH0000w1XD>w zK~zY`<(ExJR9zUy|99?nNSq8pCmjooplB==U5F!!E-X+RlQ1Z979xdTAVC{ljEf=! zM={!n;9@qxAVP~GWxy6i5)osI5(Pz^D@=Z%jpOKi-2c1s@(y>#8S8}>?*j+UJp=$JqM% zI;~bK0jN!{*Qe*?a8;|2k+x3`y}P$=%4wY4>x&1M2%e}6yib~~G!n>jErz?qqulY?Kq zdPM-NsHk8h5;=B(m6a8yr>DoH7Emy6})o<_!PT zc6WD~o105XlDM_C730$g$hU9b@axwv?Ci~fH`T4})zP>)JuCAi9vlCriT`9x9fB%lr z(NQ!uHsb8rvv9dws&9FD831tM!iD(C<@I{0*XwCC8o9r}&t!_`bUJCX*=RDER28#W zEVSG0{PE+5I=8sEn4&0hdwVQjI=1IAC2}9RaYZsVP4A_U+pQz=eec)ftUOdFRd@4h;>_Zz><;@0^o-aAGF}o(ozCQnqQK1xm@ZO*xA`hI)7?viU9cV;X^GrJ3E_MSy@RJ z2?m2KC@4_#98Z$#vKj^0N~cGTk#KjilVT( zx|#rZ^XAQjr-zf6+wG=auV-Ff9)rPP;(F}$dRbXnNdU~r$>GbFFF8Lyf8vwm*47qZ zym-M=r%q9))6wN}#rTDIH;G6j0=wOg=g*(x$bI_sXlbUL;F?{+k9Fc{F()C8N& khO1YvYStG1cXy|M0C0!SbQRzAX#fBK07*qoM6N<$f+2%R-v9sr literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_lua_scripts.png b/radio/src/bitmaps/Horus/mask_lua_scripts.png new file mode 100644 index 0000000000000000000000000000000000000000..a20c1ccbdd8f005fe649c33c1f95f4ea84d1454a GIT binary patch literal 980 zcmV;_11tQAP)Qk0G4G@US5u!ogDyx+wBI!Fu1(Dl&v925*{8Nkjv=vgtD?S5D~)R zFaUt(c|1Qqt4`kB+{FC+{QGx0oyO?sC@dBWhzM@C8(Ujj+F$_K-Q7hf6oM#<_^$bU zJ`j=28HNGR^V;B_+Wq~#0{Z4XJw1x?d3tp4`ua+-Sd5O3kI8Pglh^Adm&+v^b8&I; z<@{tasV%OLw!XfO%F0UQ<>e`&C@d^QcX#)@u%4ctke{Eg8nMsk)3?<(H#g|$==gH$ zKDy0j!|LiP0AOlrO3`mNn{j@At_>Ch0d~7RC(LfQqqVgaB_$;=7z`jH^!4>2o6TZz zaSGMRMU zZ0{9_*X#WwB~4XT6&a02S=-Uk5t&RT^85YD(xj?vZ zPEJlJ5{XbW8kLnkH8nL|hZ90000rfm$iV(L51(gJmbP|^?h6GePn8BgYrJ#e*kI*`ZkTHW=(4k95#|oDC z6&y0TRV91e$HDi{ujbX}LGX^M5at}_UNPwi90zVB02RRW-@s_b++{9SZ9oez8XzCW8d z2m-OdG))c$1O6!ngMm=5s%lEPAf8Mn;(ATfucWK-7yTqxyqAUY&04liK8f@EXxFt$*0H1$KnS%KR?gpa{!cOS%lg$0JK^y z#BmJMG*PKkvc$glet&L(Z}ar@ln)ONsW_J; zi2&H`c8PYoO#rkkD|O0Rt(KYa*HJ7MQ~Oz#MF4EK+gwhPy{2hcG!l+53?Yb<+`C95 z9AOw@WtO33Sy)|NP0c?(KBC|60|55+_HcA`l(LyuB3Bee5mn|_lu9L(N~P4i@B4yy ze}5l0H#eCSCmf2RU^yX9Hd-VS4ne&CSh}pLTb5Q7jfe_2JIW&ZqOQudk@r>i_`Twvo%_ zKK5MS)(CuC>+`M6-oI>b|F4GrTc0qCezg4ngD)E+P`YqO00000NkvXXu0mjfHSK=i literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_mixer.png b/radio/src/bitmaps/Horus/mask_mixer.png new file mode 100644 index 0000000000000000000000000000000000000000..009b360f1031291cdb1625a55cc4b8da8b5e4ddc GIT binary patch literal 1090 zcmV-I1ikx-P)6XrTkls|N#MdoyEeJWO)c9L2+>k#A5@Dj z*jCbw6fKG%vxONtUJ6pEaYmH?xA~gy_~~@4bKoq_1GAa&d44nRW#(}R0LUK*8TmsY z!Zb~jXf#UF=``tdI=>BQx7#F{Op-_>LTH-)?*je}LWp>1G#W&sQE(iG@5cN4J62a$ zp;oKKx)eo;*ZaaB2Tjw+WHMhy)9Ey(r>A@B4Ga@PAeYOrwzdXA5W0$IG8yD@xt^zt zL?V#MWPNM(g)^B9mY0_?K0e-)pw(*e^z;M(C>Dz_o6S%vl~5=Y2!%puHk$x|LZJYy zR@;-W(P$8fL~wn5-3x!j+uIvfR#q@HG$f+=d>#N$DwUwq=@1A6&}cM}&1S(c3|uak zi0AWp5tqy5l52%X%~L9sg3V?VQI=()*X!Z;`@4p{Ua!MqvB2qccHv&H7nhfplIwsB zvRKOHGH9BH&*$rz>mPB4!!htq_$geeRA4X|B+>ulcDsEb{8PAEt-@$D!t3>d=Xvbx z>|lR?AFWpFQ`}~={S=pkYqc6oCKEg!4|tvz=Xq>w43m?SSYKa9p-_;-tyb%&xFpOl z4BT$FhpZPEAc=b8{2V z&(8pWhldBq<#OEK-uA80_v=Ja6lu5H?Hk(DG(Lf*&fKI1FsZ_%0=_!VXhr4pa;V@X1m4*|E z1Qr$+pin6Kb`POY2msJ*HW3Pipin5FR4QRMn^7zl0RW4Oi;`VTAP~UG$;sfYk4mLN zJRa}4<6JJ+w;zkg;}{(s9lZ7F_rtllIo#jhf4SMaySu~8%*@x}+1XjdVlm0j-yk6f z0=Blcx^AE^JU>5=SS%*CI=?d>A0M&3y$y%MA)Xuh>;60a0|WN%>?UFLXaE2J07*qo IM6N<$f)uLwXaE2J literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_model_setup.png b/radio/src/bitmaps/Horus/mask_model_setup.png new file mode 100644 index 0000000000000000000000000000000000000000..afe526c8fa383787697c0140914d71e47230181a GIT binary patch literal 604 zcmV-i0;BzjP)lNei7{lQZi^YPg zNuf|ctyV*&Qb9Bt{c(}rG#CsBz#l#O{l05Q%IzY89anpDqv)Mxzlr zoeqk{B4)E00N`%S7y|$t4hM*$$TjNQ?S@Dsa(|Ie;R=Vt+(G)R=Ig2cO3UREs;XkQ z+xaReNfIWL3G(^8XY=W;&jf%R$Dv=P>v~{%bzP^Aa__nF9I>J($Ye5sMyx1`=W`EK zJC5^nl5><>|4XX>8n^(!vMd~rM_&a+QADX!^0l)37B15?i81CIEXEj_rs)ZnXVkWB zBgmJNh+y0H^HHm+iurtw{eJJuIGIdBRn`Ag2ao!EKBL)e23kP6-7aFW*mG1Hh7owl zGz{Z;s%2S*APBxA6a)dXEc1bDwOZkHI{7k=$K%N5a-MMgdsN?zv)L>FU^<<0H*ssG z)9J?^x7%&BS}g!TB9XX9!$a<>r)e5`z25ujwApN^(P&UAmEyjimL!Sl^*XKB>&Jby qtIOqr?RLvGYDtnFfMu`xY5NW}J>Hr(I)DrS0000|A+S}TOK}lV%P57Y7HtfJB8a3#xCo30 z`UADJ5~_u!65%2QBf>BQ7bQhSrCopD;=Rs$7t7D=!MAt=v$%6+<~MWBoHKJp06>3$ zIQm1xfJGuvQLI!d$!s>$?(Qz-a=G6EL?RJpYqQz@S-{^!MBiqePKS$&3lyT$(^IBD zIy%balAml>Fc_rH&Q7|#yd;G}@u`knE~oD9ZXzOTYHIp#GX7wj&4%xi=%cD*u~<=A zW-=L?oSY=D*UMyOWo2}Jeoh*Vrugkb3H0*v0*yu^$l7ADe5!kMbA##WX*iuu9334o zc}c(k;PH3_z)Gd^Q@ubSz;q@iCYZdadz{PVC?1bfHk)NKlgUI2w-iuNPjgS8(+T z0uK!heObvyqY?3V9En5%eSLi(A}lN{6n#`*0&i_?F%XX9U@#ca*48Efm&@f?TwDat z^Vr_r#`yU7&w%gm@1amA7{KP{CR;l=IDkr}5(LoE(Sc|*3ZCcld>tPjLnf0!CX-=i zW~MN(&*x)KtyXK^`qR@B>~=dij$?XKsT2-}1DQvoP@7+#FM0Utj-nyF#H5 zv|6nod#zTBa5yZ`X>V^wYilc3R#plF_xJZRM<*vI1o4h|MoEYWBbI-O3Cy-KBm&*v*1IF(94EEY2dJv}{`pPxrC7|c7ve-qF1 zaJgKN%jJRq1_lO@PN$2zPHwkb0RF+{<>fFK449gl!rtB<9v&X@Y_G4c(c9ZAu&b@D z#o5_eaSzDO&JL=ps|&V7j^ohM(t_dPVQg${Ael@u`__mUA0=B-xdpWFN(!t z6bJ;!@As46@29J)tAcO4N~I#bUQc?xp44hJnayT;czB?ini@)_Qh$wFrA(QrREh-> z2m~k+iO~D|`)}>)>gp(&OcD{1kpMJl-zS7#-8r9d=Q&(3Pjg5_w zLZP5=ILv|x27~nY_$UCCN~Pp-xrE*6?d|PHqmSKg&#Uf8B!Z=-C0MOi7>!2k@9*bV zbvBzV8C0oM;_mM5+k*ssL|6)_#bUwj?QKaDjD(1Y<2b6UtSqXqBoYZVH#gJB$OxHC hrjkcv(f{t>=@(0QrFXj4)8+sG002ovPDHLkV1hv&=12el literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_special_functions.png b/radio/src/bitmaps/Horus/mask_special_functions.png new file mode 100644 index 0000000000000000000000000000000000000000..a12a2a50de7e5858ffeb1121af59242b19684e08 GIT binary patch literal 811 zcmV+`1JwM9P)^1>BK(4A{E}Ed z4ibq3fk1%s^Yd?S+}_?!Bod*oudlFy{E=ibDVnAUP_<9jb-B8_l0u?9HVlJYE(gH! z@-l60ZU5};^Yb&Co13K5X?AvY*x1-8?(xdXiU8tpIHVdfF)<;4ba!_b)T`urdwT<* zrKP1R$+NJq0KoP2HK(Viwaei-EMc$aQ`T9b8|DJqoV-q?d|cgJAWJ4G)*oqFN^9u9uEL_cX!0&@#+DuuC6jL zFhDF8D;n;GhKA4K{#(#KpHBek?d`3&No;R#tM=H@(IGD{F9q$~Znv_@<#PEA$O-)S-Xn1&7{W}~ENhXseK~t%e z=(;X|`2GGoUD>QQ3`1--TiyjA2&u^NfMMbQ-LjfUF?NCL_6AP8yAdj7B5rbUG3Wg~d5R2~8%GK_C!-h&Wv; zm11gY3Qni2yM`*RudL8VycQPb&jVMBwxJXlZGIPN###V!_$j87CN*%Z1_LVWzvXvVwFv zT^_JfsRWP5%iH6orY5YfujBsyp6TxI@1w4+4n&0h{(d|^KbN=1i9`Z!w;MY!0a9FFobetmr{IPZ45q0{MD0BW@wE|;rvhpVcp z3OzkNn4h1A&1Qqo=i|toPA9s$x-9*bQk4M~i^X~SJ2El?uh+|Tve_&S4-X51 zmJFOur{VYe;cz&xy1ELLN(CYUkH>?_WI`|)tSo`Nyu9G%<_72I=LiG>czk?hf!OVK zw70iIDwX2s=%{?)tE($4FE2x*(SV3hUtf>T&Q6F#A`lT&DiszN7vcB&k;!Bbi^YoL zg0}>zR0?LZ8A72DM1;}NQJkEdFp@|nlkj*vFdB^@B8bIeY;0`2Ns&bXd%a$EDv1bd zYir2ma&J1Z*=$UAaBvWzQ0Tt{UtV5v$bMpCBG2gV?k;a`Zf=g*s8lKrd%X)BjYiSh z+RC{mKSuxe_wV>kr-Oh9eZ9T->)SUbi^t<^--!rgV`D#Eb`J3P_&5htsZ?^N? zxx`{I>GgU}brK4N*jTMr7p!2~+S=GS91e420|Nual|C027aTg|^ZAS<0HFWlM^3h# zA3m_tp3CJRl}eedLZLuB9xwh>Z*FdKmdj$XaGt{LFJJKcr%(9f^JhFfJTTer?k*?b zLRH64fpfVWW@cuX6Ol;7A^$?{`uZBRwY4B32m}IHt=7_+Yj1B4H8nLLB1j|>SgqEA z*TZhNvn6hBZpQKPae0&p0Ks4oeSLi_d%0YW$;nBW&1RTPCMcCk&a)VeMvH!MiYn*> zfdCyG9FWiFqevt|u~>{68X8EUP>@!urRnKu>geeB|9=v{?C(^G_75ye%v{nsS=Rsn N002ovPDHLkV1k4%@{0ff literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/splash.png b/radio/src/bitmaps/Horus/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..1ed7a9ae9bdb42ce8a779d72c10cb0a675c23a35 GIT binary patch literal 7654 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 literal 0 HcmV?d00001 From 34be811622c914fa36ce53fb35b8686d0f14fa22 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 7 Oct 2015 20:26:38 +0200 Subject: [PATCH 09/74] Horus drivers updated --- radio/src/targets/Horus/board_horus.h | 1 + radio/src/targets/Horus/lcd_driver.cpp | 135 ++++++++----------------- 2 files changed, 44 insertions(+), 92 deletions(-) diff --git a/radio/src/targets/Horus/board_horus.h b/radio/src/targets/Horus/board_horus.h index 7fe8a0ce7..45f1438d2 100644 --- a/radio/src/targets/Horus/board_horus.h +++ b/radio/src/targets/Horus/board_horus.h @@ -250,6 +250,7 @@ void ledRed(void); void ledBlue(void); // LCD driver +void lcdDrawFullRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); // Backlight driver #define setBacklight(xx) diff --git a/radio/src/targets/Horus/lcd_driver.cpp b/radio/src/targets/Horus/lcd_driver.cpp index 7e6b38a1a..536eeaf5d 100644 --- a/radio/src/targets/Horus/lcd_driver.cpp +++ b/radio/src/targets/Horus/lcd_driver.cpp @@ -1,14 +1,3 @@ -/** - ****************************************************************************** - * @file Project/lcd/lcd.c - * @author FrSky Application Team - * @Hardware version V0.2 - * @date 11-July-2012 - * @brief This file provides LCD Init and botom drivers. - * * - ****************************************************************************** -*/ - #include "../../opentx.h" #define HBP 42 @@ -20,45 +9,22 @@ #define HFP 3 #define VFP 2 -#define LCD_PIXEL_WIDTH ((uint16_t)480) -#define LCD_PIXEL_HEIGHT ((uint16_t)272) -/** - * @brief LCD Direction - */ +#define LCD_PIXEL_WIDTH ((uint16_t)480) +#define LCD_PIXEL_HEIGHT ((uint16_t)272) + #define LCD_DIR_HORIZONTAL 0x0000 #define LCD_DIR_VERTICAL 0x0001 -/** - * @brief LCD Layer - */ #define LCD_BACKGROUND_LAYER 0x0000 #define LCD_FOREGROUND_LAYER 0x0001 -/** - * @brief The Touch panel connector for LCD panel addresses - */ -#define LCD_ADDR 0x80 -#define ASSEMBLE_RGB(R ,G, B) ((((R)& 0xF8) << 8) | (((G) & 0xFC) << 3) | (((B) & 0xF8) >> 3)) - #define LCD_BKLIGHT_PWM_FREQ 300 -#define ABS(X) ((X) > 0 ? (X) : -(X)) -#define POLY_X(Z) ((int32_t)((Points + Z)->X)) -#define POLY_Y(Z) ((int32_t)((Points + Z)->Y)) +#define LCD_DEFAULT_FRAME_BUFFER SDRAM_BANK_ADDR +#define FRAME_BUFFER_OFFSET ((uint32_t)0x100000) -/* Global variables to set the written text color */ -static uint16_t CurrentTextColor = 0x0000; -static uint16_t CurrentBackColor = 0xFFFF; - -/** @defgroup STM324x9I_EVAL_LCD_Private_Variables - * @{ - */ -// static sFONT *LCD_Currentfonts; -#define LCD_DEFAULT_FRAME_BUFFER SDRAM_BANK_ADDR -#define FRAME_BUFFER_OFFSET ((uint32_t)0x100000) -/* Default LCD configuration with LCD Layer 1 */ -static uint32_t CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER; -static uint32_t CurrentLayer = LCD_BACKGROUND_LAYER; +uint32_t CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER; +uint32_t CurrentLayer = LCD_BACKGROUND_LAYER; #define NRST_LOW() do { LCD_GPIO_NRST->BSRRH = LCD_GPIO_PIN_NRST; } while(0) #define NRST_HIGH() do { LCD_GPIO_NRST->BSRRL = LCD_GPIO_PIN_NRST; } while(0) @@ -423,28 +389,6 @@ void LCD_SetLayer(uint32_t Layerx) } } -#define LCD_COLOR_WHITE 0xFFFF -#define LCD_COLOR_RED 0xF800 - -void LCD_DrawCircle(uint16_t Xpos, uint16_t Ypos, uint16_t Radius) -{ - int x = -Radius, y = 0, err = 2-2*Radius, e2; - do { - *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos-x) + LCD_PIXEL_WIDTH*(Ypos+y)))) = LCD_COLOR_RED; - *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos+x) + LCD_PIXEL_WIDTH*(Ypos+y)))) = LCD_COLOR_RED; - *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos+x) + LCD_PIXEL_WIDTH*(Ypos-y)))) = LCD_COLOR_RED; - *(__IO uint16_t*) (CurrentFrameBuffer + (2*((Xpos-x) + LCD_PIXEL_WIDTH*(Ypos-y)))) = LCD_COLOR_RED; - - e2 = err; - if (e2 <= y) { - err += ++y*2+1; - if (-x == y && e2 <= x) e2 = 0; - } - if (e2 > x) err += ++x*2+1; - } - while (x <= 0); -} - void LCD_Clear(uint16_t Color) { uint16_t *index ; @@ -475,33 +419,6 @@ void LCD_SetTransparency(uint8_t transparency) LTDC_ReloadConfig(LTDC_IMReload); } -void LCD_SetColorKeying(uint32_t RGBValue) -{ - uint32_t tmp = 0; - - LTDC_ColorKeying_InitTypeDef LTDC_colorkeying_InitStruct; - - /* configure the color Keying */ - LTDC_colorkeying_InitStruct.LTDC_ColorKeyBlue = 0xFF & RGBValue; - tmp = (0xFF00 & RGBValue); - LTDC_colorkeying_InitStruct.LTDC_ColorKeyGreen = (tmp >> 8); - tmp = (0xFF0000 & RGBValue); - LTDC_colorkeying_InitStruct.LTDC_ColorKeyRed = (tmp >> 16); - - if (CurrentLayer == LCD_BACKGROUND_LAYER) - { - /* Enable the color Keying for Layer1 */ - LTDC_ColorKeyingConfig(LTDC_Layer1, <DC_colorkeying_InitStruct, ENABLE); - LTDC_ReloadConfig(LTDC_IMReload); - } - else - { - /* Enable the color Keying for Layer2 */ - LTDC_ColorKeyingConfig(LTDC_Layer2, <DC_colorkeying_InitStruct, ENABLE); - LTDC_ReloadConfig(LTDC_IMReload); - } -} - void lcdInit(void) { /* Initialize the LCD */ @@ -515,15 +432,49 @@ void lcdInit(void) LCD_SetLayer(LCD_BACKGROUND_LAYER); LCD_Clear(0); LCD_SetTransparency(0); - + /* Set Foreground layer */ LCD_SetLayer(LCD_FOREGROUND_LAYER); LCD_Clear(0); LCD_SetTransparency(255); } +void lcdDrawFullRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) +{ + uint32_t addr = CurrentFrameBuffer + 2*(LCD_PIXEL_WIDTH*y + x); + uint16_t red = (0xF800 & color) >> 11; + uint16_t blue = 0x001F & color; + uint16_t green = (0x07E0 & color) >> 5; + + /* configure DMA2D */ + DMA2D_InitTypeDef DMA2D_InitStruct; + DMA2D_DeInit(); + DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M; + DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; + DMA2D_InitStruct.DMA2D_OutputGreen = green; + DMA2D_InitStruct.DMA2D_OutputBlue = blue; + DMA2D_InitStruct.DMA2D_OutputRed = red; + DMA2D_InitStruct.DMA2D_OutputAlpha = 0x0F; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = addr; + DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_PIXEL_WIDTH - w); + DMA2D_InitStruct.DMA2D_NumberOfLine = h; + DMA2D_InitStruct.DMA2D_PixelPerLine = w; + DMA2D_Init(&DMA2D_InitStruct); + + /* Start Transfer */ + DMA2D_StartTransfer(); + + /* Wait for CTC Flag activation */ + while(DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); +} void lcdRefresh() { - memcpy((void *)(SDRAM_BANK_ADDR+FRAME_BUFFER_OFFSET), (void *)SDRAM_BANK_ADDR, 480*272*2); + LCD_SetTransparency(255); + if (CurrentLayer == LCD_BACKGROUND_LAYER) + LCD_SetLayer(LCD_FOREGROUND_LAYER); + else + LCD_SetLayer(LCD_BACKGROUND_LAYER); + LCD_SetTransparency(0); + // memcpy((void *)(SDRAM_BANK_ADDR+FRAME_BUFFER_OFFSET), (void *)SDRAM_BANK_ADDR, 480*272*2); } From 2902ae1887ed645830f363c71679f6adf01bd9a7 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 7 Oct 2015 21:02:17 +0200 Subject: [PATCH 10/74] Horus simulator should now be OK --- radio/src/Makefile | 373 ++++++- radio/src/audio_arm.cpp | 7 +- radio/src/buzzer.h | 2 +- radio/src/cli.cpp | 107 +- radio/src/cli.h | 2 + radio/src/eeprom_raw.cpp | 12 +- radio/src/eeprom_raw.h | 2 +- radio/src/eeprom_rlc.cpp | 2 +- radio/src/fifo.h | 5 + radio/src/fonts.h | 9 + radio/src/functions.cpp | 2 +- radio/src/gui/Horus/fonts.cpp | 88 ++ radio/src/gui/Horus/gui.h | 70 ++ radio/src/gui/Horus/helpers.cpp | 341 ++++++ radio/src/gui/Horus/lcd.cpp | 964 +++++++++++++++++ radio/src/gui/Horus/lcd.h | 325 ++++++ radio/src/gui/Horus/menu_general.cpp | 96 ++ radio/src/gui/Horus/menu_general_calib.cpp | 216 ++++ .../src/gui/Horus/menu_general_sdmanager.cpp | 358 +++++++ radio/src/gui/Horus/menu_general_setup.cpp | 521 ++++++++++ radio/src/gui/Horus/menu_general_trainer.cpp | 121 +++ radio/src/gui/Horus/menu_general_version.cpp | 53 + radio/src/gui/Horus/menu_model.cpp | 203 ++++ radio/src/gui/Horus/menu_model_curves.cpp | 325 ++++++ .../gui/Horus/menu_model_custom_functions.cpp | 372 +++++++ .../src/gui/Horus/menu_model_flightmodes.cpp | 149 +++ radio/src/gui/Horus/menu_model_gvars.cpp | 123 +++ radio/src/gui/Horus/menu_model_heli.cpp | 113 ++ .../src/gui/Horus/menu_model_inputs_mixes.cpp | 971 ++++++++++++++++++ radio/src/gui/Horus/menu_model_limits.cpp | 290 ++++++ .../gui/Horus/menu_model_logical_switches.cpp | 275 +++++ radio/src/gui/Horus/menu_model_select.cpp | 293 ++++++ radio/src/gui/Horus/menu_model_setup.cpp | 741 +++++++++++++ radio/src/gui/Horus/menu_model_telemetry.cpp | 728 +++++++++++++ radio/src/gui/Horus/menus.cpp | 93 ++ radio/src/gui/Horus/menus.h | 377 +++++++ radio/src/gui/Horus/navigation.cpp | 468 +++++++++ radio/src/gui/Horus/popups.cpp | 236 +++++ radio/src/gui/Horus/splash.cpp | 57 + radio/src/gui/Horus/view_about.cpp | 159 +++ radio/src/gui/Horus/view_channels.cpp | 122 +++ radio/src/gui/Horus/view_main.cpp | 374 +++++++ radio/src/gui/Horus/view_statistics.cpp | 213 ++++ radio/src/gui/Horus/view_telemetry.cpp | 202 ++++ radio/src/gui/Horus/view_text.cpp | 161 +++ radio/src/gui/Horus/widgets.cpp | 315 ++++++ radio/src/gui/gui.h | 6 +- radio/src/haptic.cpp | 3 +- radio/src/keys.cpp | 21 +- radio/src/keys.h | 101 +- radio/src/logs.cpp | 13 +- radio/src/lua_api.cpp | 60 +- radio/src/main_arm.cpp | 148 ++- radio/src/main_avr.cpp | 4 +- radio/src/mixer.cpp | 9 +- radio/src/myeeprom.h | 218 +++- radio/src/opentx.cpp | 73 +- radio/src/opentx.h | 97 +- radio/src/pulses/dsm2_arm.cpp | 8 +- radio/src/pulses/pulses.h | 2 +- radio/src/pulses/pxx_arm.cpp | 2 +- radio/src/serial.cpp | 2 +- radio/src/simu.cpp | 219 ++-- radio/src/switches.cpp | 232 ++++- radio/src/targets/simu/simpgmspace.cpp | 58 +- radio/src/targets/simu/simpgmspace.h | 25 +- radio/src/tasks_arm.cpp | 3 + radio/src/telemetry/frsky.cpp | 6 +- radio/src/telemetry/frsky.h | 2 +- radio/src/thirdparty/CoOS/OsConfig.h | 4 +- .../message_definitions_v1.0/matrixpilot.xml | 2 +- .../Class/audio/inc/usbd_audio_core.h | 2 +- .../Class/cdc/inc/usbd_cdc_if_template.h | 4 +- .../Class/cdc/src/usbd_cdc_core.c | 3 +- .../Class/dfu/inc/usbd_dfu_mal.h | 4 +- .../Class/dfu/inc/usbd_mem_if_template.h | 3 +- .../Class/dfu/src/usbd_dfu_core.c | 3 +- .../Class/hid/src/usbd_hid_core.c | 3 +- .../Class/msc/inc/usbd_msc_data.h | 2 +- .../Core/inc/usbd_conf_template.h | 2 +- .../Core/inc/usbd_core.h | 2 +- .../Core/inc/usbd_req.h | 2 +- .../Core/src/usbd_req.c | 3 +- .../inc/usb_conf_template.h | 2 +- .../STM32_USB_OTG_Driver/inc/usb_defines.h | 2 +- .../STM32_USB_OTG_Driver/inc/usb_regs.h | 2 +- .../STM32_USB_OTG_Driver/src/usb_hcd.c | 3 +- radio/src/translations.cpp | 2 +- radio/src/translations.h | 2 +- radio/src/translations/en.h.txt | 43 +- 90 files changed, 12075 insertions(+), 368 deletions(-) create mode 100644 radio/src/gui/Horus/fonts.cpp create mode 100644 radio/src/gui/Horus/gui.h create mode 100644 radio/src/gui/Horus/helpers.cpp create mode 100644 radio/src/gui/Horus/lcd.cpp create mode 100644 radio/src/gui/Horus/lcd.h create mode 100644 radio/src/gui/Horus/menu_general.cpp create mode 100644 radio/src/gui/Horus/menu_general_calib.cpp create mode 100644 radio/src/gui/Horus/menu_general_sdmanager.cpp create mode 100644 radio/src/gui/Horus/menu_general_setup.cpp create mode 100644 radio/src/gui/Horus/menu_general_trainer.cpp create mode 100644 radio/src/gui/Horus/menu_general_version.cpp create mode 100644 radio/src/gui/Horus/menu_model.cpp create mode 100644 radio/src/gui/Horus/menu_model_curves.cpp create mode 100644 radio/src/gui/Horus/menu_model_custom_functions.cpp create mode 100644 radio/src/gui/Horus/menu_model_flightmodes.cpp create mode 100644 radio/src/gui/Horus/menu_model_gvars.cpp create mode 100644 radio/src/gui/Horus/menu_model_heli.cpp create mode 100644 radio/src/gui/Horus/menu_model_inputs_mixes.cpp create mode 100644 radio/src/gui/Horus/menu_model_limits.cpp create mode 100644 radio/src/gui/Horus/menu_model_logical_switches.cpp create mode 100644 radio/src/gui/Horus/menu_model_select.cpp create mode 100644 radio/src/gui/Horus/menu_model_setup.cpp create mode 100644 radio/src/gui/Horus/menu_model_telemetry.cpp create mode 100644 radio/src/gui/Horus/menus.cpp create mode 100644 radio/src/gui/Horus/menus.h create mode 100644 radio/src/gui/Horus/navigation.cpp create mode 100644 radio/src/gui/Horus/popups.cpp create mode 100644 radio/src/gui/Horus/splash.cpp create mode 100644 radio/src/gui/Horus/view_about.cpp create mode 100644 radio/src/gui/Horus/view_channels.cpp create mode 100644 radio/src/gui/Horus/view_main.cpp create mode 100644 radio/src/gui/Horus/view_statistics.cpp create mode 100644 radio/src/gui/Horus/view_telemetry.cpp create mode 100644 radio/src/gui/Horus/view_text.cpp create mode 100644 radio/src/gui/Horus/widgets.cpp diff --git a/radio/src/Makefile b/radio/src/Makefile index 35469abcb..aff9aa9ec 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -409,7 +409,7 @@ LUADEP = DEBUG_TRACE_BUFFER = NO # MCU name -ifneq ($(PCB), $(filter $(PCB), STD 9X 9XR STD128 9X128 9XR128 9X2561 9XR2561 GRUVIN9X MEGA2560 SKY9X 9XRPRO AR9X TARANIS)) +ifneq ($(PCB), $(filter $(PCB), STD 9X 9XR STD128 9X128 9XR128 9X2561 9XR2561 GRUVIN9X MEGA2560 SKY9X 9XRPRO AR9X TARANIS FLAMENCO HORUS)) $(error Sorry, the PCB $(PCB) is not supported yet in OpenTX) endif @@ -424,7 +424,7 @@ EEPROM_VARIANT = 0 GUIMODELSRC = gui/$(GUIDIRECTORY)/menu_model.cpp gui/$(GUIDIRECTORY)/menu_model_select.cpp gui/$(GUIDIRECTORY)/menu_model_setup.cpp gui/$(GUIDIRECTORY)/menu_model_inputs_mixes.cpp gui/$(GUIDIRECTORY)/menu_model_limits.cpp gui/$(GUIDIRECTORY)/menu_model_logical_switches.cpp gui/$(GUIDIRECTORY)/menu_model_custom_functions.cpp gui/$(GUIDIRECTORY)/menu_model_telemetry.cpp GUIGENERALSRC = gui/$(GUIDIRECTORY)/menu_general.cpp gui/$(GUIDIRECTORY)/menu_general_setup.cpp gui/$(GUIDIRECTORY)/menu_general_trainer.cpp gui/$(GUIDIRECTORY)/menu_general_version.cpp gui/$(GUIDIRECTORY)/menu_general_calib.cpp -BITMAPS = bitmaps/sticks.lbm +BITMAPS = bitmaps/sticks.lbm FONTS = $(patsubst %.png,%.lbm,$(wildcard fonts/std/*.png)) @@ -439,7 +439,7 @@ ifeq ($(PCB), $(filter $(PCB), STD 9X 9XR)) SDCARD = NO THR_TRACE = NO INCDIRS += targets/stock - CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM64 -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) + CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM64 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) BOARDSRC = main_avr.cpp targets/stock/board_stock.cpp EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/common_avr/telemetry_driver.cpp ifeq ($(GUI), YES) @@ -520,7 +520,7 @@ ifeq ($(PCB), $(filter $(PCB), STD128 9X128 9XR128)) SDCARD = NO THR_TRACE = NO EEPROM_VARIANT += ${M128_VARIANT} - CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM128 -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) + CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM128 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) INCDIRS += targets/stock BOARDSRC = main_avr.cpp targets/stock/board_stock.cpp EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/stock/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp @@ -595,7 +595,7 @@ ifeq ($(PCB), $(filter $(PCB), 9X2561)) SDCARD = NO THR_TRACE = YES EEPROM_VARIANT += ${M2561_VARIANT} - CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM2561 -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) + CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM2561 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) INCDIRS += targets/stock BOARDSRC = main_avr.cpp targets/stock/board_stock.cpp EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/stock/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp @@ -665,7 +665,7 @@ ifeq ($(PCB), GRUVIN9X) TRGT = avr- MCU = atmega2560 OPT = s - CPPDEFS += -DF_CPU=$(F_CPU)UL -DPCBGRUVIN9X -DCPUM2560 -DEEPROM_RLC -DEEPROM_VARIANT=0 -DAUDIO -DHAPTIC + CPPDEFS += -DF_CPU=$(F_CPU)UL -DPCBGRUVIN9X -DCPUM2560 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=0 -DAUDIO -DHAPTIC SDCARD = NO BUZZER = NO THR_TRACE = YES @@ -710,7 +710,7 @@ ifeq ($(PCB), MEGA2560) TRGT = avr- MCU = atmega2560 OPT = s - CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBMEGA2560 -DCPUM2560 -DEEPROM_RLC -DEEPROM_VARIANT=0 -DAUDIO + CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBMEGA2560 -DCPUM2560 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=0 -DAUDIO SDCARD = NO BUZZER = YES THR_TRACE = YES @@ -769,7 +769,7 @@ ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X)) GUIDIRECTORY = 9X ARCH = ARM LCDSIZE = 128 - CPPDEFS = -Dat91sam3s4 + CPPDEFS = -Dat91sam3s4 -DPPM_PIN_HW_SERIAL ifeq ($(PCB), 9XRPRO) CPPDEFS = -Dat91sam3s8 FLAVOUR = 9xrpro @@ -803,7 +803,7 @@ ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X)) BUZZER = NO THR_TRACE = YES EXT = FRSKY_SPORT - CPPDEFS += -DPCBSKY9X -DCPUARM -DEEPROM_VARIANT=0 -DAUDIO -DHAPTIC -DDSM2 -DDSM2_PPM -DPXX + CPPDEFS += -DPCBSKY9X -DCPUARM -DEEPROM -DEEPROM_VARIANT=0 -DAUDIO -DHAPTIC -DDSM2 -DDSM2_PPM -DPXX ifeq ($(PCB), SKY9X) CPPDEFS += -DROTARY_ENCODERS=1 CPPSRC += targets/sky9x/rotenc_driver.cpp @@ -926,7 +926,7 @@ ifeq ($(PCB), TARANIS) PPM_LIMITS_SYMETRICAL = YES PPM_CENTER_ADJUSTABLE = YES DSM2 = PPM - CPPDEFS += -DPCBTARANIS -DCPUARM -DCPUSTM32 -DVIRTUALINPUTS -DLUAINPUTS -DXCURVES -DEEPROM_RLC -DAUDIO -DPXX + CPPDEFS += -DPCBTARANIS -DCPUARM -DCPUSTM32 -DVIRTUALINPUTS -DLUAINPUTS -DXCURVES -DEEPROM -DEEPROM_RLC -DAUDIO -DPXX INCDIRS += targets/taranis $(THIRDPARTY) $(COOSDIR) $(COOSDIR)/kernel $(COOSDIR)/portable CPPDEFS += -DHSE_VALUE=12000000 ifeq ($(PCBREV), REV9E) @@ -1029,7 +1029,7 @@ ifeq ($(PCB), TARANIS) $(LUADIR)/lobject.c $(LUADIR)/lopcodes.c $(LUADIR)/lparser.c $(LUADIR)/lstate.c $(LUADIR)/lstring.c $(LUADIR)/ltable.c $(LUADIR)/lrotable.c $(LUADIR)/ltm.c $(LUADIR)/lundump.c $(LUADIR)/lvm.c $(LUADIR)/lzio.c \ $(LUADIR)/lbaselib.c $(LUADIR)/linit.c $(LUADIR)/lmathlib.c $(LUADIR)/lbitlib.c $(LUADIR)/loadlib.c $(LUADIR)/lauxlib.c $(LUADIR)/ltablib.c $(LUADIR)/lcorolib.c $(LUADIR)/liolib.c SRC += $(LUASRC) - LUADEP = lua_exports.inc + LUADEP = lua_exports_taranis.inc ifeq ($(USE_BIN_ALLOCATOR), YES) CPPDEFS += -DUSE_BIN_ALLOCATOR CPPSRC += bin_allocator.cpp @@ -1061,6 +1061,257 @@ ifeq ($(PCB), TARANIS) endif endif +ifeq ($(PCB), FLAMENCO) + # FLAMENCO radio + ARCH = ARM + LCDSIZE = 320 + GUIDIRECTORY = Flamenco + FLAVOUR = FLAMENCO + LDSCRIPT = targets/Flamenco/stm32f4_flash.ld + STM32LIBPATH = $(THIRDPARTY)/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries + STM32USBPATH = $(THIRDPARTY)/STM32_USB-Host-Device_Lib_V2.1.0/Libraries + TRGT = arm-none-eabi- + MCU = cortex-m4 + OPT = 2 + CPPDEFS += -DHSE_VALUE=12000000 -DCOLORLCD -DSTM32F40_41xxx + BITMAPS += $(patsubst %.png,%.lbm,$(wildcard bitmaps/Flamenco/*.png)) + FONTS = fonts/font_stdsize.lbm fonts/font_tinsize.lbm fonts/font_smlsize.lbm fonts/font_midsize.lbm fonts/font_dblsize.lbm fonts/font_xxlsize.lbm + SDCARD = YES + THR_TRACE = YES + EXT = FRSKY_SPORT + AUTOSWITCH = YES + AUTOSOURCE = YES + DBLKEYS = YES + PPM_LIMITS_SYMETRICAL = YES + PPM_CENTER_ADJUSTABLE = YES + DSM2 = PPM + TIMERS = 2 + CPPDEFS += -DTIMERS=$(TIMERS) + CPPDEFS += -DPCBFLAMENCO -DCPUARM -DCPUSTM32 -DEEPROM -DVIRTUALINPUTS -DLUAINPUTS -DXCURVES -DEEPROM_VARIANT=0 -DAUDIO -DPXX + INCDIRS += targets/Flamenco $(THIRDPARTY) $(COOSDIR) $(COOSDIR)/kernel $(COOSDIR)/portable + INCDIRS += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/inc $(STM32LIBPATH)/CMSIS/Device/ST/STM32F4xx/Include $(STM32LIBPATH)/CMSIS/include + STM32USBPATH = $(THIRDPARTY)/STM32_USB-Host-Device_Lib_V2.1.0/Libraries + INCDIRS += $(STM32USBPATH)/STM32_USB_OTG_Driver/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Core/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/hid/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/cdc/inc + BOARDSRC = main_arm.cpp targets/Flamenco/board_flamenco.cpp + EXTRABOARDSRC += targets/Flamenco/lcd_driver.cpp targets/Flamenco/delays.c targets/Flamenco/i2c_driver.cpp targets/taranis/configure_pins.cpp serial.cpp # targets/taranis/flash_driver.cpp + SRC += $(COOSDIR)/kernel/core.c $(COOSDIR)/kernel/hook.c $(COOSDIR)/kernel/task.c $(COOSDIR)/kernel/event.c $(COOSDIR)/kernel/time.c $(COOSDIR)/kernel/timer.c $(COOSDIR)/kernel/flag.c $(COOSDIR)/kernel/mutex.c $(COOSDIR)/kernel/serviceReq.c $(COOSDIR)/portable/GCC/port.c $(COOSDIR)/portable/arch.c + SRC += targets/Flamenco/pwr_driver.c targets/Flamenco/usb_driver.c + EEPROMSRC = eeprom_common.cpp eeprom_raw.cpp + PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp + CPPSRC += tasks_arm.cpp audio_arm.cpp sbus.cpp telemetry/telemetry.cpp + CPPSRC += targets/Flamenco/tw8823_driver.cpp targets/Flamenco/pulses_driver.cpp targets/Flamenco/keys_driver.cpp targets/Flamenco/adc_driver.cpp targets/Flamenco/eeprom_driver.cpp targets/Flamenco/trainer_driver.cpp targets/Flamenco/audio_driver.cpp targets/Flamenco/serial2_driver.cpp targets/Flamenco/telemetry_driver.cpp + CPPSRC += gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp + SRC += targets/Flamenco/system_stm32f4xx.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/misc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dbgmcu.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_rcc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_spi.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_i2c.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dma.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_rtc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_pwr.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_usart.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_sdio.c + SRC += $(STM32LIBPATH)/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc_ride7/startup_stm32f40_41xxx.s + SRC += targets/Flamenco/sdio_sd.c targets/Flamenco/usb_bsp.c targets/Flamenco/usbd_desc.c + SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_core.c + SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_dcd.c + SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_dcd_int.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_core.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_ioreq.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_req.c + EXTRABOARDSRC += targets/Flamenco/usbd_usr.cpp + SRC += syscalls.c + ifeq ($(USB), JOYSTICK) + CPPDEFS += -DUSB_JOYSTICK + SRC += targets/Flamenco/usbd_hid_joystick.c + else + CPPDEFS += -DUSB_MASS_STORAGE + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_data.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_scsi.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_bot.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_core.c + EXTRABOARDSRC += targets/Flamenco/usbd_storage_msd.cpp + endif + ifeq ($(GVARS), YES) + GUIMODELSRC += gui/$(GUIDIRECTORY)/menu_model_gvars.cpp + endif + ifneq ($(LUA), NO) + ifeq ($(LUA), YES) + CPPDEFS += -DLUA_MODEL_SCRIPTS + endif + CPPDEFS += -DLUA + INCDIRS += $(LUADIR) + CPPSRC += lua_api.cpp + LUASRC = $(LUADIR)/lapi.c $(LUADIR)/lcode.c $(LUADIR)/lctype.c $(LUADIR)/ldebug.c $(LUADIR)/ldo.c $(LUADIR)/ldump.c $(LUADIR)/lfunc.c $(LUADIR)/lgc.c $(LUADIR)/llex.c $(LUADIR)/lmem.c \ + $(LUADIR)/lobject.c $(LUADIR)/lopcodes.c $(LUADIR)/lparser.c $(LUADIR)/lstate.c $(LUADIR)/lstring.c $(LUADIR)/ltable.c $(LUADIR)/lrotable.c $(LUADIR)/ltm.c $(LUADIR)/lundump.c $(LUADIR)/lvm.c $(LUADIR)/lzio.c \ + $(LUADIR)/lbaselib.c $(LUADIR)/linit.c $(LUADIR)/lmathlib.c $(LUADIR)/lbitlib.c $(LUADIR)/loadlib.c $(LUADIR)/lauxlib.c $(LUADIR)/ltablib.c $(LUADIR)/lcorolib.c $(LUADIR)/liolib.c + SRC += $(LUASRC) + LUADEP = lua_exports_flamenco.inc + endif + + EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Flamenco/diskio.cpp + CPPDEFS += -DSDCARD -DVOICE -DRTCLOCK + CPPSRC += sdcard.cpp rtc.cpp logs.cpp targets/taranis/rtc_driver.cpp + GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp + INCDIRS += $(FATFSDIR) $(FATFSDIR)/option + + ifeq ($(DSM2), PPM) + CPPSRC += pulses/dsm2_arm.cpp + endif + + CPPDEFS += -DHAPTIC + CPPSRC += haptic.cpp + CPPSRC += targets/Flamenco/haptic_driver.cpp + EXTRABOARDSRC += + + ifeq ($(TW8823_TRACES), YES) + CPPDEFS += -DTW8823_TRACES + endif + + ifeq ($(TW8823_BITMAPS), YES) + CPPDEFS += -DTW8823_BITMAPS + endif + + ifeq ($(TW8823_SERIAL), YES) + CPPDEFS += -DTW8823_SERIAL + endif +endif + +ifeq ($(PCB), HORUS) + # HORUS radio + ARCH = ARM + LCDSIZE = 320 + GUIDIRECTORY = Horus + FLAVOUR = HORUS + LDSCRIPT = targets/Horus/stm32f4_flash.ld + STM32LIBPATH = $(THIRDPARTY)/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries + STM32USBPATH = $(THIRDPARTY)/STM32_USB-Host-Device_Lib_V2.1.0/Libraries + TRGT = arm-none-eabi- + MCU = cortex-m4 + OPT = 2 + CPPDEFS += -DHSE_VALUE=12000000 -DCOLORLCD -DSTM32F429_439xx + BITMAPS += $(patsubst %.png,%.lbm,$(wildcard bitmaps/Horus/*.png)) + FONTS = fonts/font_stdsize.lbm fonts/font_tinsize.lbm fonts/font_smlsize.lbm fonts/font_midsize.lbm fonts/font_dblsize.lbm fonts/font_xxlsize.lbm + SDCARD = YES + THR_TRACE = YES + EXT = FRSKY_SPORT + AUTOSWITCH = YES + AUTOSOURCE = YES + DBLKEYS = YES + PPM_LIMITS_SYMETRICAL = YES + PPM_CENTER_ADJUSTABLE = YES + DSM2 = PPM + TIMERS = 2 + CPPDEFS += -DTIMERS=$(TIMERS) + CPPDEFS += -DPCBHORUS -DCPUARM -DCPUSTM32 -DVIRTUALINPUTS -DLUAINPUTS -DXCURVES -DEEPROM_VARIANT=0 -DAUDIO -DPXX + INCDIRS += targets/Horus $(THIRDPARTY) $(COOSDIR) $(COOSDIR)/kernel $(COOSDIR)/portable + INCDIRS += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/inc $(STM32LIBPATH)/CMSIS/Device/ST/STM32F4xx/Include $(STM32LIBPATH)/CMSIS/include + STM32USBPATH = $(THIRDPARTY)/STM32_USB-Host-Device_Lib_V2.1.0/Libraries + INCDIRS += $(STM32USBPATH)/STM32_USB_OTG_Driver/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Core/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/hid/inc + INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/cdc/inc + BOARDSRC = main_arm.cpp targets/Horus/board_horus.cpp targets/Horus/led_driver.cpp + EXTRABOARDSRC += targets/Horus/lcd_driver.cpp targets/Horus/delays.c targets/Horus/i2c_driver.cpp targets/taranis/configure_pins.cpp serial.cpp # targets/taranis/flash_driver.cpp + SRC += $(COOSDIR)/kernel/core.c $(COOSDIR)/kernel/hook.c $(COOSDIR)/kernel/task.c $(COOSDIR)/kernel/event.c $(COOSDIR)/kernel/time.c $(COOSDIR)/kernel/timer.c $(COOSDIR)/kernel/flag.c $(COOSDIR)/kernel/mutex.c $(COOSDIR)/kernel/serviceReq.c $(COOSDIR)/portable/GCC/port.c $(COOSDIR)/portable/arch.c + SRC += targets/Horus/pwr_driver.c targets/Horus/usb_driver.c targets/Horus/sdram_driver.c + PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp + CPPSRC += tasks_arm.cpp audio_arm.cpp sbus.cpp telemetry/telemetry.cpp + CPPSRC += targets/Horus/pulses_driver.cpp targets/Horus/keys_driver.cpp targets/Horus/adc_driver.cpp targets/Horus/trainer_driver.cpp targets/Horus/audio_driver.cpp targets/Horus/serial2_driver.cpp targets/Horus/telemetry_driver.cpp + CPPSRC += gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp + SRC += targets/Horus/system_stm32f4xx.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/misc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dbgmcu.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_rcc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_spi.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_i2c.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dma.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_rtc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_pwr.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_usart.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_sdio.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_fmc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_ltdc.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_tim.c + SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dma2d.c + SRC += $(STM32LIBPATH)/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc_ride7/startup_stm32f40_41xxx.s + SRC += targets/Horus/sdio_sd.c targets/Horus/usb_bsp.c targets/Horus/usbd_desc.c + SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_core.c + SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_dcd.c + SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_dcd_int.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_core.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_ioreq.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_req.c + EXTRABOARDSRC += targets/Horus/usbd_usr.cpp + SRC += syscalls.c + ifeq ($(USB), JOYSTICK) + CPPDEFS += -DUSB_JOYSTICK + SRC += targets/Horus/usbd_hid_joystick.c + else ifeq ($(USB), SERIAL) + CPPDEFS += -DUSB_SERIAL + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/cdc/src/usbd_cdc_core.c + EXTRABOARDSRC += targets/Horus/usbd_cdc.cpp + else + CPPDEFS += -DUSB_MASS_STORAGE + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_data.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_scsi.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_bot.c + SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_core.c + EXTRABOARDSRC += targets/Horus/usbd_storage_msd.cpp + endif + ifeq ($(GVARS), YES) + GUIMODELSRC += gui/$(GUIDIRECTORY)/menu_model_gvars.cpp + endif + ifneq ($(LUA), NO) + ifeq ($(LUA), YES) + CPPDEFS += -DLUA_MODEL_SCRIPTS + endif + CPPDEFS += -DLUA + INCDIRS += $(LUADIR) + CPPSRC += lua_api.cpp + LUASRC = $(LUADIR)/lapi.c $(LUADIR)/lcode.c $(LUADIR)/lctype.c $(LUADIR)/ldebug.c $(LUADIR)/ldo.c $(LUADIR)/ldump.c $(LUADIR)/lfunc.c $(LUADIR)/lgc.c $(LUADIR)/llex.c $(LUADIR)/lmem.c \ + $(LUADIR)/lobject.c $(LUADIR)/lopcodes.c $(LUADIR)/lparser.c $(LUADIR)/lstate.c $(LUADIR)/lstring.c $(LUADIR)/ltable.c $(LUADIR)/lrotable.c $(LUADIR)/ltm.c $(LUADIR)/lundump.c $(LUADIR)/lvm.c $(LUADIR)/lzio.c \ + $(LUADIR)/lbaselib.c $(LUADIR)/linit.c $(LUADIR)/lmathlib.c $(LUADIR)/lbitlib.c $(LUADIR)/loadlib.c $(LUADIR)/lauxlib.c $(LUADIR)/ltablib.c $(LUADIR)/lcorolib.c $(LUADIR)/liolib.c + SRC += $(LUASRC) + LUADEP = lua_exports_flamenco.inc + endif + + EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Horus/diskio.cpp + CPPDEFS += -DSDCARD -DVOICE -DRTCLOCK + CPPSRC += sdcard.cpp rtc.cpp logs.cpp targets/taranis/rtc_driver.cpp + GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp + INCDIRS += $(FATFSDIR) $(FATFSDIR)/option + + ifeq ($(DSM2), PPM) + CPPSRC += pulses/dsm2_arm.cpp + endif + + CPPDEFS += -DHAPTIC + CPPSRC += haptic.cpp + CPPSRC += targets/Horus/haptic_driver.cpp + EXTRABOARDSRC += + + ifeq ($(TW8823_TRACES), YES) + CPPDEFS += -DTW8823_TRACES + endif + + ifeq ($(TW8823_BITMAPS), YES) + CPPDEFS += -DTW8823_BITMAPS + endif + + ifeq ($(TW8823_SERIAL), YES) + CPPDEFS += -DTW8823_SERIAL + endif +endif + CC = $(TRGT)gcc CPP = $(TRGT)cpp AS = $(TRGT)as @@ -1084,7 +1335,7 @@ FORMAT = ihex TARGET = opentx # List C++ source files here. (C dependencies are automatically generated.) -ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X TARANIS)) +ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X TARANIS FLAMENCO HORUS)) TTS_SRC = translations/tts_cz.cpp translations/tts_de.cpp translations/tts_en.cpp translations/tts_es.cpp translations/tts_fr.cpp translations/tts_it.cpp translations/tts_nl.cpp translations/tts_pt.cpp translations/tts_sk.cpp translations/tts_se.cpp translations/tts_pl.cpp translations/tts_hu.cpp else STD_TTS_SRC = translations/tts_$(shell sh -c "echo $(TTS) | tr '[:upper:]' '[:lower:]'").cpp @@ -1144,7 +1395,7 @@ ifeq ($(NAVIGATION), ROTENC) endif ifeq ($(TURNIGY_TRANSMITTER_FIX), YES) - ifeq ($(PCB), $(filter $(PCB), TARANIS)) + ifeq ($(PCB), $(filter $(PCB), TARANIS FLAMENCO HORUS)) $(warning TURNIGY_TRANSMITTER_FIX is not available on this radio) else CPPDEFS += -DMODULE_ALWAYS_SEND_PULSES @@ -1195,7 +1446,7 @@ ifeq ($(BOLD), YES) CPPDEFS += -DBOLD_FONT endif -ifneq ($(PCB), $(filter $(PCB), TARANIS)) +ifneq ($(PCB), $(filter $(PCB), TARANIS FLAMENCO HORUS)) ifeq ($(BATTGRAPH), YES) CPPDEFS += -DBATTGRAPH endif @@ -1389,7 +1640,7 @@ ifeq ($(HELI), YES) endif ifeq ($(TEMPLATES), YES) - ifneq ($(PCB), $(filter $(PCB), TARANIS)) + ifneq ($(PCB), $(filter $(PCB), TARANIS FLAMENCO HORUS)) CPPDEFS += -DTEMPLATES CPPSRC += templates.cpp GUIMODELSRC += gui/$(GUIDIRECTORY)/menu_model_templates.cpp @@ -1592,30 +1843,51 @@ translations/se.h: translations/se.h.txt @echo "Create Swedish language specific header files" $(TRANSLATE) translations/se.h.txt translations/se.h se -FONTNAME = Roboto +ifeq ($(PCB), $(filter $(PCB), HORUS)) + FONTNAME = DejaVu\ Sans + TINSIZE = 5 + SMLSIZE = 7 + STDSIZE = 16 + MIDSIZE = 16 + DBLSIZE = 20 + XXLSIZE = 38 +else + FONTNAME = Roboto + TINSIZE = 5 + SMLSIZE = 7 + STDSIZE = 8 + MIDSIZE = 11 + DBLSIZE = 14 + XXLSIZE = 38 +endif .PHONY: fonts fonts: $(FONTS) +fonts/font_%size.lbm: fonts/font_%size.png + $(IMG2LBM) $^ $@ $(LCDSIZE) 8bits + +fonts/font_tinsize.png: + $(FONT2PNG) $(FONTNAME) $(TINSIZE) fonts/font_tinsize + +fonts/font_smlsize.png: + $(FONT2PNG) $(FONTNAME) $(SMLSIZE) fonts/font_smlsize + +fonts/font_stdsize.png: + $(FONT2PNG) $(FONTNAME) $(STDSIZE) fonts/font_stdsize + +fonts/font_midsize.png: + $(FONT2PNG) $(FONTNAME) $(MIDSIZE) fonts/font_midsize + +fonts/font_dblsize.png: + $(FONT2PNG) $(FONTNAME) $(DBLSIZE) fonts/font_dblsize + +fonts/font_xxlsize.png: + $(FONT2PNG) $(FONTNAME) $(XXLSIZE) fonts/font_xxlsize + fonts/%.lbm: fonts/%.png $(IMG2LBM) $^ $@ $(LCDSIZE) -fonts/font_tinsize.lbm: - $(FONT2PNG) $(FONTNAME) 5 fonts/font_tinsize.png - $(IMG2LBM) fonts/font_tinsize.png $@ $(LCDSIZE) 4bits - -fonts/font_stdsize.lbm: - $(FONT2PNG) $(FONTNAME) 8 fonts/font_stdsize.png - $(IMG2LBM) fonts/font_stdsize.png $@ $(LCDSIZE) 4bits - -fonts/font_midsize.lbm: - $(FONT2PNG) $(FONTNAME) 10 fonts/font_midsize.png - $(IMG2LBM) fonts/font_midsize.png $@ $(LCDSIZE) 4bits - -fonts/font_dblsize.lbm: - $(FONT2PNG) $(FONTNAME) 14 fonts/font_dblsize.png - $(IMG2LBM) fonts/font_dblsize.png $@ $(LCDSIZE) 4bits - .PHONY: bitmaps bitmaps: $(BITMAPS) @@ -1631,18 +1903,41 @@ bitmaps/Taranis/%.lbm: bitmaps/Taranis/%.png bitmaps/Taranis/mainmenu.png: ../util/taranisicons.py $(wildcard bitmaps/Taranis/std/*.png) @$(PYTHON) ../util/taranisicons.py +bitmaps/Flamenco/%.lbm: bitmaps/Flamenco/%.png + $(IMG2LBM) $^ $@ $(LCDSIZE) 4/4/4/4 + +bitmaps/Horus/mask_%.lbm: bitmaps/Horus/mask_%.png + $(IMG2LBM) $^ $@ $(LCDSIZE) 8bits + +bitmaps/Horus/%.lbm: bitmaps/Horus/%.png + $(IMG2LBM) $^ $@ $(LCDSIZE) 5/6/5 + ifeq ($(OS),Windows_NT) PARSER = avr-cpp else PARSER = gcc -E -x c++ endif -lua_exports.inc: myeeprom.h ../util/luaexport.py - @echo "Generating a list of Lua exported constants in lua_exports.txt" - @$(PARSER) -DPCBTARANIS -DCPUARM -DCPUSTM32 -DLUA -DEXPORT myeeprom.h | grep LEXP > lua_exports.txt - @echo "Parsing and generating C Lua exports in lua_exports.inc" - @$(PYTHON) ../util/luaexport.py $(VERSION) lua_exports.txt lua_exports.inc lua_fields.txt - @rm lua_exports.txt +lua_exports_taranis.inc: myeeprom.h ../util/luaexport.py + @echo "Generating a list of Lua exported constants in lua_exports_taranis.txt" + @$(PARSER) -DPCBTARANIS -DCPUARM -DCPUSTM32 -DLUA -DEXPORT myeeprom.h | grep LEXP > lua_exports_taranis.txt + @echo "Parsing and generating C Lua exports in lua_exports_taranis.inc" + @$(PYTHON) ../util/luaexport.py $(VERSION) lua_exports_taranis.txt lua_exports_taranis.inc lua_fields_taranis.txt + @rm lua_exports_taranis.txt + +lua_exports_horus.inc: myeeprom.h ../util/luaexport.py + @echo "Generating a list of Lua exported constants in lua_exports_horus.txt" + @$(PARSER) -DPCBFLAMENCO -DCPUARM -DCPUSTM32 -DLUA -DEXPORT myeeprom.h | grep LEXP > lua_exports_horus.txt + @echo "Parsing and generating C Lua exports in lua_exports_horus.inc" + @$(PYTHON) ../util/luaexport.py $(VERSION) lua_exports_horus.txt lua_exports_horus.inc lua_fields_horus.txt + @rm lua_exports_horus.txt + +lua_exports_flamenco.inc: myeeprom.h ../util/luaexport.py + @echo "Generating a list of Lua exported constants in lua_exports_flamenco.txt" + @$(PARSER) -DPCBFLAMENCO -DCPUARM -DCPUSTM32 -DLUA -DEXPORT myeeprom.h | grep LEXP > lua_exports_flamenco.txt + @echo "Parsing and generating C Lua exports in lua_exports_flamenco.inc" + @$(PYTHON) ../util/luaexport.py $(VERSION) lua_exports_flamenco.txt lua_exports_flamenco.inc lua_fields_flamenco.txt + @rm lua_exports_flamenco.txt # Eye candy. # AVR Studio 3.x does not check make's exit code but relies on @@ -1803,7 +2098,7 @@ clean_list : $(REMOVE) bitmaps/*/*/*.lbm $(REMOVE) fonts/*.lbm $(REMOVE) fonts/*/*.lbm - $(REMOVE) lua_exports.txt lua_exports.inc lua_fields.txt + $(REMOVE) lua_exports* lua_fields.txt $(MAKE) -C targets/taranis/bootloader clean #### Install diff --git a/radio/src/audio_arm.cpp b/radio/src/audio_arm.cpp index 23b9b4c39..58962c696 100644 --- a/radio/src/audio_arm.cpp +++ b/radio/src/audio_arm.cpp @@ -189,7 +189,7 @@ const char * const audioFilenames[] = { "midstck2", "midstck3", "midstck4", -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) "midpot1", "midpot2", "midslid1", @@ -265,6 +265,8 @@ void referenceSystemAudioFiles() fn = *fno.lfname ? fno.lfname : fno.fname; uint8_t len = strlen(fn); + TRACE("%s", fn); + // Eliminates directories / non wav files if (len < 5 || strcasecmp(fn+len-4, SOUNDS_EXT) || (fno.fattrib & AM_DIR)) continue; @@ -520,6 +522,7 @@ void audioTask(void* pdata) setSampleRate(AUDIO_SAMPLE_RATE); #if defined(SDCARD) + TRACE("unexpectedShutdown = %d", unexpectedShutdown); if (!unexpectedShutdown) { sdInit(); AUDIO_TADA(); @@ -1096,7 +1099,7 @@ void audioEvent(unsigned int index, unsigned int freq) case AU_STICK4_MIDDLE: case AU_POT1_MIDDLE: case AU_POT2_MIDDLE: -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) case AU_SLIDER1_MIDDLE: case AU_SLIDER2_MIDDLE: #else diff --git a/radio/src/buzzer.h b/radio/src/buzzer.h index 2a451b6fa..8bb0b8463 100644 --- a/radio/src/buzzer.h +++ b/radio/src/buzzer.h @@ -65,7 +65,7 @@ inline void _beep(uint8_t b) void beep(uint8_t val); #else /* BUZZER */ -#define beep(...) +inline void beep(uint8_t) { } #endif /* BUZZER */ #if !defined(AUDIO) diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index 23f911a4c..78003b42d 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -43,7 +43,7 @@ OS_TID cliTaskId; TaskStack cliStack; Fifo<256> cliRxFifo; -uint8_t cliTracesEnabled = false; +uint8_t cliTracesEnabled = true; typedef int (* CliFunction) (const char ** args); @@ -66,7 +66,7 @@ void cliPrompt() serialPutc('>'); } -int toInt(const char ** argv, int index, int * val) +int toLongLongInt(const char ** argv, int index, long long int * val) { if (*argv[index] == '\0') { return 0; @@ -79,7 +79,7 @@ int toInt(const char ** argv, int index, int * val) s = &argv[index][2]; } char * endptr = NULL; - *val = strtol(s, &endptr, base); + *val = strtoll(s, &endptr, base); if (*endptr == '\0') return 1; else { @@ -89,6 +89,14 @@ int toInt(const char ** argv, int index, int * val) } } +int toInt(const char ** argv, int index, int * val) +{ + long long int lval = 0; + int result = toLongLongInt(argv, index, &lval); + *val = (int)lval; + return result; +} + int cliBeep(const char ** argv) { int freq = BEEP_DEFAULT_FREQ; @@ -200,6 +208,33 @@ int cliVolume(const char ** argv) return 0; } +#if defined(PCBFLAMENCO) +int cliReadBQ24195(const char ** argv) +{ + int index = 0; + if (toInt(argv, 1, &index) > 0) { + serialPrint("BQ24195[%d] = 0x%02x", index, i2cReadBQ24195(index)); + } + else { + serialPrint("%s: Invalid arguments \"%s\" \"%s\"", argv[0], argv[1]); + } + return 0; +} + +int cliWriteBQ24195(const char ** argv) +{ + int index = 0; + int data = 0; + if (toInt(argv, 1, &index) > 0 && toInt(argv, 2, &data) > 0) { + i2cWriteBQ24195(index, data); + } + else { + serialPrint("%s: Invalid arguments \"%s\" \"%s\"", argv[0], argv[1], argv[2]); + } + return 0; +} +#endif + const MemArea memAreas[] = { { "RCC", RCC, sizeof(RCC_TypeDef) }, { "GPIOA", GPIOA, sizeof(GPIO_TypeDef) }, @@ -210,13 +245,14 @@ const MemArea memAreas[] = { { "GPIOF", GPIOF, sizeof(GPIO_TypeDef) }, { "GPIOG", GPIOG, sizeof(GPIO_TypeDef) }, { "USART1", USART1, sizeof(USART_TypeDef) }, + { "USART2", USART2, sizeof(USART_TypeDef) }, { "USART3", USART3, sizeof(USART_TypeDef) }, { NULL, NULL, 0 }, }; int cliDisplay(const char ** argv) { - int address = 0; + long long int address = 0; for (const MemArea * area = memAreas; area->name != NULL; area++) { if (!strcmp(area->name, argv[1])) { @@ -225,7 +261,26 @@ int cliDisplay(const char ** argv) } } - if (!strcmp(argv[1], "adc")) { + if (!strcmp(argv[1], "keys")) { + for (int i=0; i 0) { +#if defined(PCBFLAMENCO) + else if (!strcmp(argv[1], "bq24195")) { + { + uint8_t reg = i2cReadBQ24195(0x00); + serialPrint(reg & 0x80 ? "HIZ enable" : "HIZ disable"); + } + { + uint8_t reg = i2cReadBQ24195(0x08); + serialPrint(reg & 0x01 ? "VBatt < VSysMin" : "VBatt > VSysMin"); + serialPrint(reg & 0x02 ? "Thermal sensor bad" : "Thermal sensor ok"); + serialPrint(reg & 0x04 ? "Power ok" : "Power bad"); + serialPrint(reg & 0x08 ? "Connected to charger" : "Not connected to charger"); + const char * CHARGE_STATUS[] = { "Not Charging", "Precharge", "Fast Charging", "Charge done" }; + serialPrint(CHARGE_STATUS[(reg & 0x30) >> 4]); + const char * INPUT_STATUS[] = { "Unknown input", "USB host input", "USB adapter port input", "OTG input" }; + serialPrint(INPUT_STATUS[(reg & 0xC0) >> 6]); + } + { + uint8_t reg = i2cReadBQ24195(0x09); + if (reg & 0x80) serialPrint("Watchdog timer expiration"); + uint8_t chargerFault = (reg & 0x30) >> 4; + if (chargerFault == 0x01) + serialPrint("Input fault"); + else if (chargerFault == 0x02) + serialPrint("Thermal shutdown"); + else if (chargerFault == 0x03) + serialPrint("Charge safety timer expiration"); + if (reg & 0x08) serialPrint("Battery over voltage fault"); + uint8_t ntcFault = (reg & 0x07); + if (ntcFault == 0x05) + serialPrint("NTC cold"); + else if (ntcFault == 0x06) + serialPrint("NTC hot"); + } + } +#endif + else if (toLongLongInt(argv, 1, &address) > 0) { int size = 256; if (toInt(argv, 2, &size) >= 0) { dump((uint8_t *)address, size); @@ -259,6 +350,10 @@ const CliCommand cliCommands[] = { { "stackinfo", cliStackInfo, "" }, { "trace", cliTrace, "on | off" }, { "volume", cliVolume, "" }, +#if defined(PCBFLAMENCO) + { "read_bq24195", cliReadBQ24195, "" }, + { "write_bq24195", cliWriteBQ24195, " " }, +#endif { "help", cliHelp, "[]" }, { NULL, NULL, NULL } /* sentinel */ }; diff --git a/radio/src/cli.h b/radio/src/cli.h index 83836eeef..5a57f7755 100644 --- a/radio/src/cli.h +++ b/radio/src/cli.h @@ -50,5 +50,7 @@ extern TaskStack cliStack; #endif void cliStart(); +void cliStackPaint(); +uint32_t cliStackFree(); #endif // _CLI_H_ diff --git a/radio/src/eeprom_raw.cpp b/radio/src/eeprom_raw.cpp index 1b801f0a7..cbb792218 100644 --- a/radio/src/eeprom_raw.cpp +++ b/radio/src/eeprom_raw.cpp @@ -69,15 +69,15 @@ PACK(struct EepromFileHeader uint16_t size; }); -EepromHeader eepromHeader; -EepromWriteState eepromWriteState = EEPROM_IDLE; +EepromHeader eepromHeader __DMA; +volatile EepromWriteState eepromWriteState = EEPROM_IDLE; uint8_t eepromWriteZoneIndex = FIRST_FILE_AVAILABLE; uint8_t eepromWriteFileIndex; uint16_t eepromWriteSize; uint8_t * eepromWriteSourceAddr; uint32_t eepromWriteDestinationAddr; uint16_t eepromFatAddr = 0; -uint8_t eepromWriteBuffer[EEPROM_BUFFER_SIZE]; +uint8_t eepromWriteBuffer[EEPROM_BUFFER_SIZE] __DMA; void eepromWaitSpiComplete() { @@ -424,6 +424,8 @@ void eepromFormat() void eeErase(bool warn) { + TRACE("eeErase()"); + generalDefault(); modelDefault(0); @@ -442,6 +444,10 @@ void eeErase(bool warn) void eepromWriteWait(EepromWriteState state/* = EEPROM_IDLE*/) { while (eepromWriteState != state) { +#if defined(CPUSTM32) + // Waits a little bit for CS transitions + CoTickDelay(1/*2ms*/); +#endif eepromWriteProcess(); #ifdef SIMU sleep(5/*ms*/); diff --git a/radio/src/eeprom_raw.h b/radio/src/eeprom_raw.h index 8a73d74eb..28223337c 100644 --- a/radio/src/eeprom_raw.h +++ b/radio/src/eeprom_raw.h @@ -75,7 +75,7 @@ enum EepromWriteState { EEPROM_END_WRITE }; -extern EepromWriteState eepromWriteState; +extern volatile EepromWriteState eepromWriteState; inline bool eepromIsWriting() { return (eepromWriteState != EEPROM_IDLE); diff --git a/radio/src/eeprom_rlc.cpp b/radio/src/eeprom_rlc.cpp index f7271f4bb..bc7b6d897 100644 --- a/radio/src/eeprom_rlc.cpp +++ b/radio/src/eeprom_rlc.cpp @@ -933,7 +933,7 @@ void eeLoadModelName(uint8_t id, char *name) bool eeModelExists(uint8_t id) { - return EFile::exists(FILE_MODEL(id)); + return EFile::exists(FILE_MODEL(id)); } // TODO Now the 2 functions in eeprom_rlc.cpp and eeprom_raw.cpp are really close, should be merged. diff --git a/radio/src/fifo.h b/radio/src/fifo.h index 18a1d478a..312877bda 100644 --- a/radio/src/fifo.h +++ b/radio/src/fifo.h @@ -47,6 +47,11 @@ class Fifo { } + void clear() + { + widx = ridx = 0; + } + void push(uint8_t byte) { uint32_t next = (widx+1) & (N-1); if (next != ridx) { diff --git a/radio/src/fonts.h b/radio/src/fonts.h index bb7bd0b18..6bba1021a 100644 --- a/radio/src/fonts.h +++ b/radio/src/fonts.h @@ -37,6 +37,13 @@ #ifndef fonts_h #define fonts_h +#if defined(COLORLCD) + +extern const uint16_t *fontspecsTable[16]; +extern const pm_uchar *fontsTable[16]; + +#else + extern const pm_uchar font_5x7[]; extern const pm_uchar font_10x14[]; @@ -56,3 +63,5 @@ extern const pm_uchar font_4x6_extra[]; #endif #endif + +#endif diff --git a/radio/src/functions.cpp b/radio/src/functions.cpp index 99516aef6..c75648b2b 100644 --- a/radio/src/functions.cpp +++ b/radio/src/functions.cpp @@ -324,7 +324,7 @@ void evalFunctions() if (!isFunctionActive(FUNCTION_INSTANT_TRIM)) { #if defined(GUI) if (g_menuStack[0] == menuMainView -#if defined(FRSKY) +#if defined(FRSKY) && !defined(PCBFLAMENCO) && !defined(PCBHORUS) || g_menuStack[0] == menuTelemetryFrsky #endif #if defined(PCBTARANIS) diff --git a/radio/src/gui/Horus/fonts.cpp b/radio/src/gui/Horus/fonts.cpp new file mode 100644 index 000000000..2854108b8 --- /dev/null +++ b/radio/src/gui/Horus/fonts.cpp @@ -0,0 +1,88 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +const uint16_t font_tinsize_specs[] PROGMEM = +#include "fonts/font_tinsize.specs" +; + +const pm_uchar font_tinsize[] PROGMEM = { +#include "fonts/font_tinsize.lbm" +}; + +const uint16_t font_smlsize_specs[] PROGMEM = +#include "fonts/font_smlsize.specs" +; + +const pm_uchar font_smlsize[] PROGMEM = { +#include "fonts/font_smlsize.lbm" +}; + +const uint16_t font_stdsize_specs[] PROGMEM = +#include "fonts/font_stdsize.specs" +; + +const pm_uchar font_stdsize[] PROGMEM = { +#include "fonts/font_stdsize.lbm" +}; + +const uint16_t font_midsize_specs[] PROGMEM = +#include "fonts/font_midsize.specs" +; + +const pm_uchar font_midsize[] PROGMEM = { +#include "fonts/font_midsize.lbm" +}; + +const uint16_t font_dblsize_specs[] PROGMEM = +#include "fonts/font_dblsize.specs" +; + +const pm_uchar font_dblsize[] PROGMEM = { +#include "fonts/font_dblsize.lbm" +}; + +const uint16_t font_xxlsize_specs[] PROGMEM = +#include "fonts/font_xxlsize.specs" +; + +const pm_uchar font_xxlsize[] PROGMEM = { +#include "fonts/font_xxlsize.lbm" +}; + +const uint16_t *fontspecsTable[16] = { font_stdsize_specs, font_tinsize_specs, font_smlsize_specs, font_midsize_specs, font_dblsize_specs, font_xxlsize_specs }; +const pm_uchar *fontsTable[16] = { font_stdsize, font_tinsize, font_smlsize, font_midsize, font_dblsize, font_xxlsize }; diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h new file mode 100644 index 000000000..3e94f521a --- /dev/null +++ b/radio/src/gui/Horus/gui.h @@ -0,0 +1,70 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "lcd.h" +#include "menus.h" + +#define MENU_HEADER_HEIGHT 45 +#define MENU_TITLE_TOP 48 +#define MENU_TITLE_HEIGHT 21 +#define MENU_BODY_TOP 70 +#define MENU_CONTENT_TOP 72 +#define MENU_BODY_HEIGHT 182 +#define MENU_FOOTER_TOP 251 +#define MENU_FOOTER_HEIGHT 21 + +#define MENU_TITLE_LEFT 6 + +#define DEFAULT_SCROLLBAR_X LCD_W-10 +#define DEFAULT_SCROLLBAR_Y MENU_CONTENT_TOP +#define DEFAULT_SCROLLBAR_H (MENU_FOOTER_TOP - DEFAULT_SCROLLBAR_Y - 6) + +#define NUM_BODY_LINES 9 +#define FH 20 + +#define INVERT_HORZ_MARGIN 3 +#define INVERT_VERT_MARGIN 1 +#define INVERT_LINE_HEIGHT 18 + +void displaySplash(); +void displayScreenIndex(uint8_t index, uint8_t count); +void displayScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); +void drawHeader(int index); +void drawFooter(); +#define drawStatusLine(...) +void displayProgressBar(const char *label); +void updateProgressBar(int num, int den); + diff --git a/radio/src/gui/Horus/helpers.cpp b/radio/src/gui/Horus/helpers.cpp new file mode 100644 index 000000000..30eb04045 --- /dev/null +++ b/radio/src/gui/Horus/helpers.cpp @@ -0,0 +1,341 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +uint8_t switchToMix(uint8_t source) +{ + #warning en double ! + return 0; +} + +int circularIncDec(int current, int incr, int min, int max, IsValueAvailable isValueAvailable) +{ + do { + current += incr; + if (current < min) + current = max; + else if (current > max) + current = min; + if (!isValueAvailable || isValueAvailable(current)) + return current; + } while(1); + return 0; +} + +bool isInputAvailable(int input) +{ + for (int i=0; ichn == input) + return true; + } + return false; +} + +bool isChannelUsed(int channel) +{ + for (int i=0; isrcRaw == 0) return false; + if (md->destCh == channel) return true; + if (md->destCh > channel) return false; + } + return false; +} + +int getChannelsUsed() +{ + int result = 0; + int lastCh = -1; + for (int i=0; isrcRaw == 0) return result; + if (md->destCh != lastCh) { ++result; lastCh = md->destCh; } + } + return result; +} + +bool isSourceAvailable(int source) +{ + if (source>=MIXSRC_FIRST_INPUT && source<=MIXSRC_LAST_INPUT) { + return isInputAvailable(source - MIXSRC_FIRST_INPUT); + } + +#if defined(LUA_MODEL_SCRIPTS) + if (source>=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) { + div_t qr = div(source-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); + return (qr.rem=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) + return false; +#endif + + if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { + return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); + } + +#if !defined(HELI) + if (source>=MIXSRC_CYC1 && source<=MIXSRC_CYC3) + return false; +#endif + + if (source>=MIXSRC_CH1 && source<=MIXSRC_LAST_CH) { + return isChannelUsed(source-MIXSRC_CH1); + } + + if (source>=MIXSRC_FIRST_LOGICAL_SWITCH && source<=MIXSRC_LAST_LOGICAL_SWITCH) { + LogicalSwitchData * cs = lswAddress(source-MIXSRC_FIRST_LOGICAL_SWITCH); + return (cs->func != LS_FUNC_NONE); + } + +#if !defined(GVARS) + if (source>=MIXSRC_GVAR1 && source<=MIXSRC_LAST_GVAR) + return false; +#endif + + if (source>=MIXSRC_RESERVE1 && source<=MIXSRC_RESERVE5) + return false; + + if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); + if (qr.rem == 0) + return isTelemetryFieldAvailable(qr.quot); + else + return isTelemetryFieldComparisonAvailable(qr.quot); + } + + return true; +} + +bool isSourceAvailableInCustomSwitches(int source) +{ + bool result = isSourceAvailable(source); + +#if defined(FRSKY) + if (result && source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); + result = isTelemetryFieldComparisonAvailable(qr.quot); + } +#endif + + return result; +} + +bool isInputSourceAvailable(int source) +{ + if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) + return true; + + if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) + return true; + + if (source>=MIXSRC_TrimRud && source=MIXSRC_FIRST_CH && source<=MIXSRC_LAST_CH) + return true; + + if (source>=MIXSRC_FIRST_TRAINER && source<=MIXSRC_LAST_TRAINER) + return true; + + if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) + return isTelemetryFieldAvailable(source-MIXSRC_FIRST_TELEM); + + return false; +} + +enum SwitchContext +{ + LogicalSwitchesContext, + ModelCustomFunctionsContext, + GeneralCustomFunctionsContext, + TimersContext, + MixesContext +}; + +bool isSwitchAvailable(int swtch, SwitchContext context) +{ + uint32_t index = switchInfo(abs(swtch)).quot; + + if (swtch < 0) { + if (!IS_3POS(index)) + return false; + if (swtch == -SWSRC_ON || swtch == -SWSRC_ONE) { + return false; + } + swtch = -swtch; + } + + if (swtch <= SWSRC_FIRST_SWITCH) { + return true; + } + + if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH && swtch <= SWSRC_LAST_LOGICAL_SWITCH) { + if (context == GeneralCustomFunctionsContext) { + return false; + } + else if (context != LogicalSwitchesContext) { + LogicalSwitchData * cs = lswAddress(swtch-SWSRC_FIRST_LOGICAL_SWITCH); + return (cs->func != LS_FUNC_NONE); + } + } + + if (context != ModelCustomFunctionsContext && context != GeneralCustomFunctionsContext && (swtch == SWSRC_ON || swtch == SWSRC_ONE)) { + return false; + } + + if (swtch >= SWSRC_FIRST_FLIGHT_MODE && swtch <= SWSRC_LAST_FLIGHT_MODE) { + if (context == MixesContext || context == GeneralCustomFunctionsContext) { + return false; + } + else { + swtch -= SWSRC_FIRST_FLIGHT_MODE; + if (swtch == 0) { + return true; + } + FlightModeData * fm = flightModeAddress(swtch); + return (fm->swtch != SWSRC_NONE); + } + } + + return true; +} + +bool isSwitchAvailableInLogicalSwitches(int swtch) +{ + return isSwitchAvailable(swtch, LogicalSwitchesContext); +} + +bool isSwitchAvailableInCustomFunctions(int swtch) +{ + if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) + return isSwitchAvailable(swtch, ModelCustomFunctionsContext); + else + return isSwitchAvailable(swtch, GeneralCustomFunctionsContext); +} + +bool isSwitchAvailableInMixes(int swtch) +{ + return isSwitchAvailable(swtch, MixesContext); +} + +bool isSwitchAvailableInTimers(int swtch) +{ + if (swtch >= 0) { + if (swtch < TMRMODE_COUNT) + return true; + else + swtch -= TMRMODE_COUNT-1; + } + else { + if (swtch > -TMRMODE_COUNT) + return false; + else + swtch += TMRMODE_COUNT-1; + } + + return isSwitchAvailable(swtch, TimersContext); +} + +bool isLogicalSwitchFunctionAvailable(int function) +{ + return function != LS_FUNC_RANGE; +} + +bool isAssignableFunctionAvailable(int function) +{ +#if defined(OVERRIDE_CHANNEL_FUNCTION) || defined(GVARS) + bool modelFunctions = (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions); +#endif + + switch (function) { + case FUNC_OVERRIDE_CHANNEL: +#if defined(OVERRIDE_CHANNEL_FUNCTION) + return modelFunctions; +#else + return false; +#endif + case FUNC_ADJUST_GVAR: +#if defined(GVARS) + return modelFunctions; +#else + return false; +#endif +#if !defined(HAPTIC) + case FUNC_HAPTIC: +#endif +#if !defined(DANGEROUS_MODULE_FUNCTIONS) + case FUNC_RANGECHECK: + case FUNC_BIND: +// case FUNC_MODULE_OFF: +#endif +#if !defined(LUA) + case FUNC_PLAY_SCRIPT: +#endif + case FUNC_RESERVE4: + case FUNC_RESERVE5: + return false; + + default: + return true; + } +} + +bool isModuleAvailable(int module) +{ + if (module == MODULE_TYPE_NONE) + return false; + +#if !defined(PXX) + if (module == MODULE_TYPE_XJT) + return false; +#endif + + return true; +} + + +bool modelHasNotes() +{ + char filename[sizeof(MODELS_PATH)+1+sizeof(g_model.header.name)+sizeof(TEXT_EXT)] = MODELS_PATH "/"; + char *buf = strcat_modelname(&filename[sizeof(MODELS_PATH)], g_eeGeneral.currModel); + strcpy(buf, TEXT_EXT); + return isFileAvailable(filename); +} diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp new file mode 100644 index 000000000..c65c059a6 --- /dev/null +++ b/radio/src/gui/Horus/lcd.cpp @@ -0,0 +1,964 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#if defined(SIMU) +display_t displayBuf[DISPLAY_BUF_SIZE]; +#endif + +uint16_t lcdColorTable[LCD_COLOR_COUNT]; + +#define PIXEL_PTR(x, y) &displayBuf[(y)*LCD_W + (x)] + +void lcdColorsInit() +{ + lcdColorTable[TEXT_COLOR_INDEX] = BLACK; + lcdColorTable[TEXT_BGCOLOR_INDEX] = WHITE; + lcdColorTable[TEXT_INVERTED_COLOR_INDEX] = WHITE; + lcdColorTable[TEXT_INVERTED_BGCOLOR_INDEX] = RED; + lcdColorTable[LINE_COLOR_INDEX] = RGB(88, 88, 90); + lcdColorTable[SCROLLBOX_COLOR_INDEX] = RED; + lcdColorTable[MENU_TITLE_BGCOLOR_INDEX] = DARKGREY; + lcdColorTable[MENU_TITLE_COLOR_INDEX] = WHITE; + lcdColorTable[HEADER_COLOR_INDEX] = DARKGREY; + lcdColorTable[ALARM_COLOR_INDEX] = RED; + lcdColorTable[WARNING_COLOR_INDEX] = YELLOW; + lcdColorTable[TEXT_DISABLE_COLOR_INDEX] = RGB(0x60, 0x60, 0x60); + lcdColorTable[CURVE_AXIS_COLOR_INDEX] = BLUE; + lcdColorTable[TITLE_BGCOLOR_INDEX] = RED; + lcdColorTable[HEADER_BGCOLOR_INDEX] = DARKRED; +} + +coord_t lcdLastPos; +coord_t lcdNextPos; + +#define FONT_CHARS_COUNT 103 + +char getMappedChar(unsigned char c) +{ + if (c == 0) + return 0; + else if (c < 0xC0) + return c - 0x20; + else + return c - 0xC0 + 96; +} + +int getFontPatternWidth(const uint16_t *spec, int index) +{ + return spec[index+1] - spec[index]; +} + +int getCharWidth(char c, const uint16_t *spec) +{ + return getFontPatternWidth(spec, getMappedChar(c)); +} + +void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags flags, coord_t offset, coord_t width) +{ + coord_t w = *((uint16_t *)img); + coord_t height = *(((uint16_t *)img)+1); + + if (!width || width > w) { + width = w; + } + + if (x+width > LCD_W) { + width = LCD_W-x; + } + + display_t color = lcdColorTable[COLOR_IDX(flags)]; + + for (coord_t row=0; row> 8; + const pm_uchar * font = fontsTable[fontindex]; + const uint16_t * fontspecs = fontspecsTable[fontindex]; + lcdPutFontPattern(x, y, font, fontspecs, getMappedChar(c), flags); +} + +void lcd_putc(coord_t x, coord_t y, const unsigned char c) +{ + lcd_putcAtt(x, y, c, 0); +} + +uint8_t getStringInfo(const char *s) +{ + uint8_t result = 0; + for (int i=0; s[i]; i++) { + result += s[i]; + } + return result; +} + +int getFontHeight(LcdFlags flags) +{ + static const int heightTable[16] = { 12, 10, 11, 14, 18, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 }; + return heightTable[FONTSIZE(flags) >> 8]; +} + +int getTextWidth(const pm_char *s, int len, LcdFlags flags) +{ + const uint16_t *specs = fontspecsTable[FONTSIZE(flags) >> 8]; + int result = 0; + for (int i=0; (len==0 || i= 0x20) { + lcd_putcAtt(x, y, c, flags); + x = lcdNextPos; + } + else if (c == 0x1F) { // X-coord prefix + setx = true; + } + else if (c == 0x1E) { + x = orig_x; + y += height; + } + else if (c == 1) { + x += 1; + } + else { + x += 2*(c-1); + } + s++; + } + lcdLastPos = x; + lcdNextPos = x; +} + +void lcd_putsn(coord_t x, coord_t y, const pm_char * s, uint8_t len) +{ + lcd_putsnAtt(x, y, s, len, TEXT_COLOR); +} + +void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags flags) +{ + lcd_putsnAtt(x, y, s, 255, flags); +} + +void lcd_puts(coord_t x, coord_t y, const pm_char * s) +{ + lcd_putsAtt(x, y, s, 0); +} + +void lcd_putsLeft(coord_t y, const pm_char * s) +{ + lcd_puts(MENU_TITLE_LEFT, y, s); +} + +void lcd_putsCenter(coord_t y, const pm_char * s, LcdFlags attr) +{ + int x = (LCD_W - getTextWidth(s)) / 2; + lcd_putsAtt(x, y, s, attr); +} + +void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags) +{ + uint8_t length; + length = pgm_read_byte(s++); + lcd_putsnAtt(x, y, s+length*idx, length, flags & ~ZCHAR); +} + +void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags flags) +{ + char s[5]; + for (int i=0; i<4; i++) { + char c = val & 0xf; + s[3-i] = c>9 ? c+'A'-10 : c+'0'; + val >>= 4; + } + s[4] = '\0'; + if (!(flags & LEFT)) + x -= getTextWidth(s); + else + flags -= LEFT; + lcd_putsAtt(x, y, s, flags); +} + +void lcd_outdez8(coord_t x, coord_t y, int8_t val) +{ + lcd_outdezAtt(x, y, val); +} + +void lcd_outdezAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, const char *suffix, const char *prefix) +{ + lcd_outdezNAtt(x, y, val, flags, 0, suffix, prefix); +} + +void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, int len, const char *suffix, const char *prefix) +{ + char str[48+1]; // max=16 for the prefix, 16 chars for the number, 16 chars for the suffix + char *s = str+32; + *s = '\0'; + int idx = 0; + int mode = MODE(flags); + bool neg = false; + if (val < 0) { + val = -val; + neg = true; + } + do { + *--s = '0' + (val % 10); + ++idx; + val /= 10; + if (mode!=0 && idx==mode) { + mode = 0; + *--s = '.'; + if (val==0) + *--s = '0'; + } + } while (val!=0 || mode>0 || (mode==MODE(LEADING0) && idx>1; + int y = dxabs>>1; + int px = x1; + int py = y1; + + if (dxabs >= dyabs) { + /* the line is more horizontal than vertical */ + for (int i=0; i<=dxabs; i++) { + y += dyabs; + if (y>=dxabs) { + y -= dxabs; + py += sdy; + } + if ((1<<(px%8)) & pat) { + lcdDrawPoint(px, py, att); + } + px += sdx; + } + } + else { + /* the line is more vertical than horizontal */ + for (int i=0; i<=dyabs; i++) { + x += dxabs; + if (x >= dyabs) { + x -= dyabs; + px += sdx; + } + if ((1<<(py%8)) & pat) { + lcdDrawPoint(px, py, att); + } + py += sdy; + } + } +} +#endif + +void lcd_vline(coord_t x, scoord_t y, scoord_t h) +{ + lcd_vlineStip(x, y, h, SOLID); +} + +void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) +{ + if (att & ROUND) { + lcd_vlineStip(x, y+1, h-2, pat, att); + lcd_vlineStip(x+w-1, y+1, h-2, pat, att); + lcd_hlineStip(x+1, y+h-1, w-2, pat, att); + lcd_hlineStip(x+1, y, w-2, pat, att); + } + else { + lcd_vlineStip(x, y, h, pat, att); + lcd_vlineStip(x+w-1, y, h, pat, att); + lcd_hlineStip(x+1, y+h-1, w-2, pat, att); + lcd_hlineStip(x+1, y, w-2, pat, att); + } +} + +#if defined(SIMU) +void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) +{ + for (scoord_t i=y; i> 1) + ((pat & 1) << 7); + } +} +#endif + +void putsRtcTime(coord_t x, coord_t y, LcdFlags att) +{ + putsTimer(x, y, getValue(MIXSRC_TX_TIME), att); +} + +void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att) +{ + div_t qr; + + char str[10]; // "-00:00:00" + char *s = str; + + att &= ~LEFT; + + if (tme < 0) { + // TODO lcd_putcAtt(x - ((att & DBLSIZE) ? FW+2 : ((att & MIDSIZE) ? FW+0 : FWNUM)), y, '-', att); + tme = -tme; + *s++ = '-'; + } + + qr = div(tme, 60); + + if (att & TIMEHOUR) { + div_t qr2 = div(qr.quot, 60); + *s++ = '0' + (qr2.quot/10); + *s++ = '0' + (qr2.quot%10); + *s++ = ':'; + qr.quot = qr2.rem; + } + + *s++ = '0' + (qr.quot/10); + *s++ = '0' + (qr.quot%10); + *s++ = ':'; + *s++ = '0' + (qr.rem/10); + *s++ = '0' + (qr.rem%10); + *s = '\0'; + + lcd_putsAtt(x, y, str, att); +} + +// TODO to be optimized with putsValueWithUnit +void putsVolts(coord_t x, coord_t y, uint16_t volts, LcdFlags att) +{ + lcd_outdezAtt(x, y, (int16_t)volts, (~NO_UNIT) & (att | ((att&PREC2)==PREC2 ? 0 : PREC1)), (~att & NO_UNIT) ? "v" : NULL); +} + +void putsVBat(coord_t x, coord_t y, LcdFlags att) +{ + putsVolts(x, y, g_vbat100mV, att); +} + +void putsStrIdx(coord_t x, coord_t y, const pm_char *str, int idx, LcdFlags att, const char *prefix) +{ + // TODO quick & dirty, dangerous + char s[32]; + sprintf(s, "%s%s%d", prefix, str, abs(idx)); + lcd_putsAtt(x, y, s, att); +} + +void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) +{ + if (idx == 0) { + lcd_putsiAtt(x, y, STR_VSRCRAW, 0, att); // TODO macro + } + else if (idx <= MIXSRC_LAST_INPUT) { + char s[32] = "\307"; + if (ZEXIST(g_model.inputNames[idx-MIXSRC_FIRST_INPUT])) { + zchar2str(s+1, g_model.inputNames[idx-MIXSRC_FIRST_INPUT], LEN_INPUT_NAME); + s[1+LEN_INPUT_NAME] = '\0'; + } + else { + sprintf(s+1, "%02d", idx); + } + lcd_putsAtt(x, y, s, att); + } + + else if (idx <= MIXSRC_LAST_LUA) { + div_t qr = div(idx-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); +#if defined(LUA_MODEL_SCRIPTS) + if (qr.quot < MAX_SCRIPTS && qr.rem < scriptInputsOutputs[qr.quot].outputsCount) { + lcd_putcAtt(x+2, y+1, '1'+qr.quot, TINSIZE); + lcdDrawFilledRect(x, y, 7, 7); + lcd_putsnAtt(x+8, y, scriptInputsOutputs[qr.quot].outputs[qr.rem].name, att & STREXPANDED ? 9 : 4, att); + } + else +#endif + { + putsStrIdx(x, y, "LUA", qr.quot+1, att); + lcd_putcAtt(x+20, y, 'a'+qr.rem, att); + } + } + + else if (idx < MIXSRC_LAST_POT) { + idx = idx-MIXSRC_Rud; + if (ZEXIST(g_eeGeneral.anaNames[idx])) + lcd_putsnAtt(x, y, g_eeGeneral.anaNames[idx], LEN_ANA_NAME, ZCHAR|att); + else + lcd_putsiAtt(x, y, STR_VSRCRAW, idx+1, att); + } + else if (idx >= MIXSRC_FIRST_SWITCH && idx < MIXSRC_FIRST_LOGICAL_SWITCH) { + idx = idx-MIXSRC_FIRST_SWITCH; + if (ZEXIST(g_eeGeneral.switchNames[idx])) + lcd_putsnAtt(x, y, g_eeGeneral.switchNames[idx], LEN_SWITCH_NAME, ZCHAR|att); + else + lcd_putsiAtt(x, y, STR_VSRCRAW, idx+MIXSRC_FIRST_SWITCH-MIXSRC_Rud+1, att); + } + else if (idx < MIXSRC_SW1) + lcd_putsiAtt(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1, att); + else if (idx <= MIXSRC_LAST_LOGICAL_SWITCH) + putsSwitches(x, y, SWSRC_SW1+idx-MIXSRC_SW1, att); + else if (idx < MIXSRC_CH1) + putsStrIdx(x, y, STR_PPM_TRAINER, idx-MIXSRC_FIRST_TRAINER+1, att); + else if (idx <= MIXSRC_LAST_CH) { + putsStrIdx(x, y, STR_CH, idx-MIXSRC_CH1+1, att); +#if 0 + if (ZEXIST(g_model.limitData[idx-MIXSRC_CH1].name) && (att & STREXPANDED)) { + lcd_putcAtt(lcdLastPos, y, ' ', att); + lcd_putsnAtt(lcdLastPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att); + } +#endif + } + else if (idx <= MIXSRC_LAST_GVAR) + putsStrIdx(x, y, STR_GV, idx-MIXSRC_GVAR1+1, att); + else if (idx < MIXSRC_FIRST_TELEM) { + lcd_putsiAtt(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1-NUM_LOGICAL_SWITCH-NUM_TRAINER-NUM_CHNOUT-MAX_GVARS, att); + } + else { + idx -= MIXSRC_FIRST_TELEM; + div_t qr = div(idx, 3); + char s[sizeof(g_model.telemetrySensors[qr.quot].label)+2]; + int pos = zchar2str(s, g_model.telemetrySensors[qr.quot].label, sizeof(g_model.telemetrySensors[qr.quot].label)); + if (qr.rem) s[pos++] = (qr.rem==2 ? '+' : '-'); + s[pos] = '\0'; + lcd_putsAtt(x, y, s, att); + } +} + +void putsChnLetter(coord_t x, coord_t y, uint8_t idx, LcdFlags att) +{ + lcd_putsiAtt(x, y, STR_RETA123, idx-1, att); +} + +void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) +{ + uint8_t len = sizeof(g_model.header.name); + while (len>0 && !name[len-1]) --len; + if (len==0) { + putsStrIdx(x, y, STR_MODEL, id+1, att|LEADING0); + } + else { + lcd_putsnAtt(x, y, name, sizeof(g_model.header.name), ZCHAR|att); + } +} + +void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) +{ + if (idx == SWSRC_OFF) + return lcd_putsiAtt(x, y, STR_OFFON, 0, att); + + char s[8]; + int pos = 0; + if (idx < 0) { + s[pos++] = '!'; + } + + int absidx = abs(idx); + +#if defined(FLIGHT_MODES) + if (absidx >= SWSRC_FIRST_FLIGHT_MODE) { + return putsStrIdx(x, y, STR_FP, absidx-SWSRC_FIRST_FLIGHT_MODE, att, idx < 0 ? "!" : ""); + } + else +#endif + if (absidx >= SWSRC_FIRST_SWITCH && absidx <= SWSRC_LAST_SWITCH) { + div_t swinfo = switchInfo(absidx); + if (ZEXIST(g_eeGeneral.switchNames[swinfo.quot])) { + pos = zchar2str(&s[pos], g_eeGeneral.switchNames[swinfo.quot], LEN_SWITCH_NAME); + s[pos++] = "\300-\301"[swinfo.rem]; + s[pos] = '\0'; + lcd_putsAtt(x, y, s, att); + return; + } + } + + strAppend(&s[pos], STR_VSWITCHES+1+absidx*STR_VSWITCHES[0], STR_VSWITCHES[0]); + lcd_putsAtt(x, y, s, att); +} + +#if defined(FLIGHT_MODES) +void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att) +{ + if (idx==0) { + lcd_putsiAtt(x, y, STR_MMMINV, 0, att); + } + else { + putsStrIdx(x, y, STR_FP, abs(idx)-1, att); + } +} +#endif + +void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att) +{ + if (curve.value != 0) { + switch (curve.type) { + case CURVE_REF_DIFF: + lcd_putsAtt(x, y, "D", att); + GVAR_MENU_ITEM(x+8, y, curve.value, -100, 100, LEFT|att, 0, 0); + break; + + case CURVE_REF_EXPO: + lcd_putsAtt(x, y, "E", att); + GVAR_MENU_ITEM(x+8, y, curve.value, -100, 100, LEFT|att, 0, 0); + break; + + case CURVE_REF_FUNC: + lcd_putsiAtt(x, y, STR_VCURVEFUNC, curve.value, att); + break; + + case CURVE_REF_CUSTOM: + putsCurve(x, y, curve.value, att); + break; + } + } +} + +void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att) +{ + if (idx == 0) { + return lcd_putsiAtt(x, y, STR_MMMINV, 0, att); + } + bool neg = false; + if (idx < 0) { + idx = -idx; + neg = true; + } + putsStrIdx(x, y, STR_CV, idx, att); + if (neg) { + if ((att&INVERS) && ((~att&BLINK) || BLINK_ON_PHASE)) + att &= ~(INVERS|BLINK); + lcd_putcAtt(x-3, y, '!', att); + } +} + +void putsTimerMode(coord_t x, coord_t y, int8_t mode, LcdFlags att) +{ + if (mode >= 0) { + if (mode < TMRMODE_COUNT) + return lcd_putsiAtt(x, y, STR_VTMRMODES, mode, att); + else + mode -= (TMRMODE_COUNT-1); + } + putsSwitches(x, y, mode, att); +} + +void putsTrimMode(coord_t x, coord_t y, uint8_t phase, uint8_t idx, LcdFlags att) +{ + trim_t v = getRawTrimValue(phase, idx); + unsigned int mode = v.mode; + unsigned int p = mode >> 1; + + if (mode == TRIM_MODE_NONE) { + lcd_putsAtt(x, y, "--", att); + } + else { + char s[2]; + s[0] = (mode % 2 == 0) ? ':' : '+'; + s[1] = '0'+p; + lcd_putsnAtt(x, y, s, 2, att); + } +} + +const pm_uint8_t bchunit_ar[] PROGMEM = { + UNIT_DIST, // Alt + UNIT_RAW, // Rpm + UNIT_PERCENT, // Fuel + UNIT_TEMPERATURE, // T1 + UNIT_TEMPERATURE, // T2 + UNIT_KTS, // Speed + UNIT_DIST, // Dist + UNIT_DIST, // GPS Alt +}; + +void putsValueWithUnit(coord_t x, coord_t y, lcdint_t val, uint8_t unit, LcdFlags att) +{ + // convertUnit(val, unit); + if (!(att & NO_UNIT) && unit != UNIT_RAW) { + char unitStr[8]; + strAppend(unitStr, STR_VTELEMUNIT+1+unit*STR_VTELEMUNIT[0], STR_VTELEMUNIT[0]); + lcd_outdezAtt(x, y, val, att, unitStr); + } + else { + lcd_outdezAtt(x, y, val, att); + } +} + +void displayDate(coord_t x, coord_t y, TelemetryItem & telemetryItem, LcdFlags att) +{ +} + +void displayGpsCoords(coord_t x, coord_t y, TelemetryItem & telemetryItem, LcdFlags att) +{ +} + +void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, lcdint_t value, LcdFlags att) +{ + TelemetryItem & telemetryItem = telemetryItems[channel]; + TelemetrySensor & telemetrySensor = g_model.telemetrySensors[channel]; + + if (telemetrySensor.unit == UNIT_DATETIME) { + displayDate(x, y, telemetryItem, att); + } + else if (telemetrySensor.unit == UNIT_GPS) { + displayGpsCoords(x, y, telemetryItem, att); + } + else { + LcdFlags flags = att; + if (telemetrySensor.prec==2) + flags |= PREC2; + else if (telemetrySensor.prec==1) + flags |= PREC1; + putsValueWithUnit(x, y, value, telemetrySensor.unit == UNIT_CELLS ? UNIT_VOLTS : telemetrySensor.unit, flags); + } +} + +void putsChannelValue(coord_t x, coord_t y, source_t channel, lcdint_t value, LcdFlags att) +{ + if (channel >= MIXSRC_FIRST_TELEM) { + channel = (channel-MIXSRC_FIRST_TELEM) / 3; + putsTelemetryChannelValue(x, y, channel, value, att); + } + else if (channel >= MIXSRC_FIRST_TIMER || channel == MIXSRC_TX_TIME) { + putsTimer(x, y, value, att); + } + else if (channel == MIXSRC_TX_VOLTAGE) { + lcd_outdezAtt(x, y, value, att|PREC1); + } + else if (channel < MIXSRC_FIRST_CH) { + lcd_outdezAtt(x, y, calcRESXto100(value), att); + } + else if (channel <= MIXSRC_LAST_CH) { +#if defined(PPM_UNIT_PERCENT_PREC1) + lcd_outdezAtt(x, y, calcRESXto1000(value), att|PREC1); +#else + lcd_outdezAtt(x, y, calcRESXto100(value), att); +#endif + } + else { + lcd_outdezAtt(x, y, value, att); + } +} + +void putsChannel(coord_t x, coord_t y, source_t channel, LcdFlags att) +{ + getvalue_t value = getValue(channel); + putsChannelValue(x, y, channel, value, att); +} + +void lcdSetContrast() +{ + lcdSetRefVolt(g_eeGeneral.contrast); +} + +void lcdDrawPixel(display_t * p, display_t value) +{ + *p = value; +} + +void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color) +{ + display_t * p = PIXEL_PTR(x, y); + lcdDrawTransparentPixel(p, opacity, color); +} + +void lcdDrawTransparentPixel(display_t * p, uint8_t opacity, uint16_t color) +{ + ASSERT_IN_DISPLAY(p); + + if (opacity != OPACITY_MAX) { + uint8_t bgWeight = OPACITY_MAX - opacity; + COLOR_SPLIT(color, red, green, blue); + COLOR_SPLIT(*p, bgRed, bgGreen, bgBlue); + uint16_t r = (bgRed * bgWeight + red * opacity) / OPACITY_MAX; + uint16_t g = (bgGreen * bgWeight + green * opacity) / OPACITY_MAX; + uint16_t b = (bgBlue * bgWeight + blue * opacity) / OPACITY_MAX; + lcdDrawPixel(p, COLOR_JOIN(r, g, b)); + } + else if (opacity != 0) { + lcdDrawPixel(p, color); + } +} + +void lcdDrawPoint(coord_t x, coord_t y, LcdFlags att) +{ + display_t * p = PIXEL_PTR(x, y); + display_t color = lcdColorTable[COLOR_IDX(att)]; + if (p < DISPLAY_END) { + *p = color; + } +} + +void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) +{ + if (y >= LCD_H) return; + if (x+w > LCD_W) { w = LCD_W - x; } + + display_t * p = PIXEL_PTR(x, y); + display_t color = lcdColorTable[COLOR_IDX(att)]; + + while (w--) { + if (pat&1) { + *p = color; + // lcd_mask(p, mask, 0); + pat = (pat >> 1) | 0x80; + } + else { + pat = pat >> 1; + } + p++; + } +} + +void lcd_vlineStip(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att) +{ + if (x >= LCD_W) return; + if (y >= LCD_H) return; + if (h<0) { y+=h; h=-h; } + if (y<0) { h+=y; y=0; if (h<=0) return; } + if (y+h > LCD_H) { h = LCD_H - y; } + + if (pat==DOTTED && !(y%2)) { + pat = ~pat; + } + + while (h--) { + if (pat & 1) { + lcdDrawPoint(x, y, att); + pat = (pat >> 1) | 0x80; + } + else { + pat = pat >> 1; + } + y++; + } +} + +#if !defined(BOOT) +void lcdDrawBitmap(coord_t x, coord_t y, const uint16_t * img, coord_t offset, coord_t width) +{ + const uint16_t * q = img; + coord_t w = *q++; + coord_t height = *q++; + + if (!width || width > w) { + width = w; + } + + if (x+width > LCD_W) { + width = LCD_W-x; + } + + for (coord_t row=0; row + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#ifndef _LCD_H_ +#define _LCD_H_ + +#define LCD_W 480 +#define LCD_H 272 + +#define BOX_WIDTH 61 +#define coord_t int +#define scoord_t int +#define CENTER +#define CENTER_OFS 0 +#define CONTRAST_MIN 0 +#define CONTRAST_MAX 45 + +#define lcdint_t int32_t +#define lcduint_t uint32_t + +#define LCD_LINES 17 // TODO change this constant which has no sense here, the header uses more than one line +#define LCD_COLS 30 + +#define BSS 0x00 +#define ERASE 0x00 +#define FORCE 0x00 +#define FIXEDWIDTH 0x00 +#define FILLWHITE 0x00 +#define BOLD 0x00 + +/* lcd common flags */ +#define BLINK 0x01 + +/* lcd text flags */ +#define INVERS 0x02 +#define LEFT 0x08 /* align left */ + +/* lcd puts flags */ +#define ZCHAR 0x04 + +/* lcd outdez flags */ +#define LEADING0 0x10 +#define PREC1 0x20 +#define PREC2 0x30 +#define MODE(flags) ((((int8_t)(flags) & 0x30) - 0x10) >> 4) + +/* rect, square flags */ +#define ROUND 0x02 + +/* telemetry flags */ +#define NO_UNIT 0x40 + +#define FONTSIZE(x) ((x) & 0x0700) +#define TINSIZE 0x0100 +#define SMLSIZE 0x0200 +#define MIDSIZE 0x0300 +#define DBLSIZE 0x0400 +#define XXLSIZE 0x0500 +#define ERASEBG 0x8000 +#define VERTICAL 0x0800 + +#define TIMEBLINK 0x1000 +#define TIMEHOUR 0x2000 +#define STREXPANDED 0x4000 + +// remove windows default definitions +#undef OPAQUE +#undef RGB + +#define TO5BITS(x) ((x) >> 3) +#define TO6BITS(x) ((x) >> 2) +#define RGB(r, g, b) ((TO5BITS(r) << 11) + (TO6BITS(g) << 5) + (TO5BITS(b) << 0)) +#define WHITE RGB(0xF0, 0xF0, 0xF0) +#define BLACK RGB(0, 0, 0) +#define YELLOW RGB(0xF0, 0xD0, 0x10) +#define BLUE RGB(0x30, 0xA0, 0xE0) +#define DARKGREY RGB(0x40, 0x40, 0x40) +#define RED RGB(229, 32, 30) +#define DARKRED RGB(160, 0, 6) + +#define LcdFlags uint32_t + +enum LcdColorIndex +{ + TEXT_COLOR_INDEX, + TEXT_BGCOLOR_INDEX, + TEXT_INVERTED_COLOR_INDEX, + TEXT_INVERTED_BGCOLOR_INDEX, + LINE_COLOR_INDEX, + SCROLLBOX_COLOR_INDEX, + MENU_TITLE_BGCOLOR_INDEX, + MENU_TITLE_COLOR_INDEX, + HEADER_COLOR_INDEX, + ALARM_COLOR_INDEX, + WARNING_COLOR_INDEX, + TEXT_DISABLE_COLOR_INDEX, + CURVE_AXIS_COLOR_INDEX, + HEADER_BGCOLOR_INDEX, + TITLE_BGCOLOR_INDEX, + LCD_COLOR_COUNT +}; + +extern uint16_t lcdColorTable[LCD_COLOR_COUNT]; +void lcdColorsInit(); + +#define OPACITY_MAX 0x0F + +#define COLOR(index) ((index) << 16) +#define TEXT_COLOR COLOR(TEXT_COLOR_INDEX) +#define TEXT_BGCOLOR COLOR(TEXT_BGCOLOR_INDEX) +#define TEXT_INVERTED_COLOR COLOR(TEXT_INVERTED_COLOR_INDEX) +#define TEXT_INVERTED_BGCOLOR COLOR(TEXT_INVERTED_BGCOLOR_INDEX) +#define LINE_COLOR COLOR(LINE_COLOR_INDEX) +#define SCROLLBOX_COLOR COLOR(SCROLLBOX_COLOR_INDEX) +#define HEADER_SEPARATOR_COLOR COLOR(HEADER_SEPARATOR_COLOR_INDEX) +#define MENU_TITLE_BGCOLOR COLOR(MENU_TITLE_BGCOLOR_INDEX) +#define MENU_TITLE_COLOR COLOR(MENU_TITLE_COLOR_INDEX) +#define HEADER_COLOR COLOR(HEADER_COLOR_INDEX) +#define ALARM_COLOR COLOR(ALARM_COLOR_INDEX) +#define WARNING_COLOR COLOR(WARNING_COLOR_INDEX) +#define TEXT_DISABLE_COLOR COLOR(TEXT_DISABLE_COLOR_INDEX) +#define CURVE_AXIS_COLOR COLOR(CURVE_AXIS_COLOR_INDEX) +#define TITLE_BGCOLOR COLOR(TITLE_BGCOLOR_INDEX) +#define HEADER_BGCOLOR COLOR(HEADER_BGCOLOR_INDEX) + +#define COLOR_IDX(att) uint8_t((att) >> 16) + +#define COLOR_SPLIT(color, r, g, b) \ + uint16_t r = ((color) & 0xF800) >> 11; \ + uint16_t g = ((color) & 0x07E0) >> 5; \ + uint16_t b = ((color) & 0x001F) + +#define COLOR_JOIN(r, g, b) \ + (((r) << 11) + ((g) << 5) + (b)) + +#define display_t uint16_t +#define DISPLAY_BUF_SIZE (LCD_W*LCD_H) + +#if defined(SIMU) +extern display_t displayBuf[DISPLAY_BUF_SIZE]; +#else +extern uint32_t CurrentFrameBuffer; +#define displayBuf ((uint16_t *)CurrentFrameBuffer) +#endif + +#define lcdRefreshWait() + +#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) +#define DISPLAY_END (displayBuf + DISPLAY_BUF_SIZE) +#define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) + +void lcdStartScreen(); +void lcd_putc(coord_t x, coord_t y, const unsigned char c); +void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags attr=TEXT_COLOR); +void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags attr=TEXT_COLOR); +void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s, uint8_t idx, LcdFlags attr=TEXT_COLOR); +void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, unsigned char len, LcdFlags attr=TEXT_COLOR); +void lcd_puts(coord_t x, coord_t y, const pm_char * s); +void lcd_putsn(coord_t x, coord_t y, const pm_char * s, unsigned char len); +void lcd_putsLeft(coord_t y, const pm_char * s); +void lcd_putsCenter(coord_t y, const pm_char * s, LcdFlags attr=0); +void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags mode=0); +void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, int len, const char *suffix=NULL, const char *prefix=NULL); +void lcd_outdezAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags mode=0, const char *suffix=NULL, const char *prefix=NULL); +void lcd_outdez8(coord_t x, coord_t y, int8_t val); + +void putsStrIdx(coord_t x, coord_t y, const pm_char *str, int idx, LcdFlags att=0, const char *prefix=""); +void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att); +void putsSwitches(coord_t x, coord_t y, int8_t swtch, LcdFlags att=0); +void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att=0); +void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att=0); +void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att=0); +void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att=0); +void putsTimerMode(coord_t x, coord_t y, int8_t mode, LcdFlags att=0); +void putsTrimMode(coord_t x, coord_t y, uint8_t phase, uint8_t idx, LcdFlags att); + +#define putsChn(x, y, idx, att) putsMixerSource(x, y, MIXSRC_CH1+idx-1, att) +void putsChnLetter(coord_t x, coord_t y, uint8_t idx, LcdFlags attr); + +void putsVolts(coord_t x, coord_t y, uint16_t volts, LcdFlags att); +void putsVBat(coord_t x, coord_t y, LcdFlags att); + +void putsChannelValue(coord_t x, coord_t y, source_t channel, lcdint_t val, LcdFlags att=0); +void putsChannel(coord_t x, coord_t y, source_t channel, LcdFlags att=0); +void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, lcdint_t val, LcdFlags att=0); + +#define putstime_t int32_t + +void putsRtcTime(coord_t x, coord_t y, LcdFlags att=0); +void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att=0); + +#define SOLID 0xff +#define DOTTED 0x55 + +void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color); +void lcdDrawTransparentPixel(display_t * p, uint8_t opacity, uint16_t color); +void lcdDrawPoint(coord_t x, coord_t y, LcdFlags att=0); +void lcd_mask(uint8_t *p, uint8_t mask, LcdFlags att=0); +void lcd_hline(coord_t x, coord_t y, coord_t w, LcdFlags att=0); +void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); +void lcd_vline(coord_t x, scoord_t y, scoord_t h); +void lcd_vlineStip(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); +void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); + +#if !defined(SIMU) +inline void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) +{ + lcdFillRectangle(x, y, w, h, lcdColorTable[COLOR_IDX(att)]); +} +#else +void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att); +#endif + +inline void lcdDrawHorizontalLine(coord_t x, scoord_t y, coord_t w, LcdFlags att) +{ + lcdDrawFilledRect(x, y, w, 1, att); +} + +inline void lcdDrawVerticalLine(coord_t x, scoord_t y, coord_t h, LcdFlags att) +{ + lcdDrawFilledRect(x, y, 1, h, att); +} + +inline void lcdDrawRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) +{ + lcdDrawVerticalLine(x, y, h, att); + lcdDrawVerticalLine(x+w-1, y, h, att); + lcdDrawHorizontalLine(x, y, w, att); + lcdDrawHorizontalLine(x, y+h-1, w, att); +} + +void lcdDrawFilledRectWithAttributes(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att); +void drawBlackOverlay(); +void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); + +inline void lcd_square(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { lcd_rect(x, y, w, w, SOLID, att); } + +void lcdDrawTelemetryTopBar(); + +#define V_BAR(xx, yy, ll) \ + lcd_vline(xx-1,yy-ll,ll); \ + lcd_vline(xx ,yy-ll,ll); \ + lcd_vline(xx+1,yy-ll,ll); + +void lcd_img(coord_t x, coord_t y, const pm_uchar * img, uint8_t idx, LcdFlags att=0); + +int getTextWidth(const pm_char *s, int len=0, LcdFlags flags=0); +void lcdDrawBitmap(coord_t x, coord_t y, const uint16_t * img, coord_t offset=0, coord_t width=0); +void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags flags=0, coord_t offset=0, coord_t width=0); + +#define lcdSetRefVolt(...) +void lcdSetContrast(); +void lcdInit(); +#define lcdOff(...) + +void lcdRefresh(); +void lcdSendByte(uint8_t data); +void lcdSend(const uint8_t * data, uint32_t size); +void lcdSendString(const char * s); +uint32_t lcdReceive(uint8_t * data, uint32_t len); +void lcdClearRxBuffer(); + +const pm_char * bmpLoad(uint8_t *dest, const char *filename, const unsigned int width, const unsigned int height); + +#if defined(BOOT) + #define BLINK_ON_PHASE (0) +#else + #define BLINK_ON_PHASE (g_blinkTmr10ms & (1<<6)) +#endif + +#ifdef SIMU + extern bool lcd_refresh; + extern display_t lcd_buf[DISPLAY_BUF_SIZE]; +#endif + +char *strAppend(char * dest, const char * source, int len=0); +char *strSetCursor(char *dest, int position); +char *strAppendDate(char * str, bool time=false); +char *strAppendFilename(char * dest, const char * filename, const int size); + +#define BITMAP_IDX_SPLASH 0x00 +#define BITMAP_IDX_ALERT 0x01 +#define BITMAP_IDX_WARNING 0x02 +#define BITMAP_IDX_MESSAGE 0x03 +#define BITMAP_IDX_USB 0x04 +#define BITMAP_IDX_STICKS_FIRST 0x05 // 4 bitmaps +#define BITMAP_IDX_BATTERY_FIRST 0x09 // 5 bitmaps +#define BITMAP_IDX_RSSI_FIRST 0x0E // 6 bitmaps +#define BITMAP_IDX_CHECKBOX_FIRST 0x14 // 4 bitmaps +#define BITMAP_IDX_MODEL_FIRST 0x80 + +#endif // _LCD_H_ diff --git a/radio/src/gui/Horus/menu_general.cpp b/radio/src/gui/Horus/menu_general.cpp new file mode 100644 index 000000000..ac98ffe5c --- /dev/null +++ b/radio/src/gui/Horus/menu_general.cpp @@ -0,0 +1,96 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +extern LP_CONST LanguagePack czLanguagePack; +extern LP_CONST LanguagePack enLanguagePack; +extern LP_CONST LanguagePack esLanguagePack; +extern LP_CONST LanguagePack frLanguagePack; +extern LP_CONST LanguagePack deLanguagePack; +extern LP_CONST LanguagePack itLanguagePack; +extern LP_CONST LanguagePack plLanguagePack; +extern LP_CONST LanguagePack ptLanguagePack; +extern LP_CONST LanguagePack skLanguagePack; +extern LP_CONST LanguagePack seLanguagePack; +extern LP_CONST LanguagePack huLanguagePack; + +const LanguagePack * LP_CONST languagePacks[] = { + // alphabetical order + &czLanguagePack, + &deLanguagePack, + &enLanguagePack, + &esLanguagePack, + &frLanguagePack, + &huLanguagePack, + &itLanguagePack, + &plLanguagePack, + &ptLanguagePack, + &seLanguagePack, + &skLanguagePack, + NULL +}; + +void menuGeneralSetup(evt_t event); +void menuGeneralSdManager(evt_t event); +void menuGeneralCustomFunctions(evt_t event); +void menuGeneralTrainer(evt_t event); +void menuGeneralVersion(evt_t event); +void menuGeneralCalib(evt_t event); + +enum EnumTabDiag { + e_Setup, + e_Sd, + e_GeneralCustomFunctions, + e_Trainer, + e_Calib, + e_Vers, +}; + +const MenuFuncP_PROGMEM menuTabGeneral[] PROGMEM = { + menuGeneralSetup, + CASE_SDCARD(menuGeneralSdManager) + CASE_CPUARM(menuGeneralCustomFunctions) + menuGeneralTrainer, + menuGeneralCalib, + menuGeneralVersion, +}; + +void menuGeneralCustomFunctions(evt_t event) +{ + MENU(STR_MENUGLOBALFUNCS, menuTabGeneral, e_GeneralCustomFunctions, NUM_CFN, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|4/*repeated*/ }); + return menuCustomFunctions(event, g_eeGeneral.customFn, globalFunctionsContext); +} diff --git a/radio/src/gui/Horus/menu_general_calib.cpp b/radio/src/gui/Horus/menu_general_calib.cpp new file mode 100644 index 000000000..0b491d187 --- /dev/null +++ b/radio/src/gui/Horus/menu_general_calib.cpp @@ -0,0 +1,216 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define XPOT_DELTA 10 +#define XPOT_DELAY 10 /* cycles */ + +void menuCommonCalib(evt_t event) +{ + for (uint8_t i=0; i= POT1 && i <= POT_LAST) { + if (IS_POT_WITHOUT_DETENT(i)) { + reusableBuffer.calib.midVals[i] = (reusableBuffer.calib.hiVals[i] + reusableBuffer.calib.loVals[i]) / 2; + } +#if 0 + uint8_t idx = i - POT1; + int count = reusableBuffer.calib.xpotsCalib[idx].stepsCount; + if (IS_POT_MULTIPOS(i) && count <= XPOTS_MULTIPOS_COUNT) { + if (reusableBuffer.calib.xpotsCalib[idx].lastCount == 0 || vt < reusableBuffer.calib.xpotsCalib[idx].lastPosition - XPOT_DELTA || vt > reusableBuffer.calib.xpotsCalib[idx].lastPosition + XPOT_DELTA) { + reusableBuffer.calib.xpotsCalib[idx].lastPosition = vt; + reusableBuffer.calib.xpotsCalib[idx].lastCount = 1; + } + else { + if (reusableBuffer.calib.xpotsCalib[idx].lastCount < 255) reusableBuffer.calib.xpotsCalib[idx].lastCount++; + } + if (reusableBuffer.calib.xpotsCalib[idx].lastCount == XPOT_DELAY) { + int16_t position = reusableBuffer.calib.xpotsCalib[idx].lastPosition; + bool found = false; + for (int j=0; j= step-XPOT_DELTA && position <= step+XPOT_DELTA) { + found = true; + break; + } + } + if (!found) { + if (count < XPOTS_MULTIPOS_COUNT) { + reusableBuffer.calib.xpotsCalib[idx].steps[count] = position; + } + reusableBuffer.calib.xpotsCalib[idx].stepsCount += 1; + } + } + } +#endif + } + } + + calibrationState = reusableBuffer.calib.state; // make sure we don't scroll while calibrating + + switch (event) + { + case EVT_ENTRY: + reusableBuffer.calib.state = 0; + break; + + case EVT_KEY_BREAK(KEY_ENTER): + reusableBuffer.calib.state++; + break; + } + + switch (reusableBuffer.calib.state) { + case 0: + // START CALIBRATION + if (!READ_ONLY()) { + lcd_putsCenter(MENU_HEADER_HEIGHT+FH, STR_MENUTOSTART); + } + break; + + case 1: + // SET MIDPOINT + lcd_putsCenter(MENU_HEADER_HEIGHT+FH, STR_SETMIDPOINT, INVERS); + lcd_putsCenter(MENU_HEADER_HEIGHT+2*FH, STR_MENUWHENDONE); + + for (int i=0; i 50) { + g_eeGeneral.calib[i].mid = reusableBuffer.calib.midVals[i]; + int16_t v = reusableBuffer.calib.midVals[i] - reusableBuffer.calib.loVals[i]; + g_eeGeneral.calib[i].spanNeg = v - v/STICK_TOLERANCE; + v = reusableBuffer.calib.hiVals[i] - reusableBuffer.calib.midVals[i]; + g_eeGeneral.calib[i].spanPos = v - v/STICK_TOLERANCE; + } + } + break; + + case 3: +#if 0 + for (int i=POT1; i<=POT_LAST; i++) { + int idx = i - POT1; + int count = reusableBuffer.calib.xpotsCalib[idx].stepsCount; + if (IS_POT_MULTIPOS(i)) { + if (count > 1 && count <= XPOTS_MULTIPOS_COUNT) { + for (int j=0; jcount = count - 1; + for (int j=0; jcount; j++) { + calib->steps[j] = (reusableBuffer.calib.xpotsCalib[idx].steps[j+1] + reusableBuffer.calib.xpotsCalib[idx].steps[j]) >> 5; + } + } + else { + g_eeGeneral.potsType &= ~(0x03<<(2*idx)); + } + } + } +#endif + g_eeGeneral.chkSum = evalChkSum(); + eeDirty(EE_GENERAL); + reusableBuffer.calib.state = 4; + break; + + default: + reusableBuffer.calib.state = 0; + break; + } + + doMainScreenGraphics(); + drawPotsBars(); + +#if 0 + for (int i=POT1; i<=POT_LAST; i++) { + uint8_t steps = 0; + if (reusableBuffer.calib.state == 2) { + steps = reusableBuffer.calib.xpotsCalib[i-POT1].stepsCount; + } + else if (IS_POT_MULTIPOS(i)) { + StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[i]; + steps = calib->count + 1; + } + if (steps > 0 && steps <= XPOTS_MULTIPOS_COUNT) { + lcd_outdezAtt(LCD_W/2-2+(i-POT1)*5, LCD_H-6, steps, TINSIZE); + } + } +#endif +} + +void menuGeneralCalib(evt_t event) +{ + SIMPLE_MENU(STR_MENUCALIBRATION, menuTabGeneral, e_Calib, 0, DEFAULT_SCROLLBAR_X); + + if (menuEvent) { + calibrationState = 0; + } + + menuCommonCalib(READ_ONLY() ? 0 : event); +} + +void menuFirstCalib(evt_t event) +{ + if (event == EVT_KEY_BREAK(KEY_EXIT) || reusableBuffer.calib.state == 4) { + calibrationState = 0; + chainMenu(menuMainView); + } + else { + drawMenuTemplate(STR_MENUCALIBRATION, event); + menuCommonCalib(event); + } +} diff --git a/radio/src/gui/Horus/menu_general_sdmanager.cpp b/radio/src/gui/Horus/menu_general_sdmanager.cpp new file mode 100644 index 000000000..24890cf56 --- /dev/null +++ b/radio/src/gui/Horus/menu_general_sdmanager.cpp @@ -0,0 +1,358 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define REFRESH_FILES() reusableBuffer.sdmanager.offset = 65535 + +void menuGeneralSdManagerInfo(evt_t event) +{ + SIMPLE_SUBMENU(STR_SD_INFO_TITLE, 1, DEFAULT_SCROLLBAR_X); + + lcd_putsLeft(2*FH, STR_SD_TYPE); + lcd_puts(100, 2*FH, SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD); + + lcd_putsLeft(3*FH, STR_SD_SIZE); + lcd_outdezAtt(100, 3*FH, sdGetSize(), LEFT, "M"); + + lcd_putsLeft(4*FH, STR_SD_SECTORS); +#if defined(SD_GET_FREE_BLOCKNR) + lcd_outdezAtt(100, 4*FH, SD_GET_FREE_BLOCKNR()/1000, LEFT, "/"); + lcd_outdezAtt(150, 4*FH, sdGetNoSectors()/1000, LEFT); +#else + lcd_outdezAtt(100, 4*FH, sdGetNoSectors()/1000, LEFT, "k"); +#endif + + lcd_putsLeft(5*FH, STR_SD_SPEED); + lcd_outdezAtt(100, 5*FH, SD_GET_SPEED()/1000, LEFT, "kb/s"); +} + +inline bool isFilenameGreater(bool isfile, const char * fn, const char * line) +{ + return (isfile && !line[SD_SCREEN_FILE_LENGTH+1]) || (isfile==(bool)line[SD_SCREEN_FILE_LENGTH+1] && strcasecmp(fn, line) > 0); +} + +inline bool isFilenameLower(bool isfile, const char * fn, const char * line) +{ + return (!isfile && line[SD_SCREEN_FILE_LENGTH+1]) || (isfile==(bool)line[SD_SCREEN_FILE_LENGTH+1] && strcasecmp(fn, line) < 0); +} + +void onSdManagerMenu(const char *result) +{ + TCHAR lfn[_MAX_LFN+1]; + + // TODO possible buffer overflows here! + + uint8_t index = m_posVert-s_pgOfs; + char *line = reusableBuffer.sdmanager.lines[index]; + + if (result == STR_SD_INFO) { + pushMenu(menuGeneralSdManagerInfo); + } + else if (result == STR_SD_FORMAT) { + POPUP_CONFIRMATION(STR_CONFIRM_FORMAT); + } + else if (result == STR_COPY_FILE) { + clipboard.type = CLIPBOARD_TYPE_SD_FILE; + f_getcwd(clipboard.data.sd.directory, CLIPBOARD_PATH_LEN); + strncpy(clipboard.data.sd.filename, line, CLIPBOARD_PATH_LEN-1); + } + else if (result == STR_PASTE) { + f_getcwd(lfn, _MAX_LFN); + POPUP_WARNING(fileCopy(clipboard.data.sd.filename, clipboard.data.sd.directory, lfn)); + REFRESH_FILES(); + } + else if (result == STR_RENAME_FILE) { + char *ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); + if (ext) { + memcpy(reusableBuffer.sdmanager.originalName, line, sizeof(reusableBuffer.sdmanager.originalName)); + // write spaces to allow a longer filename + memset(ext, ' ', SD_SCREEN_FILE_LENGTH-LEN_FILE_EXTENSION-(ext-line)); + line[SD_SCREEN_FILE_LENGTH-LEN_FILE_EXTENSION] = '\0'; + s_editMode = EDIT_MODIFY_STRING; + editNameCursorPos = 0; + } + } + else if (result == STR_DELETE_FILE) { + f_getcwd(lfn, _MAX_LFN); + strcat(lfn, PSTR("/")); + strcat(lfn, line); + f_unlink(lfn); + REFRESH_FILES(); + if (m_posVert == reusableBuffer.sdmanager.count-1) + m_posVert--; + } + /* TODO else if (result == STR_LOAD_FILE) { + f_getcwd(lfn, _MAX_LFN); + strcat(lfn, "/"); + strcat(lfn, line); + POPUP_WARNING(eeLoadModelSD(lfn)); + } */ + else if (result == STR_PLAY_FILE) { + f_getcwd(lfn, _MAX_LFN); + strcat(lfn, "/"); + strcat(lfn, line); + audioQueue.stopAll(); + audioQueue.playFile(lfn, 0, ID_PLAY_FROM_SD_MANAGER); + } +#if 0 + else if (result == STR_ASSIGN_BITMAP) { + strAppendFilename(g_model.header.bitmap, line, sizeof(g_model.header.bitmap)); + memcpy(modelHeaders[g_eeGeneral.currModel].bitmap, g_model.header.bitmap, sizeof(g_model.header.bitmap)); + eeDirty(EE_MODEL); + } +#endif + else if (result == STR_VIEW_TEXT) { + f_getcwd(lfn, _MAX_LFN); + strcat(lfn, "/"); + strcat(lfn, line); + pushMenuTextView(lfn); + } +#if 0 + else if (result == STR_FLASH_BOOTLOADER) { + f_getcwd(lfn, _MAX_LFN); + strcat(lfn, "/"); + strcat(lfn, line); + flashBootloader(lfn); + } +#endif +#if defined(LUA) + else if (result == STR_EXECUTE_FILE) { + f_getcwd(lfn, _MAX_LFN); + strcat(lfn, "/"); + strcat(lfn, line); + luaExec(lfn); + } +#endif +} + +void menuGeneralSdManager(evt_t _event) +{ + FILINFO fno; + DIR dir; + char *fn; /* This function is assuming non-Unicode cfg. */ + TCHAR lfn[_MAX_LFN + 1]; + fno.lfname = lfn; + fno.lfsize = sizeof(lfn); + + if (s_warning_result) { + s_warning_result = 0; + displayPopup(STR_FORMATTING); + closeLogs(); + audioQueue.stopSD(); + if (f_mkfs(0, 1, 0) == FR_OK) { + f_chdir("/"); + REFRESH_FILES(); + } + else { + POPUP_WARNING(STR_SDCARD_ERROR); + } + } + + evt_t event = (EVT_KEY_MASK(_event) == KEY_ENTER ? 0 : _event); + SIMPLE_MENU(SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD, menuTabGeneral, e_Sd, reusableBuffer.sdmanager.count, DEFAULT_SCROLLBAR_X); + + int index = m_posVert-s_pgOfs; + + switch(_event) { + case EVT_ENTRY: + f_chdir(ROOT_PATH); + REFRESH_FILES(); + break; + +#if 0 + case EVT_KEY_LONG(KEY_MENU): + if (!READ_ONLY() && s_editMode == 0) { + killEvents(_event); + MENU_ADD_ITEM(STR_SD_INFO); TODO: Implement + MENU_ADD_ITEM(STR_SD_FORMAT); + menuHandler = onSdManagerMenu; + } + break; +#endif + + case EVT_KEY_BREAK(KEY_EXIT): + REFRESH_FILES(); + break; + + case EVT_KEY_BREAK(KEY_ENTER): + if (s_editMode > 0) { + break; + } + else { + if (!reusableBuffer.sdmanager.lines[index][SD_SCREEN_FILE_LENGTH+1]) { + f_chdir(reusableBuffer.sdmanager.lines[index]); + s_pgOfs = 0; + m_posVert = 1; + index = 1; + REFRESH_FILES(); + killEvents(_event); + return; + } + } + // no break + + case EVT_KEY_LONG(KEY_ENTER): + killEvents(_event); + if (s_editMode == 0) { + char *line = reusableBuffer.sdmanager.lines[index]; + char *ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); + if (ext) { + if (!strcasecmp(ext, SOUNDS_EXT)) { + MENU_ADD_ITEM(STR_PLAY_FILE); + } + else if (!strcasecmp(ext, TEXT_EXT)) { + MENU_ADD_ITEM(STR_VIEW_TEXT); + } + else if (!strcasecmp(ext, FIRMWARE_EXT) && !READ_ONLY()) { + MENU_ADD_ITEM(STR_FLASH_BOOTLOADER); + } +#if defined(LUA) + else if (!strcasecmp(ext, SCRIPTS_EXT)) { + MENU_ADD_ITEM(STR_EXECUTE_FILE); + } +#endif + } + if (!READ_ONLY()) { + if (line[SD_SCREEN_FILE_LENGTH+1]) // it's a file + MENU_ADD_ITEM(STR_COPY_FILE); + if (clipboard.type == CLIPBOARD_TYPE_SD_FILE) + MENU_ADD_ITEM(STR_PASTE); + // MENU_ADD_ITEM(STR_RENAME_FILE); + MENU_ADD_ITEM(STR_DELETE_FILE); + } + } + menuHandler = onSdManagerMenu; + break; + } + + if (reusableBuffer.sdmanager.offset != s_pgOfs) { + if (s_pgOfs == 0) { + reusableBuffer.sdmanager.offset = 0; + memset(reusableBuffer.sdmanager.lines, 0, sizeof(reusableBuffer.sdmanager.lines)); + } + else if (s_pgOfs == reusableBuffer.sdmanager.count-NUM_BODY_LINES) { + reusableBuffer.sdmanager.offset = s_pgOfs; + memset(reusableBuffer.sdmanager.lines, 0, sizeof(reusableBuffer.sdmanager.lines)); + } + else if (s_pgOfs > reusableBuffer.sdmanager.offset) { + memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0])); + memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0xff, SD_SCREEN_FILE_LENGTH); + reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1][SD_SCREEN_FILE_LENGTH+1] = 1; + } + else { + memmove(reusableBuffer.sdmanager.lines[1], reusableBuffer.sdmanager.lines[0], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0])); + memset(reusableBuffer.sdmanager.lines[0], 0, sizeof(reusableBuffer.sdmanager.lines[0])); + } + + reusableBuffer.sdmanager.count = 0; + + FRESULT res = f_opendir(&dir, "."); /* Open the directory */ + if (res == FR_OK) { + for (;;) { + res = f_readdir(&dir, &fno); /* Read a directory item */ + if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */ + if (fno.fname[0] == '.' && fno.fname[1] == '\0') continue; /* Ignore dot entry */ +#if _USE_LFN + fn = *fno.lfname ? fno.lfname : fno.fname; +#else + fn = fno.fname; +#endif + if (strlen(fn) > SD_SCREEN_FILE_LENGTH) continue; + + reusableBuffer.sdmanager.count++; + + bool isfile = !(fno.fattrib & AM_DIR); + + if (s_pgOfs == 0) { + for (int i=0; i=0; i--) { + char *line = reusableBuffer.sdmanager.lines[i]; + if (line[0] == '\0' || isFilenameGreater(isfile, fn, line)) { + if (i > 0) memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], sizeof(reusableBuffer.sdmanager.lines[0]) * i); + memset(line, 0, sizeof(reusableBuffer.sdmanager.lines[i])); + strcpy(line, fn); + line[SD_SCREEN_FILE_LENGTH+1] = isfile; + break; + } + } + } + else if (s_pgOfs > reusableBuffer.sdmanager.offset) { + if (isFilenameGreater(isfile, fn, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-2]) && isFilenameLower(isfile, fn, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1])) { + memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0, sizeof(reusableBuffer.sdmanager.lines[0])); + strcpy(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], fn); + reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1][SD_SCREEN_FILE_LENGTH+1] = isfile; + } + } + else { + if (isFilenameLower(isfile, fn, reusableBuffer.sdmanager.lines[1]) && isFilenameGreater(isfile, fn, reusableBuffer.sdmanager.lines[0])) { + memset(reusableBuffer.sdmanager.lines[0], 0, sizeof(reusableBuffer.sdmanager.lines[0])); + strcpy(reusableBuffer.sdmanager.lines[0], fn); + reusableBuffer.sdmanager.lines[0][SD_SCREEN_FILE_LENGTH+1] = isfile; + } + } + } + } + } + + reusableBuffer.sdmanager.offset = s_pgOfs; + + for (int i=0; i + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define RADIO_SETUP_2ND_COLUMN 220 + +#define SLIDER_5POS(y, value, label, event, attr) { \ + int8_t tmp = value; \ + displaySlider(RADIO_SETUP_2ND_COLUMN, y, 2+tmp, 4, attr); \ + value = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, label, NULL, tmp, -2, +2, attr, event); \ +} + +#if defined(SPLASH) && !defined(FSPLASH) + #define CASE_SPLASH_PARAM(x) x, +#else + #define CASE_SPLASH_PARAM(x) +#endif + +enum menuGeneralSetupItems { + CASE_RTCLOCK(ITEM_SETUP_DATE) + CASE_RTCLOCK(ITEM_SETUP_TIME) + // ITEM_SETUP_BATT_RANGE, + ITEM_SETUP_SOUND_LABEL, + ITEM_SETUP_BEEP_MODE, + ITEM_SETUP_SPEAKER_VOLUME, + ITEM_SETUP_BEEP_VOLUME, + ITEM_SETUP_BEEP_LENGTH, + ITEM_SETUP_SPEAKER_PITCH, + ITEM_SETUP_WAV_VOLUME, + ITEM_SETUP_BACKGROUND_VOLUME, + CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_LABEL) + CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_VOLUME) + CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_PITCH) + CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_RANGE) + CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_REPEAT) + CASE_HAPTIC(ITEM_SETUP_HAPTIC_LABEL) + CASE_HAPTIC(ITEM_SETUP_HAPTIC_MODE) + CASE_HAPTIC(ITEM_SETUP_HAPTIC_LENGTH) + CASE_HAPTIC(ITEM_SETUP_HAPTIC_STRENGTH) + // ITEM_SETUP_CONTRAST, + ITEM_SETUP_ALARMS_LABEL, + ITEM_SETUP_BATTERY_WARNING, + ITEM_SETUP_INACTIVITY_ALARM, + // ITEM_SETUP_MEMORY_WARNING, + ITEM_SETUP_ALARM_WARNING, + // ITEM_SETUP_BACKLIGHT_LABEL, + // ITEM_SETUP_BACKLIGHT_MODE, + // ITEM_SETUP_BACKLIGHT_DELAY, + // ITEM_SETUP_BRIGHTNESS, + // ITEM_SETUP_FLASH_BEEP, + // CASE_SPLASH_PARAM(ITEM_SETUP_DISABLE_SPLASH) + CASE_GPS(ITEM_SETUP_TIMEZONE) + CASE_GPS(ITEM_SETUP_GPSFORMAT) + CASE_PXX(ITEM_SETUP_COUNTRYCODE) + ITEM_SETUP_LANGUAGE, + ITEM_SETUP_IMPERIAL, + // IF_FAI_CHOICE(ITEM_SETUP_FAI) + // CASE_MAVLINK(ITEM_MAVLINK_BAUD) + ITEM_SETUP_SWITCHES_DELAY, + ITEM_SETUP_RX_CHANNEL_ORD, + ITEM_SETUP_STICK_MODE_LABELS, + ITEM_SETUP_STICK_MODE, + ITEM_SETUP_MAX +}; + +void menuGeneralSetup(evt_t event) +{ +#if defined(RTCLOCK) + struct gtm t; + gettime(&t); + + if ((m_posVert==ITEM_SETUP_DATE || m_posVert==ITEM_SETUP_TIME) && + (s_editMode>0) && + (event==EVT_KEY_FIRST(KEY_ENTER) || event==EVT_KEY_FIRST(KEY_EXIT) || IS_ROTARY_BREAK(event) || IS_ROTARY_LONG(event))) { + // set the date and time into RTC chip + rtcSetTime(&t); + } +#endif + +#if defined(FAI_CHOICE) + if (s_warning_result) { + s_warning_result = 0; + g_eeGeneral.fai = true; + eeDirty(EE_GENERAL); + } +#endif + + MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 2|NAVIGATION_LINE_BY_LINE, 2|NAVIGATION_LINE_BY_LINE, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO_CPUARM(LABEL(VARIO)) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) LABEL(ALARMS), 0, 0, 0, CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, CASE_MAVLINK(0) 0, 0, LABEL(TX_MODE), 0, 1/*to force edit mode*/ }); + + int sub = m_posVert; + + for (int i=0; i0) ? BLINK|INVERS : INVERS); + LcdFlags attr = (sub == k ? blink : 0); + + switch(k) { + case ITEM_SETUP_DATE: + { + lcd_putsLeft(y, STR_DATE); + LcdFlags flags = 0; + if (attr && m_posHorz < 0) { + flags |= INVERS; + lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + } + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+28, y, "-", flags); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+53, y, "-", flags); + for (uint8_t j=0; j<3; j++) { + uint8_t rowattr = (m_posHorz==j ? attr : 0); + switch (j) { + case 0: + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, t.tm_year+1900, LEFT|flags|rowattr); + if (rowattr && s_editMode>0) t.tm_year = checkIncDec(event, t.tm_year, 112, 200, 0); + break; + case 1: + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+36, y, t.tm_mon+1, LEFT|flags|rowattr|LEADING0, 2); + if (rowattr && s_editMode>0) t.tm_mon = checkIncDec(event, t.tm_mon, 0, 11, 0); + break; + case 2: + { + int16_t year = 1900 + t.tm_year; + int8_t dlim = (((((year%4==0) && (year%100!=0)) || (year%400==0)) && (t.tm_mon==1)) ? 1 : 0); + static const pm_uint8_t dmon[] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + dlim += pgm_read_byte(&dmon[t.tm_mon]); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+61, y, t.tm_mday, LEFT|flags|rowattr|LEADING0, 2); + if (rowattr && s_editMode>0) t.tm_mday = checkIncDec(event, t.tm_mday, 1, dlim, 0); + break; + } + } + } + if (attr && checkIncDec_Ret) { + g_rtcTime = gmktime(&t); // update local timestamp and get wday calculated + } + break; + } + + case ITEM_SETUP_TIME: + { + lcd_putsLeft(y, STR_TIME); + LcdFlags flags = 0; + if (attr && m_posHorz < 0) { + flags |= INVERS; + lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + } + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+15, y, ":", flags); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+35, y, ":", flags); + for (uint8_t j=0; j<3; j++) { + uint8_t rowattr = (m_posHorz==j ? attr : 0); + switch (j) { + case 0: + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN, y, t.tm_hour, flags|rowattr|LEFT|LEADING0, 2); + if (rowattr && s_editMode>0) t.tm_hour = checkIncDec(event, t.tm_hour, 0, 23, 0); + break; + case 1: + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+20, y, t.tm_min, flags|rowattr|LEFT|LEADING0, 2); + if (rowattr && s_editMode>0) t.tm_min = checkIncDec(event, t.tm_min, 0, 59, 0); + break; + case 2: + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+40, y, t.tm_sec, flags|rowattr|LEFT|LEADING0, 2); + if (rowattr && s_editMode>0) t.tm_sec = checkIncDec(event, t.tm_sec, 0, 59, 0); + break; + } + } + if (attr && checkIncDec_Ret) + g_rtcTime = gmktime(&t); // update local timestamp and get wday calculated + break; + } + +#if 0 + case ITEM_SETUP_BATT_RANGE: + { + lcd_putsLeft(y, STR_BATTERY_RANGE); + LcdFlags color = WHITE; + if (attr && m_posHorz < 0) { + color = BLACK; + lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 90, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + } + putsVolts(RADIO_SETUP_2ND_COLUMN, y, 90+g_eeGeneral.vBatMin, color|(m_posHorz==0 ? attr : 0)|LEFT|NO_UNIT); + lcd_puts(RADIO_SETUP_2ND_COLUMN+20, y, "-"); + putsVolts(RADIO_SETUP_2ND_COLUMN+28, y, 120+g_eeGeneral.vBatMax, color|(m_posHorz>0 ? attr : 0)|LEFT|NO_UNIT); + if (attr && s_editMode>0) { + if (m_posHorz==0) + CHECK_INCDEC_GENVAR(event, g_eeGeneral.vBatMin, -50, g_eeGeneral.vBatMax+29); // min=4.0V + else + CHECK_INCDEC_GENVAR(event, g_eeGeneral.vBatMax, g_eeGeneral.vBatMin-29, +40); // max=16.0V + } + break; + } +#endif + + case ITEM_SETUP_SOUND_LABEL: + lcd_putsLeft(y, STR_SOUND_LABEL); + break; + + case ITEM_SETUP_BEEP_MODE: + g_eeGeneral.beepMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_SPEAKER, STR_VBEEPMODE, g_eeGeneral.beepMode, -2, 1, attr, event); +#if defined(FRSKY) + if (attr && checkIncDec_Ret) frskySendAlarms(); +#endif + break; + + case ITEM_SETUP_SPEAKER_VOLUME: + { + lcd_putsLeft(y, STR_SPEAKER_VOLUME); + uint8_t b = g_eeGeneral.speakerVolume+VOLUME_LEVEL_DEF; + displaySlider(RADIO_SETUP_2ND_COLUMN, y, b, VOLUME_LEVEL_MAX, attr); + if (attr) { + CHECK_INCDEC_GENVAR(event, b, 0, VOLUME_LEVEL_MAX); + if (checkIncDec_Ret) { + g_eeGeneral.speakerVolume = (int8_t)b-VOLUME_LEVEL_DEF; + } + } + break; + } + + case ITEM_SETUP_BEEP_VOLUME: + SLIDER_5POS(y, g_eeGeneral.beepVolume, STR_BEEP_VOLUME, event, attr); + break; + case ITEM_SETUP_WAV_VOLUME: + SLIDER_5POS(y, g_eeGeneral.wavVolume, STR_WAV_VOLUME, event, attr); + break; + case ITEM_SETUP_BACKGROUND_VOLUME: + SLIDER_5POS(y, g_eeGeneral.backgroundVolume, STR_BG_VOLUME, event, attr); + break; + + case ITEM_SETUP_BEEP_LENGTH: + SLIDER_5POS(y, g_eeGeneral.beepLength, STR_BEEP_LENGTH, event, attr); + break; + + case ITEM_SETUP_SPEAKER_PITCH: + lcd_putsLeft( y, STR_SPKRPITCH); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.speakerPitch*15, attr|LEFT, "Hz", "+"); + if (attr) { + CHECK_INCDEC_GENVAR(event, g_eeGeneral.speakerPitch, 0, 20); + } + break; + +#if defined(VARIO) + case ITEM_SETUP_VARIO_LABEL: + lcd_putsLeft(y, STR_VARIO); + break; + case ITEM_SETUP_VARIO_VOLUME: + SLIDER_5POS(y, g_eeGeneral.varioVolume, TR_SPEAKER_VOLUME, event, attr); + break; + case ITEM_SETUP_VARIO_PITCH: + lcd_putsLeft(y, STR_PITCH_AT_ZERO); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10), attr|LEFT, "Hz"); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioPitch, -40, 40); + break; + case ITEM_SETUP_VARIO_RANGE: + lcd_putsLeft(y, STR_PITCH_AT_MAX); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10)+VARIO_FREQUENCY_RANGE+(g_eeGeneral.varioRange*10), attr|LEFT, "Hz"); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRange, -80, 80); + break; + case ITEM_SETUP_VARIO_REPEAT: + lcd_putsLeft(y, STR_REPEAT_AT_ZERO); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_REPEAT_ZERO+(g_eeGeneral.varioRepeat*10), attr|LEFT, STR_MS); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRepeat, -30, 50); + break; +#endif + +#if defined(HAPTIC) + case ITEM_SETUP_HAPTIC_LABEL: + lcd_putsLeft(y, STR_HAPTIC_LABEL); + break; + + case ITEM_SETUP_HAPTIC_MODE: + g_eeGeneral.hapticMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_MODE, STR_VBEEPMODE, g_eeGeneral.hapticMode, -2, 1, attr, event); + break; + + case ITEM_SETUP_HAPTIC_LENGTH: + SLIDER_5POS(y, g_eeGeneral.hapticLength, STR_LENGTH, event, attr); + break; + + case ITEM_SETUP_HAPTIC_STRENGTH: + SLIDER_5POS(y, g_eeGeneral.hapticStrength, STR_HAPTICSTRENGTH, event, attr); + break; +#endif + +#if 0 + case ITEM_SETUP_CONTRAST: + lcd_putsLeft(y, STR_CONTRAST); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.contrast, attr|LEFT); + if (attr) { + CHECK_INCDEC_GENVAR(event, g_eeGeneral.contrast, CONTRAST_MIN, CONTRAST_MAX); + lcdSetContrast(); + } + break; +#endif + + case ITEM_SETUP_ALARMS_LABEL: + lcd_putsLeft(y, STR_ALARMS_LABEL); + break; + + case ITEM_SETUP_BATTERY_WARNING: + lcd_putsLeft(y, STR_BATTERYWARNING); + putsVolts(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.vBatWarn, attr|LEFT); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.vBatWarn, 27, 42); // 2.7-4.2V + break; + +#if 0 + case ITEM_SETUP_MEMORY_WARNING: + { + uint8_t b = 1-g_eeGeneral.disableMemoryWarning; + g_eeGeneral.disableMemoryWarning = 1 - onoffMenuItem(b, RADIO_SETUP_2ND_COLUMN, y, STR_MEMORYWARNING, attr, event); + break; + } +#endif + + case ITEM_SETUP_ALARM_WARNING: + { + uint8_t b = 1-g_eeGeneral.disableAlarmWarning; + g_eeGeneral.disableAlarmWarning = 1 - onoffMenuItem(b, RADIO_SETUP_2ND_COLUMN, y, STR_ALARMWARNING, attr, event); + break; + } + +#if defined(PCBSKY9X) + case ITEM_SETUP_CAPACITY_WARNING: + lcd_putsLeft(y, STR_CAPAWARNING); + putsValueWithUnit(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.mAhWarn*50, UNIT_MAH, attr|LEFT) ; + if(attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.mAhWarn, 0, 100); + break; +#endif + +#if defined(PCBSKY9X) + case ITEM_SETUP_TEMPERATURE_WARNING: + lcd_putsLeft(y, STR_TEMPWARNING); + putsValueWithUnit(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.temperatureWarn, UNIT_TEMPERATURE, attr|LEFT) ; + if(attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.temperatureWarn, 0, 120); // 0 means no alarm + break; +#endif + + case ITEM_SETUP_INACTIVITY_ALARM: + lcd_putsLeft( y,STR_INACTIVITYALARM); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.inactivityTimer, attr|LEFT, "m"); + if (attr) g_eeGeneral.inactivityTimer = checkIncDec(event, g_eeGeneral.inactivityTimer, 0, 250, EE_GENERAL); //0..250minutes + break; + +#if 0 + case ITEM_SETUP_BACKLIGHT_LABEL: + lcd_putsLeft(y, STR_BACKLIGHT_LABEL); + break; + + case ITEM_SETUP_BACKLIGHT_MODE: + g_eeGeneral.backlightMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_MODE, STR_VBLMODE, g_eeGeneral.backlightMode, e_backlight_mode_off, e_backlight_mode_on, attr, event); + break; + + case ITEM_SETUP_FLASH_BEEP: + g_eeGeneral.alarmsFlash = onoffMenuItem(g_eeGeneral.alarmsFlash, RADIO_SETUP_2ND_COLUMN, y, STR_ALARM, attr, event ) ; + break; + + case ITEM_SETUP_BACKLIGHT_DELAY: + lcd_putsLeft(y, STR_BLDELAY); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.lightAutoOff*5, attr|LEFT, "s"); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.lightAutoOff, 0, 600/5); + break; + + case ITEM_SETUP_BRIGHTNESS: + lcd_putsLeft(y, STR_BRIGHTNESS); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, 100-g_eeGeneral.backlightBright, attr|LEFT) ; + if (attr) { + uint8_t b = 100 - g_eeGeneral.backlightBright; + CHECK_INCDEC_GENVAR(event, b, 0, 100); + g_eeGeneral.backlightBright = 100 - b; + } + break; + + case ITEM_SETUP_BACKLIGHT_COLOR: + lcd_putsLeft(y, STR_BLCOLOR); + displaySlider(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.backlightColor, 20, attr); + if (attr) g_eeGeneral.backlightColor = checkIncDec(event, g_eeGeneral.backlightColor, 0, 20, EE_GENERAL | NO_INCDEC_MARKS); + break; +#endif + +#if 0 + case ITEM_SETUP_DISABLE_SPLASH: + { + lcd_putsLeft(y, STR_SPLASHSCREEN); + if (SPLASH_NEEDED()) { + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, SPLASH_TIMEOUT/100, attr|LEFT, "s"); + } + else { + lcd_putsiAtt(RADIO_SETUP_2ND_COLUMN, y, STR_MMMINV, 0, attr); // TODO define + } + if (attr) g_eeGeneral.splashMode = -checkIncDecGen(event, -g_eeGeneral.splashMode, -3, 4); + break; + } +#endif + + case ITEM_SETUP_TIMEZONE: + lcd_putsLeft(y, STR_TIMEZONE); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.timezone, attr|LEFT); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.timezone, -12, 12); + break; + + case ITEM_SETUP_GPSFORMAT: + g_eeGeneral.gpsFormat = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_GPSCOORD, STR_GPSFORMAT, g_eeGeneral.gpsFormat, 0, 1, attr, event); + break; + + case ITEM_SETUP_COUNTRYCODE: + g_eeGeneral.countryCode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_COUNTRYCODE, STR_COUNTRYCODES, g_eeGeneral.countryCode, 0, 2, attr, event); + break; + + case ITEM_SETUP_LANGUAGE: + lcd_putsLeft(y, STR_VOICELANG); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, currentLanguagePack->name, attr); + if (attr) { + currentLanguagePackIdx = checkIncDec(event, currentLanguagePackIdx, 0, DIM(languagePacks)-2, EE_GENERAL); + if (checkIncDec_Ret) { + currentLanguagePack = languagePacks[currentLanguagePackIdx]; + strncpy(g_eeGeneral.ttsLanguage, currentLanguagePack->id, 2); + } + } + break; + + case ITEM_SETUP_IMPERIAL: + g_eeGeneral.imperial = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_UNITSSYSTEM, STR_VUNITSSYSTEM, g_eeGeneral.imperial, 0, 1, attr, event); + break; + +#if 0 + case ITEM_SETUP_FAI: + onoffMenuItem(g_eeGeneral.fai, RADIO_SETUP_2ND_COLUMN, y, PSTR("FAI Mode"), attr, event); + if (attr && checkIncDec_Ret) { + if (g_eeGeneral.fai) + POPUP_WARNING(PSTR("FAI\001mode blocked!")); + else + POPUP_CONFIRMATION(PSTR("FAI mode?")); + } + break; +#endif + +#if defined(MAVLINK) + case ITEM_MAVLINK_BAUD: + g_eeGeneral.mavbaud = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_MAVLINK_BAUD_LABEL, STR_MAVLINK_BAUDS, g_eeGeneral.mavbaud, 0, 7, attr, event); + break; +#endif + + case ITEM_SETUP_SWITCHES_DELAY: + lcd_putsLeft(y, STR_SWITCHES_DELAY); + lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, 10*SWITCHES_DELAY(), attr|LEFT, STR_MS); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.switchesDelay, -15, +15); + break; + + case ITEM_SETUP_RX_CHANNEL_ORD: + { + lcd_putsLeft(y, STR_RXCHANNELORD); // RAET->AETR + char s[5]; + for (uint8_t i=0; i<4; i++) { + s[i] = STR_RETA123[channel_order(i+1)]; + } + s[4] = '\0'; + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, s, attr); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.templateSetup, 0, 23); + break; + } + + case ITEM_SETUP_STICK_MODE_LABELS: + lcd_putsLeft(y, NO_INDENT(STR_MODE)); + for (int i=0; i<4; i++) { + // lcdDrawBitmap((6+4*i)*12, y, ...); + } + break; + + case ITEM_SETUP_STICK_MODE: + { + char s[2] = " "; + s[0] = '1'+g_eeGeneral.stickMode; + lcd_putsAtt(INDENT_WIDTH, y, s, attr); + for (uint8_t i=0; i<4; i++) { + putsMixerSource((6+4*i)*12, y, MIXSRC_Rud + pgm_read_byte(modn12x3 + 4*g_eeGeneral.stickMode + i)); + } + if (attr && s_editMode>0) { + CHECK_INCDEC_GENVAR(event, g_eeGeneral.stickMode, 0, 3); + } + else if (stickMode != g_eeGeneral.stickMode) { + pausePulses(); + stickMode = g_eeGeneral.stickMode; + checkTHR(); + resumePulses(); + clearKeyEvents(); + } + break; + } + } + } +} diff --git a/radio/src/gui/Horus/menu_general_trainer.cpp b/radio/src/gui/Horus/menu_general_trainer.cpp new file mode 100644 index 000000000..6fdfbc63b --- /dev/null +++ b/radio/src/gui/Horus/menu_general_trainer.cpp @@ -0,0 +1,121 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define TRAINER_COLUMN_WIDTH 60 +#define TRAINER_COLUMN_1 MENU_TITLE_LEFT+80 +#define TRAINER_COLUMN_2 TRAINER_COLUMN_1+TRAINER_COLUMN_WIDTH +#define TRAINER_COLUMN_3 TRAINER_COLUMN_2+TRAINER_COLUMN_WIDTH + +#define TRAINER_CALIB_POS 12 + +void menuGeneralTrainer(evt_t event) +{ + uint8_t y; + bool slave = SLAVE_MODE(); + + MENU(STR_MENUTRAINER, menuTabGeneral, e_Trainer, (slave ? 0 : 6), DEFAULT_SCROLLBAR_X, { 2, 2, 2, 2, 0/*, 0*/ }); + + if (slave) { + lcd_putsCenter(5*FH, STR_SLAVE, TEXT_COLOR); + return; + } + + uint8_t attr; + uint8_t blink = ((s_editMode>0) ? BLINK|INVERS : INVERS); + + lcd_putsAtt(TRAINER_COLUMN_1, MENU_HEADER_HEIGHT+1, "Mode", HEADER_COLOR); + lcd_putsAtt(TRAINER_COLUMN_2, MENU_HEADER_HEIGHT+1, "Weight", HEADER_COLOR); + lcd_putsAtt(TRAINER_COLUMN_3, MENU_HEADER_HEIGHT+1, "Source", HEADER_COLOR); + + y = MENU_CONTENT_TOP + FH; + int sub = m_posVert + 1; + + for (int i=1; i<=NUM_STICKS; i++) { + uint8_t chan = channel_order(i); + volatile TrainerMix *td = &g_eeGeneral.trainer.mix[chan-1]; + + putsMixerSource(MENU_TITLE_LEFT, y, MIXSRC_Rud-1+chan, ((sub==i && CURSOR_ON_LINE()) ? INVERS : 0)); + + for (int j=0; j<3; j++) { + + attr = ((sub==i && m_posHorz==j) ? blink : 0); + + switch(j) { + case 0: + lcd_putsiAtt(TRAINER_COLUMN_1, y, STR_TRNMODE, td->mode, attr); + if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->mode, 0, 2); + break; + + case 1: + lcd_outdezAtt(TRAINER_COLUMN_2, y, td->studWeight, LEFT|attr, "%"); + if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->studWeight, -125, 125); + break; + + case 2: + lcd_putsiAtt(TRAINER_COLUMN_3, y, STR_TRNCHN, td->srcChn, attr); + if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->srcChn, 0, 3); + break; + } + } + y += FH; + } + + attr = (sub==5) ? blink : 0; + lcd_putsLeft(MENU_CONTENT_TOP + 5*FH, STR_MULTIPLIER); + lcd_outdezAtt(TRAINER_COLUMN_1, MENU_CONTENT_TOP + 5*FH, g_eeGeneral.PPM_Multiplier+10, LEFT|attr|PREC1); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.PPM_Multiplier, -10, 40); + + attr = (sub==6) ? INVERS : 0; + if (attr) s_editMode = 0; + lcd_putsAtt(MENU_TITLE_LEFT, MENU_CONTENT_TOP + 6*FH, STR_CAL, attr); + for (int i=0; i<4; i++) { +#if defined (PPM_UNIT_PERCENT_PREC1) + lcd_outdezAtt(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])*2, LEFT|PREC1); +#else + lcd_outdezAtt(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])/5, LEFT); +#endif + } + + if (attr) { + if (event==EVT_KEY_LONG(KEY_ENTER)){ + memcpy(g_eeGeneral.trainer.calib, ppmInput, sizeof(g_eeGeneral.trainer.calib)); + eeDirty(EE_GENERAL); + AUDIO_WARNING1(); + } + } +} diff --git a/radio/src/gui/Horus/menu_general_version.cpp b/radio/src/gui/Horus/menu_general_version.cpp new file mode 100644 index 000000000..7b01ba41e --- /dev/null +++ b/radio/src/gui/Horus/menu_general_version.cpp @@ -0,0 +1,53 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +void menuGeneralVersion(evt_t event) +{ + SIMPLE_MENU(STR_MENUVERSION, menuTabGeneral, e_Vers, 1, DEFAULT_SCROLLBAR_X); + + lcd_putsLeft(MENU_CONTENT_TOP + FH, vers_stamp); + lcd_putsLeft(MENU_CONTENT_TOP + 2*FH, date_stamp); + lcd_putsLeft(MENU_CONTENT_TOP + 3*FH, time_stamp); + lcd_putsLeft(MENU_CONTENT_TOP + 4*FH, eeprom_stamp); + + // TODO EEPROM erase + backup + // lcd_putsCenter(MENU_HEADER_HEIGHT+6*FH, STR_EEBACKUP); + // if (event == EVT_KEY_LONG(KEY_ENTER)) { + // backupEeprom(); + // } +} diff --git a/radio/src/gui/Horus/menu_model.cpp b/radio/src/gui/Horus/menu_model.cpp new file mode 100644 index 000000000..24f910706 --- /dev/null +++ b/radio/src/gui/Horus/menu_model.cpp @@ -0,0 +1,203 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +// TODO elsewhere! +#define WCHART 64 +#define X0 (LCD_W-WCHART-8) +#define Y0 118 + +enum EnumTabModel { + // e_ModelSelect, + e_ModelSetup, + CASE_HELI(e_Heli) + CASE_FLIGHT_MODES(e_FlightModesAll) + e_InputsAll, + e_MixAll, + e_Limits, + CASE_CURVES(e_CurvesAll) + CASE_GVARS(e_GVars) + e_LogicalSwitches, + e_CustomFunctions, +#if defined(LUA_MODEL_SCRIPTS) + e_CustomScripts, +#endif + CASE_FRSKY(e_Telemetry) +}; + +void menuModelSelect(evt_t event); +void menuModelSetup(evt_t event); +void menuModelHeli(evt_t event); +void menuModelFlightModesAll(evt_t event); +void menuModelExposAll(evt_t event); +void menuModelMixAll(evt_t event); +void menuModelLimits(evt_t event); +void menuModelCurvesAll(evt_t event); +void menuModelCurveOne(evt_t event); +void menuModelGVars(evt_t event); +void menuModelLogicalSwitches(evt_t event); +void menuModelCustomFunctions(evt_t event); +void menuModelCustomScripts(evt_t event); +void menuModelTelemetry(evt_t event); +void menuModelExpoOne(evt_t event); + +extern uint8_t s_curveChan; + +#define FlightModesType uint16_t + +void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t attr); + +#define MIXES_2ND_COLUMN 80 + +uint8_t editDelay(const coord_t x, const coord_t y, const evt_t event, const uint8_t attr, const pm_char *str, uint8_t delay) +{ + lcd_putsAtt(x+MENU_TITLE_LEFT, y, str, TEXT_COLOR); + lcd_outdezAtt(x+MIXES_2ND_COLUMN, y, (10/DELAY_STEP)*delay, attr|PREC1|LEFT); + if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, delay, DELAY_MAX); + return delay; +} +#define EDIT_DELAY(x, y, event, attr, str, delay) editDelay(x, y, event, attr, str, delay) + +const MenuFuncP_PROGMEM menuTabModel[] PROGMEM = { +// menuModelSelect, + menuModelSetup, + CASE_HELI(menuModelHeli) + CASE_FLIGHT_MODES(menuModelFlightModesAll) + menuModelExposAll, + menuModelMixAll, + menuModelLimits, + CASE_CURVES(menuModelCurvesAll) +#if defined(GVARS) && defined(FLIGHT_MODES) + CASE_GVARS(menuModelGVars) +#endif + menuModelLogicalSwitches, + menuModelCustomFunctions, +#if defined(LUA_MODEL_SCRIPTS) + menuModelCustomScripts, +#endif + CASE_FRSKY(menuModelTelemetry) + CASE_MAVLINK(menuTelemetryMavlinkSetup) + CASE_TEMPLATES(menuModelTemplates) +}; + +#define COPY_MODE 1 +#define MOVE_MODE 2 +static uint8_t s_copyMode = 0; +static int8_t s_copySrcRow; +static int8_t s_copyTgtOfs; + +static uint8_t editNameCursorPos = 0; + +void editName(coord_t x, coord_t y, char *name, uint8_t size, evt_t event, uint8_t active) +{ + uint8_t mode = 0; + if (active && s_editMode <= 0) { + mode = INVERS; + } + + if (!active || s_editMode <= 0) { + if (zlen(name, size) == 0) { + char tmp[] = "---"; + lcd_putsnAtt(x, y, tmp, size, mode); + } + else { + lcd_putsnAtt(x, y, name, size, ZCHAR | mode); + } + } + + if (active) { + uint8_t cur = editNameCursorPos; + if (s_editMode > 0) { + int8_t c = name[cur]; + int8_t v = c; + + if (IS_ROTARY_RIGHT(event) || IS_ROTARY_LEFT(event) || event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_FIRST(KEY_UP) + || event==EVT_KEY_REPT(KEY_DOWN) || event==EVT_KEY_REPT(KEY_UP)) { + v = checkIncDec(event, abs(v), 0, ZCHAR_MAX, 0); + if (c <= 0) v = -v; + } + + switch (event) { + case EVT_ROTARY_BREAK: + if (s_editMode == EDIT_MODIFY_FIELD) { + s_editMode = EDIT_MODIFY_STRING; + cur = 0; + } + else if (cur=-26 && v<=26) { + v = -v; // toggle case + if (event==EVT_KEY_LONG(KEY_LEFT)) + killEvents(KEY_LEFT); + } + break; + } + + if (c != v) { + name[cur] = v; + eeDirty(g_menuPos[0] == 0 ? EE_MODEL : EE_GENERAL); + } + + lcd_putsnAtt(x, y, name, size, ZCHAR | mode); + coord_t w = (editNameCursorPos == 0 ? 0 : getTextWidth(name, editNameCursorPos, ZCHAR)); + char s[] = { idx2char(v), '\0' }; + lcdDrawFilledRect(x+w, y-1, getTextWidth(s, 1)-1, 12, TEXT_INVERTED_BGCOLOR); + lcd_putsAtt(x+w, y, s, TEXT_INVERTED_COLOR); + } + else { + cur = 0; + } + editNameCursorPos = cur; + } +} + +void editSingleName(coord_t x, coord_t y, const pm_char *label, char *name, uint8_t size, evt_t event, uint8_t active) +{ + lcd_putsLeft(y, label); + editName(x, y, name, size, event, active); +} + +static uint8_t s_currIdx; diff --git a/radio/src/gui/Horus/menu_model_curves.cpp b/radio/src/gui/Horus/menu_model_curves.cpp new file mode 100644 index 000000000..6a407f670 --- /dev/null +++ b/radio/src/gui/Horus/menu_model_curves.cpp @@ -0,0 +1,325 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +uint8_t s_curveChan; + +int curveFn(int x) +{ + return applyCustomCurve(x, s_curveChan); +} + +struct point_t { + coord_t x; + coord_t y; +}; + +point_t getPoint(uint8_t i) +{ + point_t result = {0, 0}; + CurveInfo &crv = g_model.curves[s_curveChan]; + int8_t *points = curveAddress(s_curveChan); + bool custom = (crv.type == CURVE_TYPE_CUSTOM); + uint8_t count = 5+crv.points; + if (i < count) { + result.x = X0-1-WCHART+i*WCHART*2/(count-1); + result.y = Y0 - (points[i]) * (WCHART-1) / 100; + if (custom && i>0 && i g_model.points + sizeof(g_model.points)) { + AUDIO_WARNING2(); + return false; + } + + int8_t *nextCrv = curveAddress(index+1); + memmove(nextCrv+shift, nextCrv, 5*(MAX_CURVES-index-1)+curveEnd[MAX_CURVES-1]-curveEnd[index]); + if (shift < 0) memclear(&g_model.points[NUM_POINTS-1] + shift, -shift); + while (index0 ? INVERS|BLINK : INVERS) : 0); + lcd_putsLeft(MENU_CONTENT_TOP + 2*FH, "Type"); + lcd_putsiAtt(MENU_TITLE_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 3*FH, STR_CURVE_TYPES, crv.type, attr); + if (attr) { + uint8_t newType = checkIncDecModelZero(event, crv.type, CURVE_TYPE_LAST); + if (newType != crv.type) { + for (int i=1; i<4+crv.points; i++) + points[i] = calcRESXto100(applyCustomCurve(calc100toRESX(-100 + i*200/(4+crv.points)), s_curveChan)); + moveCurve(s_curveChan, checkIncDec_Ret > 0 ? 3+crv.points : -3-crv.points); + if (newType == CURVE_TYPE_CUSTOM) { + for (int i=0; i<3+crv.points; i++) + points[5+crv.points+i] = -100 + ((i+1)*200) / (4+crv.points); + } + crv.type = newType; + } + } + + attr = (m_posVert==2 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); + lcd_putsLeft(MENU_CONTENT_TOP + 4*FH, STR_COUNT); + lcd_outdezAtt(MENU_TITLE_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 5*FH, 5+crv.points, LEFT|attr, STR_PTS); + if (attr) { + int count = checkIncDecModel(event, crv.points, -3, 12); // 2pts - 17pts + if (checkIncDec_Ret) { + int newPoints[MAX_POINTS]; + newPoints[0] = points[0]; + newPoints[4+count] = points[4+crv.points]; + for (int i=1; i<4+count; i++) + newPoints[i] = calcRESXto100(applyCustomCurve(calc100toRESX(-100 + (i*200) / (4+count)), s_curveChan)); + moveCurve(s_curveChan, checkIncDec_Ret*(crv.type==CURVE_TYPE_CUSTOM?2:1)); + for (int i=0; i<5+count; i++) { + points[i] = newPoints[i]; + if (crv.type == CURVE_TYPE_CUSTOM && i!=0 && i!=4+count) + points[5+count+i-1] = -100 + (i*200) / (4+count); + } + crv.points = count; + } + } + + lcd_putsLeft(MENU_CONTENT_TOP + 6*FH, STR_SMOOTH); + lcdDrawCheckBox(70, MENU_CONTENT_TOP + 6*FH, crv.smooth, m_posVert==3 ? INVERS : 0); + if (m_posVert==3) crv.smooth = checkIncDecModel(event, crv.smooth, 0, 1); + + switch(event) { + case EVT_ENTRY: + pointsOfs = 0; + break; + case EVT_KEY_LONG(KEY_ENTER): + if (m_posVert > 1) { + killEvents(event); + MENU_ADD_ITEM(STR_CURVE_PRESET); + MENU_ADD_ITEM(STR_MIRROR); + MENU_ADD_ITEM(STR_CLEAR); + menuHandler = onCurveOneMenu; + } + break; + case EVT_KEY_LONG(KEY_MENU): + pushMenu(menuChannelsView); + killEvents(event); + } + + if (m_posVert >= 0) { + DrawCurve(6); + } + + lcd_putsAtt(115, MENU_FOOTER_TOP, "X", HEADER_COLOR); + lcd_putsAtt(145, MENU_FOOTER_TOP, "Y", HEADER_COLOR); + + coord_t posY = MENU_HEADER_HEIGHT+1; + attr = (s_editMode > 0 ? INVERS|BLINK : INVERS); + for (int i=0; i<5+crv.points; i++) { + point_t point = getPoint(i); + uint8_t selectionMode = 0; + if (crv.type==CURVE_TYPE_CUSTOM) { + if (m_posVert==4+2*i || (i==5+crv.points-1 && m_posVert==4+5+crv.points+5+crv.points-2-1)) + selectionMode = 2; + else if (i>0 && m_posVert==3+2*i) + selectionMode = 1; + } + else if (m_posVert == 4+i) { + selectionMode = 2; + } + + if (i>=pointsOfs && i0 && i<5+crv.points-1) x = points[5+crv.points+i-1]; + lcd_outdezAtt(100, posY, i+1, LEFT); + lcd_outdezAtt(115, posY, x, LEFT|(selectionMode==1?attr:0)); + lcd_outdezAtt(145, posY, points[i], LEFT|(selectionMode==2?attr:0)); + posY += FH; + } + + if (selectionMode > 0) { + // do selection square + lcdDrawFilledRect(point.x-7, point.y-2, 5, 5, TEXT_INVERTED_BGCOLOR); + if (s_editMode > 0) { + if (selectionMode == 1) + CHECK_INCDEC_MODELVAR(event, points[5+crv.points+i-1], i==1 ? -100 : points[5+crv.points+i-2], i==5+crv.points-2 ? 100 : points[5+crv.points+i]); // edit X + else if (selectionMode == 2) + CHECK_INCDEC_MODELVAR(event, points[i], -100, 100); + } + if (i < pointsOfs) + pointsOfs = i; + else if (i > pointsOfs+NUM_BODY_LINES-1) + pointsOfs = i-NUM_BODY_LINES+1; + } + } + + if (5+crv.points > NUM_BODY_LINES) { + displayScrollbar(167, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); + } +} + +void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t attr) +{ + lcd_putsiAtt(x, y, "\004DiffExpoFuncCstm", curve.type, (m_posHorz==0 ? attr : 0)); + if (attr && m_posHorz==0) { + CHECK_INCDEC_MODELVAR_ZERO(event, curve.type, CURVE_REF_CUSTOM); + if (checkIncDec_Ret) curve.value = 0; + } + switch (curve.type) { + case CURVE_REF_DIFF: + case CURVE_REF_EXPO: + curve.value = GVAR_MENU_ITEM(x+40, y, curve.value, -100, 100, m_posHorz==1 ? LEFT|attr : LEFT, 0, event); + break; + case CURVE_REF_FUNC: + lcd_putsiAtt(x+40, y, STR_VCURVEFUNC, curve.value, (m_posHorz==1 ? attr : 0)); + if (attr && m_posHorz==1) CHECK_INCDEC_MODELVAR_ZERO(event, curve.value, CURVE_BASE-1); + break; + case CURVE_REF_CUSTOM: + putsCurve(x+40, y, curve.value, (m_posHorz==1 ? attr : 0)); + if (attr && m_posHorz==1) { + if (event==EVT_KEY_LONG(KEY_ENTER) && curve.value!=0) { + s_curveChan = (curve.value<0 ? -curve.value-1 : curve.value-1); + pushMenu(menuModelCurveOne); + } + else { + CHECK_INCDEC_MODELVAR(event, curve.value, -MAX_CURVES, MAX_CURVES); + } + } + break; + } +} + +void menuModelCurvesAll(evt_t event) +{ + SIMPLE_MENU(STR_MENUCURVES, menuTabModel, e_CurvesAll, MAX_CURVES, DEFAULT_SCROLLBAR_X); + + int8_t sub = m_posVert; + + switch (event) { + case EVT_KEY_BREAK(KEY_ENTER): + if (!READ_ONLY()) { + s_curveChan = sub; + pushMenu(menuModelCurveOne); + } + break; + } + + for (int i=0; i= 0) { + s_curveChan = sub; + DrawCurve(23); + } +} diff --git a/radio/src/gui/Horus/menu_model_custom_functions.cpp b/radio/src/gui/Horus/menu_model_custom_functions.cpp new file mode 100644 index 000000000..a4b91c558 --- /dev/null +++ b/radio/src/gui/Horus/menu_model_custom_functions.cpp @@ -0,0 +1,372 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define MODEL_CUSTOM_FUNC_1ST_COLUMN 50 +#define MODEL_CUSTOM_FUNC_2ND_COLUMN 100 +#define MODEL_CUSTOM_FUNC_2ND_COLUMN_EXT 150 +#define MODEL_CUSTOM_FUNC_3RD_COLUMN 200 +#define MODEL_CUSTOM_FUNC_4TH_COLUMN 286 +#define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF 290 + +void onCustomFunctionsFileSelectionMenu(const char *result) +{ + int sub = m_posVert; + CustomFunctionData * cf = &g_model.customFn[sub]; + uint8_t func = CFN_FUNC(cf); + + if (result == STR_UPDATE_LIST) { + char directory[256]; + if (func == FUNC_PLAY_SCRIPT) { + strcpy(directory, SCRIPTS_FUNCS_PATH); + } + else { + strcpy(directory, SOUNDS_PATH); + strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); + } + if (!listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cf->play.name), NULL)) { + POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); + s_menu_flags = 0; + } + } + else { + // The user choosed a file in the list + memcpy(cf->play.name, result, sizeof(cf->play.name)); + eeDirty(EE_MODEL); + if (func == FUNC_PLAY_SCRIPT) { + LUA_LOAD_MODEL_SCRIPTS(); + } + } +} + +void onCustomFunctionsMenu(const char *result) +{ + int sub = m_posVert; + CustomFunctionData * cfn; + uint8_t eeFlags; + + if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) { + cfn = &g_model.customFn[sub]; + eeFlags = EE_MODEL; + } + else { + cfn = &g_eeGeneral.customFn[sub]; + eeFlags = EE_GENERAL; + } + + if (result == STR_COPY) { + clipboard.type = CLIPBOARD_TYPE_CUSTOM_FUNCTION; + clipboard.data.cfn = *cfn; + } + else if (result == STR_PASTE) { + *cfn = clipboard.data.cfn; + eeDirty(eeFlags); + } + else if (result == STR_CLEAR) { + memset(cfn, 0, sizeof(CustomFunctionData)); + eeDirty(eeFlags); + } + else if (result == STR_INSERT) { + memmove(cfn+1, cfn, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); + memset(cfn, 0, sizeof(CustomFunctionData)); + eeDirty(eeFlags); + } + else if (result == STR_DELETE) { + memmove(cfn, cfn+1, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); + memset(&g_model.customFn[NUM_CFN-1], 0, sizeof(CustomFunctionData)); + eeDirty(eeFlags); + } +} + +void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext) +{ + uint32_t sub = m_posVert; + + uint8_t eeFlags = (functions == g_model.customFn) ? EE_MODEL : EE_GENERAL; + + if (m_posHorz<0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) { + killEvents(event); + CustomFunctionData *cfn = &functions[sub]; + if (!CFN_EMPTY(cfn)) + MENU_ADD_ITEM(STR_COPY); + if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_FUNCTION) + MENU_ADD_ITEM(STR_PASTE); + if (!CFN_EMPTY(cfn) && CFN_EMPTY(&functions[NUM_CFN-1])) + MENU_ADD_ITEM(STR_INSERT); + if (!CFN_EMPTY(cfn)) + MENU_ADD_ITEM(STR_CLEAR); + for (int i=sub+1; i0) ? BLINK|INVERS : INVERS) : 0); + LcdFlags active = (attr && s_editMode>0); + switch (j) { + case 0: + putsSwitches(MODEL_CUSTOM_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | ((functionsContext.activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); + if (active || AUTOSWITCH_ENTER_LONG()) CHECK_INCDEC_SWITCH(event, CFN_SWITCH(cfn), SWSRC_FIRST, SWSRC_LAST, eeFlags, isSwitchAvailableInCustomFunctions); + break; + + case 1: + if (CFN_SWITCH(cfn)) { + lcd_putsiAtt(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); + if (active) { + func = CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); + if (checkIncDec_Ret) CFN_RESET(cfn); + } + } + else { + j = 4; // skip other fields + if (sub==k && m_posHorz > 0) { + REPEAT_LAST_CURSOR_MOVE(); + } + } + break; + + case 2: + { + int8_t maxParam = NUM_CHNOUT-1; +#if defined(OVERRIDE_CHANNEL_FUNCTION) + if (func == FUNC_OVERRIDE_CHANNEL) { + putsChn(MODEL_CUSTOM_FUNC_2ND_COLUMN_EXT, y, CFN_CH_INDEX(cfn)+1, attr); + } + else +#endif + if (func == FUNC_TRAINER) { + maxParam = 4; + putsMixerSource(MODEL_CUSTOM_FUNC_2ND_COLUMN_EXT, y, CFN_CH_INDEX(cfn)==0 ? 0 : MIXSRC_Rud+CFN_CH_INDEX(cfn)-1, attr); + } +#if defined(GVARS) + else if (func == FUNC_ADJUST_GVAR) { + maxParam = MAX_GVARS-1; + putsStrIdx(MODEL_CUSTOM_FUNC_2ND_COLUMN_EXT, y, STR_GV, CFN_GVAR_INDEX(cfn)+1, attr); + if (active) CFN_GVAR_INDEX(cfn) = checkIncDec(event, CFN_GVAR_INDEX(cfn), 0, maxParam, eeFlags); + break; + } +#endif + else if (func == FUNC_SET_TIMER) { + maxParam = MAX_TIMERS-1; + putsStrIdx(MODEL_CUSTOM_FUNC_2ND_COLUMN_EXT, y, STR_TIMER, CFN_TIMER_INDEX(cfn)+1, attr); + if (active) CFN_TIMER_INDEX(cfn) = checkIncDec(event, CFN_TIMER_INDEX(cfn), 0, maxParam, eeFlags); + break; + } + else if (attr) { + REPEAT_LAST_CURSOR_MOVE(); + } + if (active) CHECK_INCDEC_MODELVAR_ZERO(event, CFN_CH_INDEX(cfn), maxParam); + break; + } + + case 3: + { + INCDEC_DECLARE_VARS(eeFlags); + int16_t val_displayed = CFN_PARAM(cfn); + int16_t val_min = 0; + int16_t val_max = 255; + if (func == FUNC_RESET) { + val_max = FUNC_RESET_PARAM_LAST; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, CFN_PARAM(cfn), attr); + } +#if defined(OVERRIDE_CHANNEL_FUNCTION) + else if (func == FUNC_OVERRIDE_CHANNEL) { + val_min = -LIMIT_EXT_PERCENT; val_max = +LIMIT_EXT_PERCENT; + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + } +#endif +#if defined(DANGEROUS_MODULE_FUNCTIONS) + else if (func >= FUNC_RANGECHECK && func <= FUNC_MODULE_OFF) { + val_max = NUM_MODULES-1; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); + } +#endif + else if (func == FUNC_SET_TIMER) { + val_max = 59*60+59; + putsTimer(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + } + else if (func == FUNC_PLAY_SOUND) { + val_max = AU_FRSKY_LAST-AU_FRSKY_FIRST-1; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); + } +#if defined(HAPTIC) + else if (func == FUNC_HAPTIC) { + val_max = 3; + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + } +#endif +#if defined(SDCARD) + else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT) { + coord_t x = MODEL_CUSTOM_FUNC_3RD_COLUMN; + if (ZEXIST(cfn->play.name)) + lcd_putsnAtt(x, y, cfn->play.name, sizeof(cfn->play.name), attr); + else + lcd_putsiAtt(x, y, STR_VCSWFUNC, 0, attr); + if (active && event==EVT_KEY_BREAK(KEY_ENTER)) { + s_editMode = 0; + char directory[256]; + if (func==FUNC_PLAY_SCRIPT) { + strcpy(directory, SCRIPTS_FUNCS_PATH); + } + else { + strcpy(directory, SOUNDS_PATH); + strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); + } + if (listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { + menuHandler = onCustomFunctionsFileSelectionMenu; + } + else { + POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); + s_menu_flags = 0; + } + } + break; + } + else if (func == FUNC_PLAY_VALUE) { + val_max = MIXSRC_LAST_TELEM; + putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); + INCDEC_ENABLE_CHECK(isSourceAvailable); + } +#endif + else if (func == FUNC_VOLUME) { + val_max = MIXSRC_LAST_CH; + putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); + INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE); + INCDEC_ENABLE_CHECK(isSourceAvailable); + } + else if (func == FUNC_LOGS) { + if (val_displayed) { + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|PREC1|LEFT, "s"); + } + else { + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); + } + } +#if defined(REVPLUS) + else if (func == FUNC_BACKLIGHT) { + displaySlider(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, CFN_PARAM(cfn), 100, attr); + INCDEC_SET_FLAG(eeFlags | NO_INCDEC_MARKS); + val_min = 0; + val_max = 100; + } +#endif +#if defined(GVARS) + else if (func == FUNC_ADJUST_GVAR) { + switch (CFN_GVAR_MODE(cfn)) { + case FUNC_ADJUST_GVAR_CONSTANT: + val_displayed = (int16_t)CFN_PARAM(cfn); + val_min = -CFN_GVAR_CST_MAX; val_max = +CFN_GVAR_CST_MAX; + lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + break; + case FUNC_ADJUST_GVAR_SOURCE: + val_max = MIXSRC_LAST_CH; + putsMixerSource(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr); + INCDEC_SET_FLAG(eeFlags | INCDEC_SOURCE); + INCDEC_ENABLE_CHECK(isSourceAvailable); + break; + case FUNC_ADJUST_GVAR_GVAR: + val_max = MAX_GVARS-1; + putsStrIdx(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_GV, val_displayed+1, attr); + break; + default: // FUNC_ADJUST_GVAR_INC + val_max = 1; + lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); + break; + } + + if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + s_editMode = !s_editMode; + active = true; + CFN_GVAR_MODE(cfn) += 1; + CFN_GVAR_MODE(cfn) &= 0x03; + val_displayed = 0; + } + } +#endif + else if (attr) { + REPEAT_LAST_CURSOR_MOVE(); + } + + if (active) { + CFN_PARAM(cfn) = CHECK_INCDEC_PARAM(event, val_displayed, val_min, val_max); + } + break; + } + + case 4: + if (HAS_ENABLE_PARAM(func)) { + lcdDrawCheckBox(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); + if (active) CFN_ACTIVE(cfn) = checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); + } + else if (HAS_REPEAT_PARAM(func)) { + if (CFN_PLAY_REPEAT(cfn) == 0) { + lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); + } + else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { + lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, "!1x", attr); + } + else { + lcd_outdezAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+12, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr, "s"); + } + if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); + } + else if (attr) { + REPEAT_LAST_CURSOR_MOVE(); + } + break; + } + } + } +} + +void menuModelCustomFunctions(evt_t event) +{ + MENU(STR_MENUCUSTOMFUNC, menuTabModel, e_CustomFunctions, NUM_CFN, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|4/*repeated*/ }); + return menuCustomFunctions(event, g_model.customFn, modelFunctionsContext); +} diff --git a/radio/src/gui/Horus/menu_model_flightmodes.cpp b/radio/src/gui/Horus/menu_model_flightmodes.cpp new file mode 100644 index 000000000..508407830 --- /dev/null +++ b/radio/src/gui/Horus/menu_model_flightmodes.cpp @@ -0,0 +1,149 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +enum FlightModesItems { + ITEM_FLIGHT_MODES_NAME, + ITEM_FLIGHT_MODES_SWITCH, + ITEM_FLIGHT_MODES_TRIM_RUD, + ITEM_FLIGHT_MODES_TRIM_ELE, + ITEM_FLIGHT_MODES_TRIM_THR, + ITEM_FLIGHT_MODES_TRIM_AIL, + ITEM_FLIGHT_MODES_FADE_IN, + ITEM_FLIGHT_MODES_FADE_OUT, + ITEM_FLIGHT_MODES_COUNT, + ITEM_FLIGHT_MODES_LAST = ITEM_FLIGHT_MODES_COUNT-1 +}; + +bool isTrimModeAvailable(int mode) +{ + return (mode < 0 || (mode%2) == 0 || (mode/2) != m_posVert); +} + +void menuModelFlightModesAll(evt_t event) +{ + MENU(STR_MENUFLIGHTPHASES, menuTabModel, e_FlightModesAll, MAX_FLIGHT_MODES+1, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|(ITEM_FLIGHT_MODES_LAST-1), NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, 0}); + + int sub = m_posVert; + + horzpos_t posHorz = m_posHorz; + if (sub==0 && posHorz > 0) { posHorz += 1; } + + if (sub=0) { + displayColumnHeader(STR_PHASES_HEADERS, posHorz); + } + + for (int i=0; i0) ? BLINK|INVERS : INVERS) : 0); + LcdFlags active = (attr && s_editMode>0) ; + switch (j) { + case ITEM_FLIGHT_MODES_NAME: + editName(45, y, p->name, sizeof(p->name), event, attr); + break; + + case ITEM_FLIGHT_MODES_SWITCH: + if (k == 0) + lcd_puts(145, y, "N/A"); + else + putsSwitches(145, y, p->swtch, attr); + if (active) CHECK_INCDEC_MODELSWITCH(event, p->swtch, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); + break; + + case ITEM_FLIGHT_MODES_TRIM_RUD: + case ITEM_FLIGHT_MODES_TRIM_ELE: + case ITEM_FLIGHT_MODES_TRIM_THR: + case ITEM_FLIGHT_MODES_TRIM_AIL: + { + uint8_t t = j-ITEM_FLIGHT_MODES_TRIM_RUD; + putsTrimMode(155+j*15, y, k, t, attr); + if (active) { + trim_t & v = p->trim[t]; + v.mode = checkIncDec(event, v.mode==TRIM_MODE_NONE ? -1 : v.mode, -1, k==0 ? 0 : 2*MAX_FLIGHT_MODES-1, EE_MODEL, isTrimModeAvailable); + } + break; + } + + case ITEM_FLIGHT_MODES_FADE_IN: + lcd_outdezAtt(272, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); + if (active) p->fadeIn = checkIncDec(event, p->fadeIn, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); + break; + + case ITEM_FLIGHT_MODES_FADE_OUT: + lcd_outdezAtt(300, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); + if (active) p->fadeOut = checkIncDec(event, p->fadeOut, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); + break; + + } + } + } +} diff --git a/radio/src/gui/Horus/menu_model_gvars.cpp b/radio/src/gui/Horus/menu_model_gvars.cpp new file mode 100644 index 000000000..bb3d82a0e --- /dev/null +++ b/radio/src/gui/Horus/menu_model_gvars.cpp @@ -0,0 +1,123 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +void onGVARSMenu(const char *result) +{ + int8_t sub = m_posVert; + + if (result == STR_ENABLE_POPUP) { + g_model.gvars[sub].popup = true; + eeDirty(EE_MODEL); + } + else if (result == STR_DISABLE_POPUP) { + g_model.gvars[sub].popup = false; + eeDirty(EE_MODEL); + } + else if (result == STR_CLEAR) { + for (int i=0; i0) ? BLINK|INVERS : INVERS) : 0); + coord_t x = GVARS_FM_COLUMN(j-1); + switch(j) + { + case 0: + editName(MENU_TITLE_LEFT+30, y, g_model.gvars[i].name, LEN_GVAR_NAME, event, attr); + break; + + default: + { + FlightModeData *fm = &g_model.flightModeData[j-1]; + int16_t & v = fm->gvars[i]; + int16_t vmin, vmax; + if (v > GVAR_MAX) { + uint8_t p = v - GVAR_MAX - 1; + if (p >= j-1) p++; + putsFlightMode(x-21, y, p+1, attr); + vmin = GVAR_MAX+1; vmax = GVAR_MAX+MAX_FLIGHT_MODES-1; + } + else { + if (abs(v) >= 1000) + lcd_outdezAtt(x, y+1, v, TINSIZE|attr); + else + lcd_outdezAtt(x, y, v, attr); + vmin = -GVAR_MAX; vmax = GVAR_MAX; + } + if (attr) { + if (event == EVT_KEY_LONG(KEY_ENTER)) { + v = (v > GVAR_MAX ? 0 : GVAR_MAX+1); + eeDirty(EE_MODEL); + } + else if (s_editMode>0) { + v = checkIncDec(event, v, vmin, vmax, EE_MODEL); + } + } + break; + } + } + } + } + + if (m_posHorz < 0 && event==EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + if (g_model.gvars[sub].popup) + MENU_ADD_ITEM(STR_DISABLE_POPUP); + else + MENU_ADD_ITEM(STR_ENABLE_POPUP); + MENU_ADD_ITEM(STR_CLEAR); + menuHandler = onGVARSMenu; + } +} diff --git a/radio/src/gui/Horus/menu_model_heli.cpp b/radio/src/gui/Horus/menu_model_heli.cpp new file mode 100644 index 000000000..b5e6c864a --- /dev/null +++ b/radio/src/gui/Horus/menu_model_heli.cpp @@ -0,0 +1,113 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +enum menuModelHeliItems { + ITEM_HELI_SWASHTYPE, + ITEM_HELI_SWASHRING, + ITEM_HELI_ELE, + ITEM_HELI_ELE_WEIGHT, + ITEM_HELI_AIL, + ITEM_HELI_AIL_WEIGHT, + ITEM_HELI_COL, + ITEM_HELI_COL_WEIGHT, + ITEM_HELI_MAX +}; + +#define MODEL_HELI_2ND_COLUMN (230) + +void menuModelHeli(evt_t event) +{ + SIMPLE_MENU(STR_MENUHELISETUP, menuTabModel, e_Heli, ITEM_HELI_MAX, DEFAULT_SCROLLBAR_X); + + int sub = m_posVert; + + for (unsigned int i=0; i0) ? BLINK|INVERS : INVERS); + LcdFlags attr = (sub == k ? blink : 0); + + switch(k) { + case ITEM_HELI_SWASHTYPE: + g_model.swashR.type = selectMenuItem(MODEL_HELI_2ND_COLUMN, y, STR_SWASHTYPE, STR_VSWASHTYPE, g_model.swashR.type, 0, SWASH_TYPE_MAX, attr, event); + break; + + case ITEM_HELI_SWASHRING: + lcd_putsLeft(y, STR_SWASHRING); + lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.value, LEFT|attr); + if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.swashR.value, 100); + break; + + case ITEM_HELI_ELE: + lcd_putsLeft(y, STR_ELEVATOR); + putsMixerSource(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.elevatorSource, attr); + if (attr) CHECK_INCDEC_MODELSOURCE(event, g_model.swashR.elevatorSource, 0, MIXSRC_LAST_CH); + break; + + case ITEM_HELI_ELE_WEIGHT: + lcd_puts(MENU_TITLE_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.elevatorWeight, LEFT|attr); + if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.elevatorWeight, -100, 100); + break; + + case ITEM_HELI_AIL: + lcd_putsLeft(y, STR_AILERON); + putsMixerSource(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.aileronSource, attr); + if (attr) CHECK_INCDEC_MODELSOURCE(event, g_model.swashR.aileronSource, 0, MIXSRC_LAST_CH); + break; + + case ITEM_HELI_AIL_WEIGHT: + lcd_puts(MENU_TITLE_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.aileronWeight, LEFT|attr); + if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.aileronWeight, -100, 100); + break; + + case ITEM_HELI_COL: + lcd_putsLeft(y, STR_COLLECTIVE); + putsMixerSource(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.collectiveSource, attr); + if (attr) CHECK_INCDEC_MODELSOURCE(event, g_model.swashR.collectiveSource, 0, MIXSRC_LAST_CH); + break; + + case ITEM_HELI_COL_WEIGHT: + lcd_puts(MENU_TITLE_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.collectiveWeight, LEFT|attr); + if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.collectiveWeight, -100, 100); + break; + + } + } +} diff --git a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp new file mode 100644 index 000000000..a460f88e5 --- /dev/null +++ b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp @@ -0,0 +1,971 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define EXPO_ONE_2ND_COLUMN 80 + +#if defined(FLIGHT_MODES) +void displayFlightModes(coord_t x, coord_t y, FlightModesType value, uint8_t attr) +{ + for (int i=0; isrcRaw, x); + return anas[ed->chn]; +} + +coord_t getYCoord(FnFuncP fn, coord_t x) +{ + return limit(0, Y0 - (fn(x * (RESX/WCHART)) * WCHART / RESX), LCD_H-1); +} + +void DrawFunction(FnFuncP fn, int offset) +{ + lcd_vlineStip(X0-offset, Y0-WCHART, WCHART*2, 0xee, CURVE_AXIS_COLOR); + lcd_hlineStip(X0-WCHART-offset, Y0, WCHART*2, 0xee, CURVE_AXIS_COLOR); + + coord_t ymin=0, ymax=0, info=0; + for (int xv=-WCHART; xv<=WCHART; xv++) { + coord_t yv = getYCoord(fn, xv); + + if (ymin == 0 || yv < ymin) + ymin = yv; + if (ymax == 0 || yv > ymax) + ymax = yv; + info += yv*xv; + } +} + +int getLinesCount(uint8_t expo) +{ + int lastch = -1; + uint8_t count = (expo ? MAX_INPUTS : NUM_CHNOUT); + for (int i=0; i<(expo ? MAX_EXPOS : MAX_MIXERS); i++) { + bool valid = (expo ? EXPO_VALID(expoAddress(i)) : mixAddress(i)->srcRaw); + if (!valid) + break; + int ch = (expo ? expoAddress(i)->chn : mixAddress(i)->destCh); + if (ch == lastch) { + count++; + } + else { + lastch = ch; + } + } + return count; +} + +uint8_t getExpoMixCount(uint8_t expo) +{ + uint8_t count = 0; + uint8_t ch ; + + for(int8_t i=(expo ? MAX_EXPOS-1 : MAX_MIXERS-1); i>=0; i--) { + ch = (expo ? EXPO_VALID(expoAddress(i)) : mixAddress(i)->srcRaw); + if (ch != 0) { + count++; + } + } + return count; +} + +bool reachExpoMixCountLimit(uint8_t expo) +{ + // check mixers count limit + if (getExpoMixCount(expo) >= (expo ? MAX_EXPOS : MAX_MIXERS)) { + POPUP_WARNING(expo ? STR_NOFREEEXPO : STR_NOFREEMIXER); + return true; + } + return false; +} + +void deleteExpoMix(uint8_t expo, uint8_t idx) +{ + pauseMixerCalculations(); + if (expo) { + ExpoData *expo = expoAddress(idx); + int input = expo->chn; + memmove(expo, expo+1, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); + memclear(&g_model.expoData[MAX_EXPOS-1], sizeof(ExpoData)); + if (!isInputAvailable(input)) { + memclear(&g_model.inputNames[input], LEN_INPUT_NAME); + } + } + else { + MixData *mix = mixAddress(idx); + memmove(mix, mix+1, (MAX_MIXERS-(idx+1))*sizeof(MixData)); + memclear(&g_model.mixData[MAX_MIXERS-1], sizeof(MixData)); + } + resumeMixerCalculations(); + eeDirty(EE_MODEL); +} + +// TODO avoid this global s_currCh on ARM boards ... +int8_t s_currCh; +void insertExpoMix(uint8_t expo, uint8_t idx) +{ + pauseMixerCalculations(); + if (expo) { + ExpoData *expo = expoAddress(idx); + memmove(expo+1, expo, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); + memclear(expo, sizeof(ExpoData)); + expo->srcRaw = (s_currCh > 4 ? MIXSRC_Rud - 1 + s_currCh : MIXSRC_Rud - 1 + channel_order(s_currCh)); + expo->curve.type = CURVE_REF_EXPO; + expo->mode = 3; // pos&neg + expo->chn = s_currCh - 1; + expo->weight = 100; + } + else { + MixData *mix = mixAddress(idx); + memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); + memclear(mix, sizeof(MixData)); + mix->destCh = s_currCh-1; + mix->srcRaw = s_currCh; + if (!isSourceAvailable(mix->srcRaw)) { + mix->srcRaw = (s_currCh > 4 ? MIXSRC_Rud - 1 + s_currCh : MIXSRC_Rud - 1 + channel_order(s_currCh)); + while (!isSourceAvailable(mix->srcRaw)) { + mix->srcRaw += 1; + } + } + mix->weight = 100; + } + resumeMixerCalculations(); + eeDirty(EE_MODEL); +} + +void copyExpoMix(uint8_t expo, uint8_t idx) +{ + pauseMixerCalculations(); + if (expo) { + ExpoData *expo = expoAddress(idx); + memmove(expo+1, expo, (MAX_EXPOS-(idx+1))*sizeof(ExpoData)); + } + else { + MixData *mix = mixAddress(idx); + memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); + } + resumeMixerCalculations(); + eeDirty(EE_MODEL); +} + +void memswap(void *a, void *b, uint8_t size) +{ + uint8_t *x = (uint8_t*)a; + uint8_t *y = (uint8_t*)b; + uint8_t temp ; + + while (size--) { + temp = *x; + *x++ = *y; + *y++ = temp; + } +} + +bool swapExpoMix(uint8_t expo, uint8_t &idx, uint8_t up) +{ + void *x, *y; + uint8_t size; + int8_t tgt_idx = (up ? idx-1 : idx+1); + + if (expo) { + x = (ExpoData *)expoAddress(idx); + + if (tgt_idx < 0) { + if (((ExpoData *)x)->chn == 0) + return false; + ((ExpoData *)x)->chn--; + return true; + } + + if (tgt_idx == MAX_EXPOS) { + if (((ExpoData *)x)->chn == NUM_INPUTS-1) + return false; + ((ExpoData *)x)->chn++; + return true; + } + + y = (ExpoData *)expoAddress(tgt_idx); + if(((ExpoData *)x)->chn != ((ExpoData *)y)->chn || !EXPO_VALID((ExpoData *)y)) { + if (up) { + if (((ExpoData *)x)->chn>0) ((ExpoData *)x)->chn--; + else return false; + } + else { + if (((ExpoData *)x)->chnchn++; + else return false; + } + return true; + } + + size = sizeof(ExpoData); + } + else { + x = (MixData *)mixAddress(idx); + + if (tgt_idx < 0) { + if (((MixData *)x)->destCh == 0) + return false; + ((MixData *)x)->destCh--; + return true; + } + + if (tgt_idx == MAX_MIXERS) { + if (((MixData *)x)->destCh == NUM_CHNOUT-1) + return false; + ((MixData *)x)->destCh++; + return true; + } + + y = (MixData *)mixAddress(tgt_idx); + uint8_t destCh = ((MixData *)x)->destCh; + if(!((MixData *)y)->srcRaw || destCh != ((MixData *)y)->destCh) { + if (up) { + if (destCh>0) ((MixData *)x)->destCh--; + else return false; + } + else { + if (destChdestCh++; + else return false; + } + return true; + } + + size = sizeof(MixData); + } + + pauseMixerCalculations(); + memswap(x, y, size); + resumeMixerCalculations(); + + idx = tgt_idx; + return true; +} + +enum ExposFields { + EXPO_FIELD_INPUT_NAME, + EXPO_FIELD_NAME, + EXPO_FIELD_SOURCE, + EXPO_FIELD_SCALE, + EXPO_FIELD_WEIGHT, + EXPO_FIELD_OFFSET, + CASE_CURVES(EXPO_FIELD_CURVE) + CASE_FLIGHT_MODES(EXPO_FIELD_FLIGHT_MODES) + EXPO_FIELD_SWITCH, + EXPO_FIELD_SIDE, + EXPO_FIELD_TRIM, + EXPO_FIELD_MAX +}; + +#define CURVE_ROWS 1 + +void menuModelExpoOne(evt_t event) +{ + ExpoData *ed = expoAddress(s_currIdx); + + SUBMENU(STR_MENUINPUTS, EXPO_FIELD_MAX, EXPO_ONE_2ND_COLUMN+85, { 0, 0, 0, (ed->srcRaw >= MIXSRC_FIRST_TELEM ? (uint8_t)0 : (uint8_t)HIDDEN_ROW), 0, 0, CASE_CURVES(CURVE_ROWS) CASE_FLIGHT_MODES((MAX_FLIGHT_MODES-1) | NAVIGATION_LINE_BY_LINE) 0 /*, ...*/}); + + putsMixerSource(60, MENU_FOOTER_TOP, MIXSRC_FIRST_INPUT+ed->chn, HEADER_COLOR); + + int8_t sub = m_posVert; + + coord_t y = MENU_CONTENT_TOP; + + DrawFunction(expoFn); + + { + int x = getValue(ed->srcRaw); + if (ed->srcRaw >= MIXSRC_FIRST_TELEM) { + putsTelemetryChannelValue(LCD_W-8, 6*FH, ed->srcRaw - MIXSRC_FIRST_TELEM, x); + if (ed->scale > 0) x = (x * 1024) / convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale); + } + else { + lcd_outdezAtt(X0+WCHART, MENU_HEADER_HEIGHT+6*FH, calcRESXto1000(x), PREC1); + } + + x = limit(-1024, x, 1024); + int y = limit(-1024, expoFn(x), 1024); + lcd_outdezAtt(X0-WCHART, MENU_HEADER_HEIGHT+1, calcRESXto1000(y), LEFT|PREC1); + + x = x / (RESX/WCHART); + y = getYCoord(expoFn, x); + + lcd_vlineStip(X0+x, y-3, 7, SOLID, TEXT_INVERTED_BGCOLOR); + lcd_hlineStip(X0+x-3, y, 7, SOLID, TEXT_INVERTED_BGCOLOR); + } + + for (unsigned int k=0; k0 ? BLINK|INVERS : INVERS) : 0); + switch(i) + { + case EXPO_FIELD_INPUT_NAME: + editSingleName(EXPO_ONE_2ND_COLUMN, y, STR_INPUTNAME, g_model.inputNames[ed->chn], sizeof(g_model.inputNames[ed->chn]), event, attr); + break; + + case EXPO_FIELD_NAME: + editSingleName(EXPO_ONE_2ND_COLUMN, y, STR_EXPONAME, ed->name, sizeof(ed->name), event, attr); + break; + + case EXPO_FIELD_SOURCE: + lcd_putsLeft(y, NO_INDENT(STR_SOURCE)); + putsMixerSource(EXPO_ONE_2ND_COLUMN, y, ed->srcRaw, STREXPANDED|attr); + if (attr) ed->srcRaw = checkIncDec(event, ed->srcRaw, INPUTSRC_FIRST, INPUTSRC_LAST, EE_MODEL|INCDEC_SOURCE|NO_INCDEC_MARKS, isInputSourceAvailable); + break; + + case EXPO_FIELD_SCALE: + lcd_putsLeft(y, STR_SCALE); + putsTelemetryChannelValue(EXPO_ONE_2ND_COLUMN, y, (ed->srcRaw - MIXSRC_FIRST_TELEM)/3, convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale), LEFT|attr); + if (attr) ed->scale = checkIncDec(event, ed->scale, 0, maxTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1), EE_MODEL); + break; + + case EXPO_FIELD_WEIGHT: + lcd_putsLeft(y, STR_WEIGHT); + ed->weight = GVAR_MENU_ITEM(EXPO_ONE_2ND_COLUMN, y, ed->weight, MIN_EXPO_WEIGHT, 100, LEFT|attr, 0, event); + break; + + case EXPO_FIELD_OFFSET: + lcd_putsLeft(y, NO_INDENT(STR_OFFSET)); + ed->offset = GVAR_MENU_ITEM(EXPO_ONE_2ND_COLUMN, y, ed->offset, -100, 100, LEFT|attr, 0, event); + break; + +#if defined(CURVES) + case EXPO_FIELD_CURVE: + lcd_putsLeft(y, STR_CURVE); + editCurveRef(EXPO_ONE_2ND_COLUMN, y, ed->curve, event, attr); + break; +#endif + +#if defined(FLIGHT_MODES) + case EXPO_FIELD_FLIGHT_MODES: + ed->flightModes = editFlightModes(EXPO_ONE_2ND_COLUMN, y, event, ed->flightModes, attr); + break; +#endif + + case EXPO_FIELD_SWITCH: + ed->swtch = switchMenuItem(EXPO_ONE_2ND_COLUMN, y, ed->swtch, attr, event); + break; + + case EXPO_FIELD_SIDE: + ed->mode = 4 - selectMenuItem(EXPO_ONE_2ND_COLUMN, y, STR_SIDE, STR_VSIDE, 4-ed->mode, 1, 3, attr, event); + break; + + case EXPO_FIELD_TRIM: + uint8_t not_stick = (ed->srcRaw > MIXSRC_Ail); + int8_t carryTrim = -ed->carryTrim; + lcd_putsLeft(y, STR_TRIM); + lcd_putsiAtt(EXPO_ONE_2ND_COLUMN, y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); + if (attr) ed->carryTrim = -checkIncDecModel(event, carryTrim, not_stick ? TRIM_ON : -TRIM_OFF, -TRIM_AIL); + break; + } + y += FH; + } +} + +enum MixFields { + MIX_FIELD_NAME, + MIX_FIELD_SOURCE, + MIX_FIELD_WEIGHT, + MIX_FIELD_OFFSET, + MIX_FIELD_TRIM, + CASE_CURVES(MIX_FIELD_CURVE) + CASE_FLIGHT_MODES(MIX_FIELD_FLIGHT_PHASE) + MIX_FIELD_SWITCH, + // MIX_FIELD_WARNING, + MIX_FIELD_MLTPX, + MIX_FIELD_DELAY_UP, + MIX_FIELD_DELAY_DOWN, + MIX_FIELD_SLOW_UP, + MIX_FIELD_SLOW_DOWN, + MIX_FIELD_COUNT +}; + +void gvarWeightItem(coord_t x, coord_t y, MixData *md, uint8_t attr, evt_t event) +{ + u_int8int16_t weight; + MD_WEIGHT_TO_UNION(md, weight); + weight.word = GVAR_MENU_ITEM(x, y, weight.word, GV_RANGELARGE_WEIGHT_NEG, GV_RANGELARGE_WEIGHT, attr, 0, event); + MD_UNION_TO_WEIGHT(weight, md); +} + +#define GAUGE_WIDTH 33 +#define GAUGE_HEIGHT 6 +void drawOffsetBar(uint8_t x, uint8_t y, MixData * md) +{ + int offset = GET_GVAR(MD_OFFSET(md), GV_RANGELARGE_NEG, GV_RANGELARGE, mixerCurrentFlightMode); + int weight = abs(GET_GVAR(MD_WEIGHT(md), GV_RANGELARGE_NEG, GV_RANGELARGE, mixerCurrentFlightMode)); + int barMin = offset - weight; + int barMax = offset + weight; + if (y > 15) { + lcd_outdezAtt(x-((barMin >= 0) ? 2 : 3), y-6, barMin, TINSIZE|LEFT); + lcd_outdezAtt(x+GAUGE_WIDTH+1, y-6, barMax, TINSIZE); + } + if (barMin < -101) + barMin = -101; + if (barMax > 101) + barMax = 101; + lcd_hlineStip(x-2, y, GAUGE_WIDTH+2, DOTTED); + lcd_hlineStip(x-2, y+GAUGE_HEIGHT, GAUGE_WIDTH+2, DOTTED); + lcd_vline(x-2, y+1, GAUGE_HEIGHT-1); + lcd_vline(x+GAUGE_WIDTH-1, y+1, GAUGE_HEIGHT-1); + if (barMin <= barMax) { + int8_t right = (barMax * GAUGE_WIDTH) / 200; + int8_t left = ((barMin * GAUGE_WIDTH) / 200)-1; + // TODO lcdDrawFilledRect(x+GAUGE_WIDTH/2+left, y+2, right-left, GAUGE_HEIGHT-3); + } + lcd_vline(x+GAUGE_WIDTH/2-1, y, GAUGE_HEIGHT+1); +#if 0 // TODO + if (barMin == -101) { + for (uint8_t i=0; i<3; ++i) { + lcdDrawPoint(x+i, y+4-i); + lcdDrawPoint(x+3+i, y+4-i); + } + } + if (barMax == 101) { + for (uint8_t i=0; i<3; ++i) { + lcdDrawPoint(x+GAUGE_WIDTH-8+i, y+4-i); + lcdDrawPoint(x+GAUGE_WIDTH-5+i, y+4-i); + } + } +#endif +} +#undef GAUGE_WIDTH +#undef GAUGE_HEIGHT + +void menuModelMixOne(evt_t event) +{ + MixData *md2 = mixAddress(s_currIdx) ; + + SUBMENU(s_currCh ? STR_INSERTMIX : STR_EDITMIX, MIX_FIELD_COUNT, 0, { 0, 0, 0, 0, 0, CASE_CURVES(1) CASE_FLIGHT_MODES((MAX_FLIGHT_MODES-1) | NAVIGATION_LINE_BY_LINE) 0 /*, ...*/ }); + + putsChn(80, MENU_FOOTER_TOP, md2->destCh+1, HEADER_COLOR); + + // The separation line between 2 columns + lcd_vlineStip(MENU_COLUMN2_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, SOLID, HEADER_COLOR); + + int8_t sub = m_posVert; + int8_t editMode = s_editMode; + + for (int k=0; k<2*NUM_BODY_LINES; k++) { + coord_t y; + coord_t x; + if (k >= NUM_BODY_LINES) { + y = MENU_CONTENT_TOP + (k-NUM_BODY_LINES)*FH; + x = MENU_COLUMN2_X; + } + else { + y = MENU_CONTENT_TOP + k*FH; + x = 0; + } + int8_t i = k; + + LcdFlags attr = (sub==i ? (editMode>0 ? BLINK|INVERS : INVERS) : 0); + switch(i) { + case MIX_FIELD_NAME: + editSingleName(x+MIXES_2ND_COLUMN, y, STR_MIXNAME, md2->name, sizeof(md2->name), event, attr); + break; + case MIX_FIELD_SOURCE: + lcd_putsLeft(y, NO_INDENT(STR_SOURCE)); + putsMixerSource(x+MIXES_2ND_COLUMN, y, md2->srcRaw, STREXPANDED|attr); + if (attr) CHECK_INCDEC_MODELSOURCE(event, md2->srcRaw, 1, MIXSRC_LAST); + break; + case MIX_FIELD_WEIGHT: + lcd_putsColumnLeft(x, y, STR_WEIGHT); + gvarWeightItem(x+MIXES_2ND_COLUMN, y, md2, attr|LEFT, event); + break; + case MIX_FIELD_OFFSET: + { + lcd_putsColumnLeft(x, y, NO_INDENT(STR_OFFSET)); + u_int8int16_t offset; + MD_OFFSET_TO_UNION(md2, offset); + offset.word = GVAR_MENU_ITEM(x+MIXES_2ND_COLUMN, y, offset.word, GV_RANGELARGE_OFFSET_NEG, GV_RANGELARGE_OFFSET, attr|LEFT, 0, event); + MD_UNION_TO_OFFSET(offset, md2); +#if 0 + drawOffsetBar(x+MIXES_2ND_COLUMN+22, y, md2); +#endif + break; + } + case MIX_FIELD_TRIM: + lcd_putsColumnLeft(x, y, STR_TRIM); + lcdDrawCheckBox(x+MIXES_2ND_COLUMN, y, !md2->carryTrim, attr); + if (attr) md2->carryTrim = !checkIncDecModel(event, !md2->carryTrim, 0, 1); + break; +#if defined(CURVES) + case MIX_FIELD_CURVE: + { + lcd_putsColumnLeft(x, y, STR_CURVE); + editCurveRef(x+MIXES_2ND_COLUMN, y, md2->curve, event, attr); + break; + } +#endif +#if defined(FLIGHT_MODES) + case MIX_FIELD_FLIGHT_PHASE: + md2->flightModes = editFlightModes(x+MIXES_2ND_COLUMN, y, event, md2->flightModes, attr); + break; +#endif + case MIX_FIELD_SWITCH: + md2->swtch = switchMenuItem(x+MIXES_2ND_COLUMN, y, md2->swtch, attr, event); + break; +#if 0 + case MIX_FIELD_WARNING: + lcd_putsColumnLeft(x+MIXES_2ND_COLUMN, y, STR_MIXWARNING); + if (md2->mixWarn) + lcd_outdezAtt(x+MIXES_2ND_COLUMN, y, md2->mixWarn, attr|LEFT); + else + lcd_putsAtt(x+MIXES_2ND_COLUMN, y, STR_OFF, attr); + if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, md2->mixWarn, 3); + break; +#endif + case MIX_FIELD_MLTPX: + md2->mltpx = selectMenuItem(x+MIXES_2ND_COLUMN, y, STR_MULTPX, STR_VMLTPX, md2->mltpx, 0, 2, attr, event); + break; + case MIX_FIELD_DELAY_UP: + md2->delayUp = EDIT_DELAY(x, y, event, attr, STR_DELAYUP, md2->delayUp); + break; + case MIX_FIELD_DELAY_DOWN: + md2->delayDown = EDIT_DELAY(x, y, event, attr, STR_DELAYDOWN, md2->delayDown); + break; + case MIX_FIELD_SLOW_UP: + md2->speedUp = EDIT_DELAY(x, y, event, attr, STR_SLOWUP, md2->speedUp); + break; + case MIX_FIELD_SLOW_DOWN: + md2->speedDown = EDIT_DELAY(x, y, event, attr, STR_SLOWDOWN, md2->speedDown); + break; + } + } +} + +static uint8_t s_copySrcIdx; +static uint8_t s_copySrcCh; + +#define _STR_MAX(x) PSTR("/" #x) +#define STR_MAX(x) _STR_MAX(x) + +#define EXPO_LINE_WEIGHT_POS 89 +#define EXPO_LINE_SRC_POS 102 +#define EXPO_LINE_FM_POS 138 +#define EXPO_LINE_CURVE_POS 138 +#define EXPO_LINE_SWITCH_POS 182 +#define EXPO_LINE_SIDE_POS 215 +#define EXPO_LINE_SELECT_POS 61 +#define EXPO_LINE_SELECT_WIDTH 244 +#define EXPO_LINE_NAME_POS 238 + +#define MIX_LINE_WEIGHT_POS 69 +#define MIX_LINE_SRC_POS 83 +#define MIX_LINE_FM_POS 142 +#define MIX_LINE_CURVE_POS 142 +#define MIX_LINE_SWITCH_POS 190 +#define MIX_LINE_DELAY_POS 225 +#define MIX_LINE_SELECT_POS 40 +#define MIX_LINE_SELECT_WIDTH 265 + +void lineSurround(bool expo, coord_t y, LcdFlags flags=CURVE_AXIS_COLOR) +{ + lcd_rect(expo ? EXPO_LINE_SELECT_POS : MIX_LINE_SELECT_POS, y-INVERT_VERT_MARGIN, expo ? EXPO_LINE_SELECT_WIDTH : MIX_LINE_SELECT_WIDTH, INVERT_LINE_HEIGHT, WARNING_COLOR|(s_copyMode == COPY_MODE ? SOLID : DOTTED), flags); +} + +void onExpoMixMenu(const char *result) +{ + bool expo = (g_menuStack[g_menuStackPtr] == menuModelExposAll); + uint8_t chn = (expo ? expoAddress(s_currIdx)->chn+1 : mixAddress(s_currIdx)->destCh+1); + + if (result == STR_EDIT) { + pushMenu(expo ? menuModelExpoOne : menuModelMixOne); + } + else if (result == STR_INSERT_BEFORE || result == STR_INSERT_AFTER) { + if (!reachExpoMixCountLimit(expo)) { + s_currCh = chn; + if (result == STR_INSERT_AFTER) { s_currIdx++; m_posVert++; } + insertExpoMix(expo, s_currIdx); + pushMenu(expo ? menuModelExpoOne : menuModelMixOne); + } + } + else if (result == STR_COPY || result == STR_MOVE) { + s_copyMode = (result == STR_COPY ? COPY_MODE : MOVE_MODE); + s_copySrcIdx = s_currIdx; + s_copySrcCh = chn; + s_copySrcRow = m_posVert; + } + else if (result == STR_DELETE) { + deleteExpoMix(expo, s_currIdx); + } +} + +void displayHeaderChannelName(uint8_t ch) +{ + uint8_t len = zlen(g_model.limitData[ch-1].name, sizeof(g_model.limitData[ch-1].name)); + if (len) { + lcd_putsnAtt(COLUMN_HEADER_X, MENU_FOOTER_TOP, g_model.limitData[ch-1].name, len, HEADER_COLOR|ZCHAR); + } +} + +void displayMixInfos(coord_t y, MixData *md) +{ + putsCurveRef(MIX_LINE_CURVE_POS, y, md->curve); + + if (md->swtch) { + putsSwitches(MIX_LINE_SWITCH_POS, y, md->swtch); + } +} + +void displayMixLine(coord_t y, MixData *md) +{ + if (md->name[0]) + lcd_putsnAtt(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); + if (!md->flightModes || ((md->curve.value || md->swtch) && ((get_tmr10ms() / 200) & 1))) + displayMixInfos(y, md); + else + displayFlightModes(MIX_LINE_FM_POS, y, md->flightModes, 0); +} + +void displayExpoInfos(coord_t y, ExpoData *ed) +{ + putsCurveRef(EXPO_LINE_CURVE_POS, y, ed->curve); + if (ed->swtch) { + putsSwitches(EXPO_LINE_SWITCH_POS, y, ed->swtch); + } + if (ed->mode != 3) { + lcd_puts(EXPO_LINE_SIDE_POS, y, ed->mode == 2 ? "\176" : "\177"); + } +} + +void displayExpoLine(coord_t y, ExpoData *ed) +{ + putsMixerSource(EXPO_LINE_SRC_POS, y, ed->srcRaw); + + if (!ed->flightModes || ((ed->curve.value || ed->swtch) && ((get_tmr10ms() / 200) & 1))) + displayExpoInfos(y, ed); + else + displayFlightModes(EXPO_LINE_FM_POS, y, ed->flightModes, 0); + + if (ed->name[0]) { + lcd_putsnAtt(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); + } +} + +void menuModelExpoMix(uint8_t expo, evt_t event) +{ + int sub = m_posVert; + + if (s_editMode > 0) + s_editMode = 0; + + uint8_t chn = (expo ? expoAddress(s_currIdx)->chn+1 : mixAddress(s_currIdx)->destCh+1); + + switch (event) + { + case EVT_ENTRY: + case EVT_ENTRY_UP: + s_copyMode = 0; + s_copyTgtOfs = 0; + break; + case EVT_KEY_LONG(KEY_EXIT): + if (s_copyMode && s_copyTgtOfs == 0) { + deleteExpoMix(expo, s_currIdx); + killEvents(event); + event = 0; + } + // no break + case EVT_KEY_BREAK(KEY_EXIT): + if (s_copyMode) { + if (s_copyTgtOfs) { + // cancel the current copy / move operation + if (s_copyMode == COPY_MODE) { + deleteExpoMix(expo, s_currIdx); + } + else { + do { + swapExpoMix(expo, s_currIdx, s_copyTgtOfs > 0); + s_copyTgtOfs += (s_copyTgtOfs < 0 ? +1 : -1); + } while (s_copyTgtOfs != 0); + eeDirty(EE_MODEL); + } + m_posVert = s_copySrcRow; + s_copyTgtOfs = 0; + } + s_copyMode = 0; + event = 0; + } + break; + case EVT_KEY_BREAK(KEY_ENTER): + if ((!s_currCh || (s_copyMode && !s_copyTgtOfs)) && !READ_ONLY()) { + s_copyMode = (s_copyMode == COPY_MODE ? MOVE_MODE : COPY_MODE); + s_copySrcIdx = s_currIdx; + s_copySrcCh = chn; + s_copySrcRow = sub; + break; + } + // no break + + CASE_EVT_ROTARY_BREAK + case EVT_KEY_LONG(KEY_ENTER): + killEvents(event); + if (s_copyTgtOfs) { + s_copyMode = 0; + s_copyTgtOfs = 0; + } + else { + if (READ_ONLY()) { + if (!s_currCh) { + pushMenu(expo ? menuModelExpoOne : menuModelMixOne); + } + } + else { + if (s_copyMode) s_currCh = 0; + if (s_currCh) { + if (reachExpoMixCountLimit(expo)) break; + insertExpoMix(expo, s_currIdx); + pushMenu(expo ? menuModelExpoOne : menuModelMixOne); + s_copyMode = 0; + } + else { + event = 0; + s_copyMode = 0; + MENU_ADD_ITEM(STR_EDIT); + MENU_ADD_ITEM(STR_INSERT_BEFORE); + MENU_ADD_ITEM(STR_INSERT_AFTER); + MENU_ADD_ITEM(STR_COPY); + MENU_ADD_ITEM(STR_MOVE); + MENU_ADD_ITEM(STR_DELETE); + menuHandler = onExpoMixMenu; + } + } + } + break; + case EVT_KEY_LONG(KEY_LEFT): + case EVT_KEY_LONG(KEY_RIGHT): + if (s_copyMode && !s_copyTgtOfs) { + if (reachExpoMixCountLimit(expo)) break; + s_currCh = chn; + if (event == EVT_KEY_LONG(KEY_RIGHT)) { s_currIdx++; m_posVert++; } + insertExpoMix(expo, s_currIdx); + pushMenu(expo ? menuModelExpoOne : menuModelMixOne); + s_copyMode = 0; + killEvents(event); + } + break; + case EVT_KEY_FIRST(KEY_MOVE_UP): + case EVT_KEY_REPT(KEY_MOVE_UP): + case EVT_KEY_FIRST(KEY_MOVE_DOWN): + case EVT_KEY_REPT(KEY_MOVE_DOWN): + if (s_copyMode) { + uint8_t key = (event & 0x1f); + uint8_t next_ofs = ((IS_ROTARY_UP(event) || key==KEY_MOVE_UP) ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); + + if (s_copyTgtOfs==0 && s_copyMode==COPY_MODE) { + // insert a mix on the same channel (just above / just below) + if (reachExpoMixCountLimit(expo)) break; + copyExpoMix(expo, s_currIdx); + if (IS_ROTARY_DOWN(event) || key==KEY_MOVE_DOWN) s_currIdx++; + else if (sub-s_pgOfs >= 6) s_pgOfs++; + } + else if (next_ofs==0 && s_copyMode==COPY_MODE) { + // delete the mix + deleteExpoMix(expo, s_currIdx); + if (IS_ROTARY_UP(event) || key==KEY_MOVE_UP) s_currIdx--; + } + else { + // only swap the mix with its neighbor + if (!swapExpoMix(expo, s_currIdx, IS_ROTARY_UP(event) || key==KEY_MOVE_UP)) break; + eeDirty(EE_MODEL); + } + + s_copyTgtOfs = next_ofs; + } + break; + } + + int linesCount = getLinesCount(expo); + SIMPLE_MENU(expo ? STR_MENUINPUTS : STR_MIXER, menuTabModel, expo ? e_InputsAll : e_MixAll, linesCount, DEFAULT_SCROLLBAR_X); + + char str[6]; + sprintf(str, "%d/%d", getExpoMixCount(expo), expo ? MAX_EXPOS : MAX_MIXERS); + lcd_putsAtt(60, MENU_FOOTER_TOP, str, HEADER_COLOR); + + sub = m_posVert; + s_currCh = 0; + int cur = 0; + int i = 0; + + for (int ch=1; ch<=(expo ? NUM_INPUTS : NUM_CHNOUT); ch++) { + void *pointer = NULL; MixData * &md = (MixData * &)pointer; ExpoData * &ed = (ExpoData * &)pointer; + coord_t y = MENU_CONTENT_TOP + (cur-s_pgOfs)*FH; + if (expo ? (ichn+1 == ch && EXPO_VALID(ed)) : (isrcRaw && md->destCh+1 == ch)) { + if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { + if (expo) { + putsMixerSource(MENU_TITLE_LEFT, y, ch); + } + else { + putsChn(MENU_TITLE_LEFT, y, ch, 0); // show CHx + } + } + uint8_t mixCnt = 0; + do { + if (s_copyMode) { + if (s_copyMode == MOVE_MODE && cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES && s_copySrcCh == ch && s_copyTgtOfs != 0 && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { + lineSurround(expo, y); + cur++; y+=FH; + } + if (s_currIdx == i) { + sub = m_posVert = cur; + s_currCh = ch; + } + } + else if (sub == cur) { + s_currIdx = i; + } + if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { + LcdFlags attr = ((s_copyMode || sub != cur) ? 0 : INVERS); + if (expo) { + ed->weight = GVAR_MENU_ITEM(EXPO_LINE_WEIGHT_POS, y, ed->weight, MIN_EXPO_WEIGHT, 100, attr | (isExpoActive(i) ? BOLD : 0), 0, 0); + displayExpoLine(y, ed); + } + else { + if (attr) { + displayHeaderChannelName(ch); + } + + if (mixCnt > 0) lcd_putsiAtt(6, y, STR_VMLTPX2, md->mltpx, 0); + + putsMixerSource(MIX_LINE_SRC_POS, y, md->srcRaw); + + gvarWeightItem(MIX_LINE_WEIGHT_POS, y, md, attr | (isMixActive(i) ? BOLD : 0), event); + + displayMixLine(y, md); + + char cs[] = " "; + if (md->speedDown || md->speedUp) + cs[0] = 'S'; + if (md->delayUp || md->delayDown) + cs[0] = (cs[0] =='S' ? '*' : 'D'); + lcd_puts(MIX_LINE_DELAY_POS, y, cs); + } + if (s_copyMode) { + if ((s_copyMode==COPY_MODE || s_copyTgtOfs == 0) && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { + /* draw a border around the raw on selection mode (copy/move) */ + lineSurround(expo, y); + } + if (cur == sub) { + /* invert the raw when it's the current one */ + lineSurround(expo, y, WARNING_COLOR); + } + } + } + cur++; y+=FH; mixCnt++; i++; if (expo) ed++; else md++; + } while (expo ? (ichn+1 == ch && EXPO_VALID(ed)) : (isrcRaw && md->destCh+1 == ch)); + if (s_copyMode == MOVE_MODE && cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { + lineSurround(expo, y); + cur++; y+=FH; + } + } + else { + uint8_t attr = 0; + if (sub == cur) { + s_currIdx = i; + s_currCh = ch; + if (!s_copyMode) { + attr = INVERS; + } + } + if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { + if (expo) { + putsMixerSource(MENU_TITLE_LEFT, y, ch, attr); + } + else { + putsChn(MENU_TITLE_LEFT, y, ch, attr); // show CHx + if (attr) { + displayHeaderChannelName(ch); + } + } + if (s_copyMode == MOVE_MODE && s_copySrcCh == ch) { + lineSurround(expo, y); + } + } + cur++; y+=FH; + } + } + + if (sub >= linesCount-1) m_posVert = linesCount-1; +} + +void menuModelExposAll(evt_t event) +{ + return menuModelExpoMix(1, event); +} + +void menuModelMixAll(evt_t event) +{ + return menuModelExpoMix(0, event); +} diff --git a/radio/src/gui/Horus/menu_model_limits.cpp b/radio/src/gui/Horus/menu_model_limits.cpp new file mode 100644 index 000000000..2d9c11bee --- /dev/null +++ b/radio/src/gui/Horus/menu_model_limits.cpp @@ -0,0 +1,290 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +bool isThrottleOutput(uint8_t ch) +{ + for (uint8_t i=0; idestCh==ch && mix->srcRaw==MIXSRC_Thr) + return true; + } + return false; +} + +enum LimitsItems { + ITEM_LIMITS_CH_NAME, + ITEM_LIMITS_OFFSET, + ITEM_LIMITS_MIN, + ITEM_LIMITS_MAX, + ITEM_LIMITS_DIRECTION, +#if defined(CURVES) + ITEM_LIMITS_CURVE, +#endif +#if defined(PPM_CENTER_ADJUSTABLE) + ITEM_LIMITS_PPM_CENTER, +#endif +#if defined(PPM_LIMITS_SYMETRICAL) + ITEM_LIMITS_SYMETRICAL, +#endif + ITEM_LIMITS_COUNT, + ITEM_LIMITS_MAXROW = ITEM_LIMITS_COUNT-1 +}; + +#if defined(PPM_CENTER_ADJUSTABLE) + #define LIMITS_NAME_POS 44 + #define LIMITS_OFFSET_POS 136 + #define LIMITS_MIN_POS 175 + #define LIMITS_DIRECTION_POS 177 + #define LIMITS_MAX_POS 214 + #define LIMITS_REVERT_POS 218 + #define LIMITS_CURVE_POS 232 + #define LIMITS_PPM_CENTER_POS 292 + #define LIMITS_SYMETRICAL_POS 298 +#else + #define LIMITS_NAME_POS 44 + #define LIMITS_OFFSET_POS 136 + #define LIMITS_MIN_POS 178 + #define LIMITS_DIRECTION_POS 181 + #define LIMITS_MAX_POS 218 + #define LIMITS_REVERT_POS 228 + #define LIMITS_CURVE_POS 258 + #define LIMITS_SYMETRICAL_POS 294 +#endif + +#define LIMITS_MIN_MAX_OFFSET 1000 +#define CONVERT_US_MIN_MAX(x) (((x)*1280)/250) + +#if defined(PPM_UNIT_US) + #define SET_MIN_MAX(x, val) x = ((val)*250)/128 + #define MIN_MAX_DISPLAY(x) CONVERT_US_MIN_MAX(x) +#else + #define MIN_MAX_DISPLAY(x) (x) + #define SET_MIN_MAX(x, val) x = (val) +#endif + +void onLimitsMenu(const char *result) +{ + uint8_t ch = m_posVert - 1; + + if (result == STR_RESET) { + LimitData *ld = limitAddress(ch); + ld->min = 0; + ld->max = 0; + ld->offset = 0; + ld->ppmCenter = 0; + ld->revert = false; + ld->curve = 0; + } + else if (result == STR_COPY_STICKS_TO_OFS) { + copySticksToOffset(ch); + } + else if (result == STR_COPY_TRIMS_TO_OFS) { + copyTrimsToOffset(ch); + } +} + +void menuModelLimits(evt_t event) +{ + MENU(STR_MENULIMITS, menuTabModel, e_Limits, NUM_CHNOUT+1, DEFAULT_SCROLLBAR_X, + { NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, + NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, + NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, + NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, + 0 }); + + uint32_t sub = m_posVert; + + if (sub < NUM_CHNOUT) { +#if defined(PPM_CENTER_ADJUSTABLE) || defined(PPM_UNIT_US) + lcd_outdezAtt(100, MENU_FOOTER_TOP, PPM_CH_CENTER(sub)+channelOutputs[sub]/2, HEADER_COLOR, STR_US); +#else + lcd_outdezAtt(100, MENU_FOOTER_TOP, calcRESXto1000(channelOutputs[sub]), HEADER_COLOR|PREC1); +#endif + } + + if (sub=0) { + displayColumnHeader(STR_LIMITS_HEADERS, m_posHorz); + } + + if (s_warning_result) { + s_warning_result = 0; + LimitData *ld = limitAddress(sub); + ld->revert = !ld->revert; + eeDirty(EE_MODEL); + } + + for (int i=0; irevert) ? -LIMIT_OFS(ld) : LIMIT_OFS(ld); + char swVal[2] = "-"; // '-', '<', '>' + if ((channelOutputs[k] - v) > 50) swVal[0] = (ld->revert ? 127 : 126); // Switch to raw inputs? - remove trim! + if ((channelOutputs[k] - v) < -50) swVal[0] = (ld->revert ? 126 : 127); + lcd_puts(LIMITS_DIRECTION_POS, y, swVal); + + int limit = (g_model.extendedLimits ? LIMIT_EXT_MAX : 1000); + + putsChn(MENU_TITLE_LEFT, y, k+1, (sub==k && m_posHorz < 0) ? INVERS : 0); + if (sub==k && m_posHorz < 0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) { + killEvents(event); + MENU_ADD_ITEM(STR_RESET); + MENU_ADD_ITEM(STR_COPY_TRIMS_TO_OFS); + MENU_ADD_ITEM(STR_COPY_STICKS_TO_OFS); + menuHandler = onLimitsMenu; + } + + for (uint8_t j=0; j0) ? BLINK|INVERS : INVERS) : 0); + uint8_t active = (attr && s_editMode>0) ; + if (active) STICK_SCROLL_DISABLE(); + switch(j) + { + case ITEM_LIMITS_CH_NAME: + editName(LIMITS_NAME_POS, y, ld->name, sizeof(ld->name), event, attr); + break; + + case ITEM_LIMITS_OFFSET: + if (GV_IS_GV_VALUE(ld->offset, -1000, 1000) || (attr && event == EVT_KEY_LONG(KEY_ENTER))) { + ld->offset = GVAR_MENU_ITEM(LIMITS_OFFSET_POS, y, ld->offset, -1000, 1000, attr|PREC1, 0, event); + break; + } + +#if defined(PPM_UNIT_US) + lcd_outdezAtt(LIMITS_OFFSET_POS, y, ((int32_t)ld->offset*128) / 25, attr|PREC1); +#else + lcd_outdezAtt(LIMITS_OFFSET_POS, y, ld->offset, attr|PREC1); +#endif + if (active) { + ld->offset = checkIncDec(event, ld->offset, -1000, 1000, EE_MODEL, NULL, stops1000); + } + else if (attr && event==EVT_KEY_LONG(KEY_MENU)) { + copySticksToOffset(k); + s_editMode = 0; + } + break; + + case ITEM_LIMITS_MIN: + if (GV_IS_GV_VALUE(ld->min, -GV_RANGELARGE, GV_RANGELARGE) || (attr && event == EVT_KEY_LONG(KEY_ENTER))) { + ld->min = GVAR_MENU_ITEM(LIMITS_MIN_POS, y, ld->min, -LIMIT_EXT_MAX, LIMIT_EXT_MAX, attr|PREC1, 0, event); + break; + } + lcd_outdezAtt(LIMITS_MIN_POS, y, MIN_MAX_DISPLAY(ld->min-LIMITS_MIN_MAX_OFFSET), attr|PREC1); + if (active) ld->min = LIMITS_MIN_MAX_OFFSET + checkIncDec(event, ld->min-LIMITS_MIN_MAX_OFFSET, -limit, 0, EE_MODEL, NULL, stops1000); + break; + + case ITEM_LIMITS_MAX: + if (GV_IS_GV_VALUE(ld->max, -GV_RANGELARGE, GV_RANGELARGE) || (attr && event == EVT_KEY_LONG(KEY_ENTER))) { + ld->max = GVAR_MENU_ITEM(LIMITS_MAX_POS, y, ld->max, -LIMIT_EXT_MAX, LIMIT_EXT_MAX, attr|PREC1, 0, event); + break; + } + lcd_outdezAtt(LIMITS_MAX_POS, y, MIN_MAX_DISPLAY(ld->max+LIMITS_MIN_MAX_OFFSET), attr|PREC1); + if (active) ld->max = -LIMITS_MIN_MAX_OFFSET + checkIncDec(event, ld->max+LIMITS_MIN_MAX_OFFSET, 0, +limit, EE_MODEL, NULL, stops1000); + break; + + case ITEM_LIMITS_DIRECTION: + { + uint8_t revert = ld->revert; +#if defined(PPM_CENTER_ADJUSTABLE) + lcd_putsAtt(LIMITS_REVERT_POS, y, revert ? "\177" : "\176", attr); +#else + lcd_putsiAtt(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); +#endif + if (active) { + uint8_t revert_new = checkIncDecModel(event, revert, 0, 1); + if (checkIncDec_Ret && isThrottleOutput(k)) { + POPUP_CONFIRMATION(STR_INVERT_THR); + } + else { + ld->revert = revert_new; + } + } + break; + } + +#if defined(CURVES) + case ITEM_LIMITS_CURVE: + putsCurve(LIMITS_CURVE_POS, y, ld->curve, attr); + if (attr && event==EVT_KEY_LONG(KEY_ENTER) && ld->curve>0) { + s_curveChan = (ld->curve<0 ? -ld->curve-1 : ld->curve-1); + pushMenu(menuModelCurveOne); + } + if (active) { + CHECK_INCDEC_MODELVAR(event, ld->curve, -MAX_CURVES, +MAX_CURVES); + } + break; +#endif + +#if defined(PPM_CENTER_ADJUSTABLE) + case ITEM_LIMITS_PPM_CENTER: + lcd_outdezAtt(LIMITS_PPM_CENTER_POS, y, PPM_CENTER+ld->ppmCenter, attr); + if (active) { + CHECK_INCDEC_MODELVAR(event, ld->ppmCenter, -PPM_CENTER_MAX, +PPM_CENTER_MAX); + } + break; +#endif + +#if defined(PPM_LIMITS_SYMETRICAL) + case ITEM_LIMITS_SYMETRICAL: + lcd_putsAtt(LIMITS_SYMETRICAL_POS, y, ld->symetrical ? "=" : "\306", attr); + if (active) { + CHECK_INCDEC_MODELVAR_ZERO(event, ld->symetrical, 1); + } + break; +#endif + } + } + } +} diff --git a/radio/src/gui/Horus/menu_model_logical_switches.cpp b/radio/src/gui/Horus/menu_model_logical_switches.cpp new file mode 100644 index 000000000..7d20c6eef --- /dev/null +++ b/radio/src/gui/Horus/menu_model_logical_switches.cpp @@ -0,0 +1,275 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +enum LogicalSwitchFields { + LS_FIELD_FUNCTION, + LS_FIELD_V1, + LS_FIELD_V2, + LS_FIELD_V3, + LS_FIELD_ANDSW, + LS_FIELD_DURATION, + LS_FIELD_DELAY, + LS_FIELD_COUNT, + LS_FIELD_LAST = LS_FIELD_COUNT-1 +}; + +#define CSW_1ST_COLUMN 40 +#define CSW_2ND_COLUMN 85 +#define CSW_3RD_COLUMN 130 +#define CSW_4TH_COLUMN 185 +#define CSW_5TH_COLUMN 225 +#define CSW_6TH_COLUMN 270 + +#define INCDEC_DECLARE_VARS(f) uint8_t incdecFlag = (f); IsValueAvailable isValueAvailable = NULL +#define INCDEC_SET_FLAG(f) incdecFlag = (f) +#define INCDEC_ENABLE_CHECK(fn) isValueAvailable = fn +#define CHECK_INCDEC_PARAM(event, var, min, max) checkIncDec(event, var, min, max, incdecFlag, isValueAvailable) + +void putsEdgeDelayParam(coord_t x, coord_t y, LogicalSwitchData *cs, uint8_t lattr, uint8_t rattr) +{ + char s[32]; + div_t left = div(lswTimerValue(cs->v2), 10); + char sleft[10]; + sprintf(sleft, "%d.%d", left.quot, left.rem); + div_t right = div(lswTimerValue(cs->v2+cs->v3), 10); + char sright[10]; + sprintf(sright, "%d.%d", right.quot, right.rem); + sprintf(s, "[%s:%s]", sleft, sright); + lcd_puts(x-4, y, s); + /* if (cs->v3 < 0) + lcd_putsAtt(lcdLastPos+3, y, "<<", rattr); + else if (cs->v3 == 0) + lcd_putsAtt(lcdLastPos+3, y, "--", rattr); */ +} + +void onLogicalSwitchesMenu(const char *result) +{ + int8_t sub = m_posVert; + LogicalSwitchData * cs = lswAddress(sub); + + if (result == STR_COPY) { + clipboard.type = CLIPBOARD_TYPE_CUSTOM_SWITCH; + clipboard.data.csw = *cs; + } + else if (result == STR_PASTE) { + *cs = clipboard.data.csw; + eeDirty(EE_MODEL); + } + else if (result == STR_CLEAR) { + memset(cs, 0, sizeof(LogicalSwitchData)); + eeDirty(EE_MODEL); + } +} + +void menuModelLogicalSwitches(evt_t event) +{ + INCDEC_DECLARE_VARS(EE_MODEL); + + MENU(STR_MENULOGICALSWITCHES, menuTabModel, e_LogicalSwitches, NUM_LOGICAL_SWITCH, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|LS_FIELD_LAST/*repeated...*/} ); + + int k = 0; + int sub = m_posVert; + horzpos_t horz = m_posHorz; + + if (horz>=0) { + displayColumnHeader(STR_CSW_HEADERS, horz); + } + + if (horz<0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) { + killEvents(event); + LogicalSwitchData * cs = lswAddress(sub); + if (cs->func) + MENU_ADD_ITEM(STR_COPY); + if (clipboard.type == CLIPBOARD_TYPE_CUSTOM_SWITCH) + MENU_ADD_ITEM(STR_PASTE); + if (cs->func || cs->v1 || cs->v2 || cs->delay || cs->duration || cs->andsw) + MENU_ADD_ITEM(STR_CLEAR); + menuHandler = onLogicalSwitchesMenu; + } + + for (int i=0; i0) ? BLINK|INVERS : INVERS) : 0); + LcdFlags attr1 = (horz==1 ? attr : 0); + LcdFlags attr2 = (horz==2 ? attr : 0); + LogicalSwitchData * cs = lswAddress(k); + + // CSW name + unsigned int sw = SWSRC_SW1+k; + putsSwitches(MENU_TITLE_LEFT, y, sw, (getSwitch(sw) ? 0/*BOLD*/ : 0) | ((sub==k && CURSOR_ON_LINE()) ? INVERS : 0)); + + // CSW func + lcd_putsiAtt(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); + + // CSW params + unsigned int cstate = lswFamily(cs->func); + int v1_val=cs->v1, v1_min=0, v1_max=MIXSRC_LAST_TELEM; + int v2_min=0, v2_max=MIXSRC_LAST_TELEM; + int v3_min=-1, v3_max=100; + + if (cstate == LS_FAMILY_BOOL || cstate == LS_FAMILY_STICKY) { + putsSwitches(CSW_2ND_COLUMN, y, cs->v1, attr1); + putsSwitches(CSW_3RD_COLUMN, y, cs->v2, attr2); + v1_min = SWSRC_FIRST_IN_LOGICAL_SWITCHES; v1_max = SWSRC_LAST_IN_LOGICAL_SWITCHES; + v2_min = SWSRC_FIRST_IN_LOGICAL_SWITCHES; v2_max = SWSRC_LAST_IN_LOGICAL_SWITCHES; + INCDEC_SET_FLAG(EE_MODEL | INCDEC_SWITCH); + INCDEC_ENABLE_CHECK(isSwitchAvailableInLogicalSwitches); + } + else if (cstate == LS_FAMILY_EDGE) { + putsSwitches(CSW_2ND_COLUMN, y, cs->v1, attr1); + putsEdgeDelayParam(CSW_3RD_COLUMN, y, cs, attr2, (horz==LS_FIELD_V3 ? attr : 0)); + v1_min = SWSRC_FIRST_IN_LOGICAL_SWITCHES; v1_max = SWSRC_LAST_IN_LOGICAL_SWITCHES; + v2_min=-129; v2_max = 122; + v3_max = 222 - cs->v2; + if (horz == 1) { + INCDEC_SET_FLAG(EE_MODEL | INCDEC_SWITCH); + INCDEC_ENABLE_CHECK(isSwitchAvailableInLogicalSwitches); + } + else { + INCDEC_SET_FLAG(EE_MODEL); + INCDEC_ENABLE_CHECK(NULL); + } + } + else if (cstate == LS_FAMILY_COMP) { + v1_val = (uint8_t)cs->v1; + putsMixerSource(CSW_2ND_COLUMN, y, v1_val, attr1); + putsMixerSource(CSW_3RD_COLUMN, y, cs->v2, attr2); + INCDEC_SET_FLAG(EE_MODEL | INCDEC_SOURCE); + INCDEC_ENABLE_CHECK(isSourceAvailable); + } + else if (cstate == LS_FAMILY_TIMER) { + lcd_outdezAtt(CSW_2ND_COLUMN, y, lswTimerValue(cs->v1), LEFT|PREC1|attr1); + lcd_outdezAtt(CSW_3RD_COLUMN, y, lswTimerValue(cs->v2), LEFT|PREC1|attr2); + v1_min = v2_min = -128; + v1_max = v2_max = 122; + INCDEC_SET_FLAG(EE_MODEL); + INCDEC_ENABLE_CHECK(NULL); + } + else { + v1_val = (uint8_t)cs->v1; + putsMixerSource(CSW_2ND_COLUMN, y, v1_val, attr1); + if (horz == 1) { + INCDEC_SET_FLAG(EE_MODEL | INCDEC_SOURCE); + INCDEC_ENABLE_CHECK(isSourceAvailableInCustomSwitches); + } + else { + INCDEC_SET_FLAG(EE_MODEL); + INCDEC_ENABLE_CHECK(NULL); + } + v2_max = getMaximumValue(v1_val); + v2_min = - v2_min; + putsChannelValue(CSW_3RD_COLUMN, y, v1_val, v1_val <= MIXSRC_LAST_CH ? calc100toRESX(cs->v2) : cs->v2, LEFT|attr2); + } + + // CSW AND switch + putsSwitches(CSW_4TH_COLUMN, y, cs->andsw, horz==LS_FIELD_ANDSW ? attr : 0); + + // CSW duration + if (cs->duration > 0) + lcd_outdezAtt(CSW_5TH_COLUMN, y, cs->duration, (horz==LS_FIELD_DURATION ? attr : 0)|PREC1|LEFT); + else + lcd_putsiAtt(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); + + // CSW delay + if (cstate == LS_FAMILY_EDGE) { + lcd_puts(CSW_6TH_COLUMN, y, STR_NA); + if (attr && horz == LS_FIELD_DELAY) { + REPEAT_LAST_CURSOR_MOVE(); + } + } + else if (cs->delay > 0) { + lcd_outdezAtt(CSW_6TH_COLUMN, y, cs->delay, (horz==LS_FIELD_DELAY ? attr : 0)|PREC1|LEFT); + } + else { + lcd_putsiAtt(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); + } + + if (attr && horz == LS_FIELD_V3 && cstate != LS_FAMILY_EDGE) { + REPEAT_LAST_CURSOR_MOVE(); + } + + if (s_editMode>0 && attr) { + switch (horz) { + case LS_FIELD_FUNCTION: + { + cs->func = checkIncDec(event, cs->func, 0, LS_FUNC_MAX, EE_MODEL, isLogicalSwitchFunctionAvailable); + uint8_t new_cstate = lswFamily(cs->func); + if (cstate != new_cstate) { + if (new_cstate == LS_FAMILY_TIMER) { + cs->v1 = cs->v2 = -119; + } + else if (new_cstate == LS_FAMILY_EDGE) { + cs->v1 = 0; cs->v2 = -129; cs->v3 = 0; + } + else { + cs->v1 = cs->v2 = 0; + } + } + break; + } + case LS_FIELD_V1: + cs->v1 = CHECK_INCDEC_PARAM(event, v1_val, v1_min, v1_max); + break; + case LS_FIELD_V2: + cs->v2 = CHECK_INCDEC_PARAM(event, cs->v2, v2_min, v2_max); + if (cstate==LS_FAMILY_OFS && cs->v1!=0 && event==EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + getvalue_t x = getValue(v1_val); + if (v1_val <= MIXSRC_LAST_CH) + cs->v2 = calcRESXto100(x); + eeDirty(EE_MODEL); + } + break; + case LS_FIELD_V3: + cs->v3 = CHECK_INCDEC_PARAM(event, cs->v3, v3_min, v3_max); + break; + case LS_FIELD_ANDSW: + INCDEC_SET_FLAG(EE_MODEL | INCDEC_SWITCH); + INCDEC_ENABLE_CHECK(isSwitchAvailableInLogicalSwitches); + cs->andsw = CHECK_INCDEC_PARAM(event, cs->andsw, -MAX_LS_ANDSW, MAX_LS_ANDSW); + break; + case LS_FIELD_DURATION: + CHECK_INCDEC_MODELVAR_ZERO(event, cs->duration, MAX_LS_DURATION); + break; + case LS_FIELD_DELAY: + CHECK_INCDEC_MODELVAR_ZERO(event, cs->delay, MAX_LS_DELAY); + break; + } + } + } +} diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp new file mode 100644 index 000000000..82d604c65 --- /dev/null +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -0,0 +1,293 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define POS_MODEL_NAME 35 +#define POS_MODEL_SIZE 170 + +void onModelSelectMenu(const char *result) +{ + int sub = m_posVert; + + if (result == STR_SELECT_MODEL || result == STR_CREATE_MODEL) { + selectModel(sub); + } + else if (result == STR_COPY_MODEL) { + s_copyMode = COPY_MODE; + s_copyTgtOfs = 0; + s_copySrcRow = -1; + } + else if (result == STR_MOVE_MODEL) { + s_copyMode = MOVE_MODE; + s_copyTgtOfs = 0; + s_copySrcRow = -1; + } + else if (result == STR_BACKUP_MODEL) { + eeCheck(true); // force writing of current model data before this is changed + POPUP_WARNING(eeBackupModel(sub)); + } + else if (result == STR_RESTORE_MODEL || result == STR_UPDATE_LIST) { + if (!listSdFiles(MODELS_PATH, MODELS_EXT, MENU_LINE_LENGTH-1, NULL)) { + POPUP_WARNING(STR_NO_MODELS_ON_SD); + s_menu_flags = 0; + } + } + else if (result == STR_DELETE_MODEL) { + POPUP_CONFIRMATION(STR_DELETEMODEL); + SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); + } + else { + // The user choosed a file on SD to restore + POPUP_WARNING(eeRestoreModel(sub, (char *)result)); + if (!s_warning && g_eeGeneral.currModel == sub) + eeLoadModel(sub); + } +} + +char *strAppendModelSize(char *dest, int size) +{ + char s[8] = "---"; + if (size) sprintf(s, "%d", size); + dest = strSetCursor(dest, POS_MODEL_SIZE-getTextWidth(s)); + return strAppend(dest, s); +} + +void menuModelSelect(evt_t event) +{ + if (s_warning_result) { + s_warning_result = 0; + eeDeleteModel(m_posVert); // delete file + s_copyMode = 0; + event = EVT_ENTRY_UP; + } + + evt_t _event_ = event; + event = (IS_ROTARY_BREAK(event) || IS_ROTARY_LONG(event) ? 0 : event); + if ((s_copyMode && EVT_KEY_MASK(event) == KEY_EXIT) || event == EVT_KEY_BREAK(KEY_EXIT)) { + event = 0; + } + + int8_t oldSub = m_posVert; + + // TODO SIMPLE_MENU(STR_MENUMODELSEL, menuTabModel, e_ModelSelect, MAX_MODELS, DEFAULT_SCROLLBAR_X); + + if (s_editMode > 0) s_editMode = 0; + + int8_t sub = m_posVert; + + switch (_event_) + { + case EVT_ENTRY: + m_posVert = sub = g_eeGeneral.currModel; + if (sub >= NUM_BODY_LINES) s_pgOfs = sub-(NUM_BODY_LINES-1); + s_copyMode = 0; + s_editMode = EDIT_MODE_INIT; + eeCheck(true); + break; + case EVT_KEY_LONG(KEY_EXIT): + if (s_copyMode && s_copyTgtOfs == 0 && g_eeGeneral.currModel != sub && eeModelExists(sub)) { + POPUP_CONFIRMATION(STR_DELETEMODEL); + SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); + killEvents(_event_); + break; + } + // no break + case EVT_KEY_BREAK(KEY_EXIT): + if (s_copyMode) { + sub = m_posVert = (s_copyMode == MOVE_MODE || s_copySrcRow<0) ? (MAX_MODELS+sub+s_copyTgtOfs) % MAX_MODELS : s_copySrcRow; + s_copyMode = 0; + } + else { + if (m_posVert != g_eeGeneral.currModel) { + m_posVert = g_eeGeneral.currModel; + s_pgOfs = 0; + } + else if (_event_ != EVT_KEY_LONG(KEY_EXIT)) { + popMenu(); + } + } + break; + case EVT_KEY_LONG(KEY_ENTER): + case EVT_KEY_BREAK(KEY_ENTER): + s_editMode = 0; + if (READ_ONLY()) { + if (g_eeGeneral.currModel != sub && eeModelExists(sub)) { + selectModel(sub); + } + } + else if (s_copyMode && (s_copyTgtOfs || s_copySrcRow>=0)) { + eeCheck(true); // force writing of current model data before this is changed + uint8_t cur = (MAX_MODELS + sub + s_copyTgtOfs) % MAX_MODELS; + if (s_copyMode == COPY_MODE) { + if (!eeCopyModel(cur, s_copySrcRow)) { + cur = sub; + } + } + + s_copySrcRow = g_eeGeneral.currModel; // to update the currModel value + while (sub != cur) { + uint8_t src = cur; + cur = (s_copyTgtOfs > 0 ? cur+MAX_MODELS-1 : cur+1) % MAX_MODELS; + eeSwapModels(src, cur); + if (src == s_copySrcRow) + s_copySrcRow = cur; + else if (cur == s_copySrcRow) + s_copySrcRow = src; + } + + if (s_copySrcRow != g_eeGeneral.currModel) { + g_eeGeneral.currModel = s_copySrcRow; + eeDirty(EE_GENERAL); + } + + s_copyMode = 0; + _event_ = EVT_ENTRY_UP; + } + else if (_event_ == EVT_KEY_LONG(KEY_ENTER)) { + s_copyMode = 0; + killEvents(_event_); + if (g_eeGeneral.currModel != sub) { + if (eeModelExists(sub)) { + MENU_ADD_ITEM(STR_SELECT_MODEL); + MENU_ADD_SD_ITEM(STR_BACKUP_MODEL); + MENU_ADD_ITEM(STR_COPY_MODEL); + MENU_ADD_ITEM(STR_MOVE_MODEL); + MENU_ADD_ITEM(STR_DELETE_MODEL); + } + else { + MENU_ADD_ITEM(STR_CREATE_MODEL); + MENU_ADD_ITEM(STR_RESTORE_MODEL); + } + } + else { + MENU_ADD_SD_ITEM(STR_BACKUP_MODEL); + MENU_ADD_ITEM(STR_COPY_MODEL); + MENU_ADD_ITEM(STR_MOVE_MODEL); + } + menuHandler = onModelSelectMenu; + } + else if (eeModelExists(sub)) { + s_copyMode = (s_copyMode == COPY_MODE ? MOVE_MODE : COPY_MODE); + s_copyTgtOfs = 0; + s_copySrcRow = -1; + } + break; + +#if 0 + case EVT_KEY_BREAK(KEY_PAGE): + case EVT_KEY_LONG(KEY_PAGE): + chainMenu(_event_ == EVT_KEY_BREAK(KEY_PAGE) ? menuModelSetup : menuTabModel[DIM(menuTabModel)-1]); + killEvents(_event_); + break; +#endif + + case EVT_KEY_FIRST(KEY_MOVE_UP): + case EVT_KEY_REPT(KEY_MOVE_UP): + case EVT_KEY_FIRST(KEY_MOVE_DOWN): + case EVT_KEY_REPT(KEY_MOVE_DOWN): + if (s_copyMode) { + int8_t next_ofs = s_copyTgtOfs + oldSub - m_posVert; + if (next_ofs == MAX_MODELS || next_ofs == -MAX_MODELS) + next_ofs = 0; + + if (s_copySrcRow < 0 && s_copyMode==COPY_MODE) { + s_copySrcRow = oldSub; + // find a hole (in the first empty slot above / below) + sub = eeFindEmptyModel(s_copySrcRow, IS_ROTARY_DOWN(_event_) || _event_==EVT_KEY_FIRST(KEY_MOVE_DOWN)); + if (sub < 0) { + // no free room for duplicating the model + AUDIO_ERROR(); + sub = oldSub; + s_copyMode = 0; + } + next_ofs = 0; + m_posVert = sub; + } + s_copyTgtOfs = next_ofs; + } + break; + } + + for (int i=0; i= 0)) { + if (k == sub) { + if (s_copyMode == COPY_MODE) { + k = s_copySrcRow; + // lcd_rect(MENU_TITLE_LEFT-5, y-3, 170, 16, SOLID, YELLOW); + } + else { + k = sub + s_copyTgtOfs; + } + } + else if (s_copyTgtOfs < 0 && ((k < sub && k >= sub+s_copyTgtOfs) || (k-MAX_MODELS < sub && k-MAX_MODELS >= sub+s_copyTgtOfs))) + k += 1; + else if (s_copyTgtOfs > 0 && ((k > sub && k <= sub+s_copyTgtOfs) || (k+MAX_MODELS > sub && k+MAX_MODELS <= sub+s_copyTgtOfs))) + k += MAX_MODELS-1; + } + + k %= MAX_MODELS; + + if (eeModelExists(k)) { + strcat_modelname(str, k); + lcd_putsAtt(POS_MODEL_NAME, y, str, flags); + if (k==g_eeGeneral.currModel && (s_copyMode!=COPY_MODE || s_copySrcRow<0 || i+s_pgOfs!=(vertpos_t)sub)) { + lcd_puts(1, y, "*"); + } + } + else { + lcd_putsAtt(POS_MODEL_NAME, y, "---", flags); + } + + if (s_copyMode && (vertpos_t)sub==i+s_pgOfs) { + lcd_rect(MENU_TITLE_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, s_copyMode == COPY_MODE ? SOLID : DOTTED, WARNING_COLOR); + } + } + + // TODO lcdDrawBitmap(200, 67, logos, 60*g_model.header.bitmap, 60); +} diff --git a/radio/src/gui/Horus/menu_model_setup.cpp b/radio/src/gui/Horus/menu_model_setup.cpp new file mode 100644 index 000000000..a322b6e38 --- /dev/null +++ b/radio/src/gui/Horus/menu_model_setup.cpp @@ -0,0 +1,741 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +uint8_t g_moduleIdx; +void menuModelFailsafe(evt_t event); + +enum menuModelSetupItems { + ITEM_MODEL_NAME, + ITEM_MODEL_BITMAP, + ITEM_MODEL_TIMER1, + ITEM_MODEL_TIMER1_NAME, + ITEM_MODEL_TIMER1_PERSISTENT, + ITEM_MODEL_TIMER1_MINUTE_BEEP, + ITEM_MODEL_TIMER1_COUNTDOWN_BEEP, +#if TIMERS > 1 + ITEM_MODEL_TIMER2, + ITEM_MODEL_TIMER2_NAME, + ITEM_MODEL_TIMER2_PERSISTENT, + ITEM_MODEL_TIMER2_MINUTE_BEEP, + ITEM_MODEL_TIMER2_COUNTDOWN_BEEP, +#endif +#if TIMERS > 2 + ITEM_MODEL_TIMER3, + ITEM_MODEL_TIMER3_NAME, + ITEM_MODEL_TIMER3_PERSISTENT, + ITEM_MODEL_TIMER3_MINUTE_BEEP, + ITEM_MODEL_TIMER3_COUNTDOWN_BEEP, +#endif + ITEM_MODEL_EXTENDED_LIMITS, + ITEM_MODEL_EXTENDED_TRIMS, + ITEM_MODEL_DISPLAY_TRIMS, + ITEM_MODEL_TRIM_INC, + ITEM_MODEL_THROTTLE_LABEL, + // ITEM_MODEL_THROTTLE_REVERSED, + ITEM_MODEL_THROTTLE_TRACE, + ITEM_MODEL_THROTTLE_TRIM, + ITEM_MODEL_PREFLIGHT_LABEL, + ITEM_MODEL_CHECKLIST_DISPLAY, + ITEM_MODEL_THROTTLE_WARNING, + ITEM_MODEL_SWITCHES_WARNING, + ITEM_MODEL_POT_WARNING, + ITEM_MODEL_BEEP_CENTER, + ITEM_MODEL_USE_GLOBAL_FUNCTIONS, + ITEM_MODEL_EXTERNAL_MODULE_LABEL, + ITEM_MODEL_EXTERNAL_MODULE_MODE, + ITEM_MODEL_EXTERNAL_MODULE_CHANNELS, + ITEM_MODEL_EXTERNAL_MODULE_BIND, + ITEM_MODEL_EXTERNAL_MODULE_FAILSAFE, + ITEM_MODEL_TRAINER_LABEL, + ITEM_MODEL_TRAINER_MODE, + ITEM_MODEL_TRAINER_CHANNELS, + ITEM_MODEL_TRAINER_SETTINGS, + ITEM_MODEL_SETUP_MAX +}; + +#define FIELD_PROTOCOL_MAX 1 + +#define MODEL_SETUP_2ND_COLUMN 200 +#define MODEL_SETUP_BIND_OFS 20 +#define MODEL_SETUP_RANGE_OFS 40 +#define MODEL_SETUP_SET_FAILSAFE_OFS 60 + +void copySelection(char *dst, const char *src, uint8_t size) +{ + if (memcmp(src, "---", 3) == 0) + memset(dst, 0, size); + else + memcpy(dst, src, size); +} + +void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, evt_t event) +{ + TimerData * timer = &g_model.timers[timerIdx]; + if (attr && m_posHorz < 0) { + lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 80+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + } + putsStrIdx(MENU_TITLE_LEFT, y, STR_TIMER, timerIdx+1); + putsTimerMode(MODEL_SETUP_2ND_COLUMN, y, timer->mode, (m_posHorz<=0 ? attr : 0)); + putsTimer(MODEL_SETUP_2ND_COLUMN+50, y, timer->start, (m_posHorz!=0 ? attr : 0)); + if (attr && s_editMode>0) { + switch (m_posHorz) { + case 0: + { + int8_t timerMode = timer->mode; + if (timerMode < 0) timerMode -= TMRMODE_COUNT-1; + CHECK_INCDEC_MODELVAR_CHECK(event, timerMode, -TMRMODE_COUNT-SWSRC_LAST+1, TMRMODE_COUNT+SWSRC_LAST-1, isSwitchAvailableInTimers); + if (timerMode < 0) timerMode += TMRMODE_COUNT-1; + timer->mode = timerMode; +#if defined(AUTOSWITCH) + if (s_editMode>0) { + int8_t val = timer->mode - (TMRMODE_COUNT-1); + int8_t switchVal = checkIncDecMovedSwitch(val); + if (val != switchVal) { + timer->mode = switchVal + (TMRMODE_COUNT-1); + eeDirty(EE_MODEL); + } + } +#endif + break; + } + case 1: + { + const int stopsMinutes[] = { 8, 60, 120, 180, 240, 300, 600, 900, 1200 }; + timer->start = checkIncDec(event, timer->start, 0, 60*60, EE_MODEL, NULL, (const CheckIncDecStops&)stopsMinutes); + break; + } + } + } +} + +#define CURRENT_MODULE_EDITED(k) (k>=ITEM_MODEL_TRAINER_LABEL ? TRAINER_MODULE : EXTERNAL_MODULE) + +int getSwitchWarningsCount() +{ + int count = 0; + for (int i=0; i> 6) ? (uint8_t)NUM_POTS : (uint8_t)0) + #define TIMER_ROWS NAVIGATION_LINE_BY_LINE|1, 0, CASE_PERSISTENT_TIMERS(0) 0, 0 + #if TIMERS == 1 + #define TIMERS_ROWS TIMER_ROWS + #elif TIMERS == 2 + #define TIMERS_ROWS TIMER_ROWS, TIMER_ROWS + #elif TIMERS == 3 + #define TIMERS_ROWS TIMER_ROWS, TIMER_ROWS, TIMER_ROWS + #endif + bool CURSOR_ON_CELL = (m_posHorz >= 0); + + MENU(STR_MENUSETUP, menuTabModel, e_ModelSetup, ITEM_MODEL_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 0, 0, TIMERS_ROWS, 0, 1, 0, 0, LABEL(Throttle), 0, 0, LABEL(PreflightCheck), 0, 0, uint8_t(NAVIGATION_LINE_BY_LINE|(getSwitchWarningsCount()-1)), POT_WARN_ITEMS(), NAVIGATION_LINE_BY_LINE|(NUM_STICKS+NUM_POTS+NUM_ROTARY_ENCODERS-1), 0, LABEL(ExternalModule), (IS_MODULE_XJT(EXTERNAL_MODULE) || IS_MODULE_DSM2(EXTERNAL_MODULE)) ? (uint8_t)1 : (uint8_t)0, EXTERNAL_MODULE_CHANNELS_ROWS(), (IS_MODULE_XJT(EXTERNAL_MODULE) && IS_D8_RX(EXTERNAL_MODULE)) ? (uint8_t)1 : (IS_MODULE_PPM(EXTERNAL_MODULE) || IS_MODULE_XJT(EXTERNAL_MODULE) || IS_MODULE_DSM2(EXTERNAL_MODULE)) ? (uint8_t)2 : HIDDEN_ROW, IF_EXTERNAL_MODULE_XJT(FAILSAFE_ROWS(EXTERNAL_MODULE)), LABEL(Trainer), 0, TRAINER_CHANNELS_ROWS(), IF_TRAINER_ON(2) }); + +#if (defined(DSM2) || defined(PXX)) + if (menuEvent) { + moduleFlag[0] = 0; +#if NUM_MODULES > 1 + moduleFlag[1] = 0; +#endif + } +#endif + + int sub = m_posVert; + + for (int i=0; i0) ? BLINK|INVERS : INVERS); + LcdFlags attr = (sub == k ? blink : 0); + + switch(k) { + case ITEM_MODEL_NAME: + editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_MODELNAME, g_model.header.name, sizeof(g_model.header.name), event, attr); + memcpy(modelHeaders[g_eeGeneral.currModel].name, g_model.header.name, sizeof(g_model.header.name)); + break; + + case ITEM_MODEL_BITMAP: + lcd_putsLeft(y, STR_BITMAP); + lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN, y, g_model.header.bitmap, LEFT|attr); + if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.bitmap, 3); + break; + + case ITEM_MODEL_TIMER1: + editTimerMode(0, y, attr, event); + break; + + case ITEM_MODEL_TIMER1_NAME: + editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER_NAME, g_model.timers[0].name, LEN_TIMER_NAME, event, attr); + break; + + case ITEM_MODEL_TIMER1_MINUTE_BEEP: + g_model.timers[0].minuteBeep = onoffMenuItem(g_model.timers[0].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, STR_MINUTEBEEP, attr, event); + break; + + case ITEM_MODEL_TIMER1_COUNTDOWN_BEEP: + g_model.timers[0].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_BEEPCOUNTDOWN, STR_VBEEPCOUNTDOWN, g_model.timers[0].countdownBeep, 0, 2, attr, event); + break; + + case ITEM_MODEL_TIMER1_PERSISTENT: + g_model.timers[0].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_PERSISTENT, STR_VPERSISTENT, g_model.timers[0].persistent, 0, 2, attr, event); + break; + +#if TIMERS > 1 + case ITEM_MODEL_TIMER2: + editTimerMode(1, y, attr, event); + break; + + case ITEM_MODEL_TIMER2_NAME: + editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER_NAME, g_model.timers[1].name, LEN_TIMER_NAME, event, attr); + break; + + case ITEM_MODEL_TIMER2_MINUTE_BEEP: + g_model.timers[1].minuteBeep = onoffMenuItem(g_model.timers[1].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, STR_MINUTEBEEP, attr, event); + break; + + case ITEM_MODEL_TIMER2_COUNTDOWN_BEEP: + g_model.timers[1].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_BEEPCOUNTDOWN, STR_VBEEPCOUNTDOWN, g_model.timers[1].countdownBeep, 0, 2, attr, event); + break; + + case ITEM_MODEL_TIMER2_PERSISTENT: + g_model.timers[1].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_PERSISTENT, STR_VPERSISTENT, g_model.timers[1].persistent, 0, 2, attr, event); + break; +#endif + +#if TIMERS > 2 + case ITEM_MODEL_TIMER3: + editTimerMode(2, y, attr, event); + break; + + case ITEM_MODEL_TIMER3_NAME: + editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER_NAME, g_model.timers[2].name, LEN_TIMER_NAME, event, attr); + break; + + case ITEM_MODEL_TIMER3_MINUTE_BEEP: + g_model.timers[2].minuteBeep = onoffMenuItem(g_model.timers[2].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, STR_MINUTEBEEP, attr, event); + break; + + case ITEM_MODEL_TIMER3_COUNTDOWN_BEEP: + g_model.timers[2].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_BEEPCOUNTDOWN, STR_VBEEPCOUNTDOWN, g_model.timers[2].countdownBeep, 0, 2, attr, event); + break; + + case ITEM_MODEL_TIMER3_PERSISTENT: + g_model.timers[2].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_PERSISTENT, STR_VPERSISTENT, g_model.timers[2].persistent, 0, 2, attr, event); + break; +#endif + + case ITEM_MODEL_EXTENDED_LIMITS: + ON_OFF_MENU_ITEM(g_model.extendedLimits, MODEL_SETUP_2ND_COLUMN, y, STR_ELIMITS, attr, event); + break; + + case ITEM_MODEL_EXTENDED_TRIMS: + ON_OFF_MENU_ITEM(g_model.extendedTrims, MODEL_SETUP_2ND_COLUMN, y, STR_ETRIMS, m_posHorz<=0 ? attr : 0, event==EVT_KEY_BREAK(KEY_ENTER) ? event : 0); + lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+18, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); + if (attr && m_posHorz>0) { + s_editMode = 0; + if (event==EVT_KEY_LONG(KEY_ENTER)) { + s_noHi = NO_HI_LEN; + for (uint8_t i=0; i MIXSRC_Thr) + idx += 1; + if (idx >= MIXSRC_FIRST_POT+NUM_POTS) + idx += MIXSRC_CH1 - MIXSRC_FIRST_POT - NUM_POTS; + putsMixerSource(MODEL_SETUP_2ND_COLUMN, y, idx, attr); + break; + } + + case ITEM_MODEL_THROTTLE_TRIM: + ON_OFF_MENU_ITEM(g_model.thrTrim, MODEL_SETUP_2ND_COLUMN, y, STR_TTRIM, attr, event); + break; + + case ITEM_MODEL_PREFLIGHT_LABEL: + lcd_putsLeft(y, STR_PREFLIGHT); + break; + + case ITEM_MODEL_CHECKLIST_DISPLAY: + ON_OFF_MENU_ITEM(g_model.displayChecklist, MODEL_SETUP_2ND_COLUMN, y, STR_CHECKLIST, attr, event); + break; + + case ITEM_MODEL_THROTTLE_WARNING: + g_model.disableThrottleWarning = !onoffMenuItem(!g_model.disableThrottleWarning, MODEL_SETUP_2ND_COLUMN, y, STR_THROTTLEWARNING, attr, event); + break; + + case ITEM_MODEL_SWITCHES_WARNING: + { + lcd_putsLeft(y, STR_SWITCHWARNING); + if (!READ_ONLY() && attr && m_posHorz<0 && event==EVT_KEY_LONG(KEY_ENTER)) { + killEvents(event); + s_noHi = NO_HI_LEN; + getMovedSwitch(); + g_model.switchWarningState = switches_states; + AUDIO_WARNING1(); + eeDirty(EE_MODEL); + } + + if (attr && m_posHorz < 0) { + lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, NUM_SWITCHES*18+INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + } + + unsigned int newStates = 0; + for (int i=0, current=0; i> (3*i)) & 0x07); + LcdFlags color = (state > 0 ? TEXT_COLOR : TEXT_DISABLE_COLOR); + if (attr && m_posHorz < 0) { + color |= INVERS; + } + char s[3]; + s[0] = 'A' + i + (i >= 3 ? 1 : 0 /* skip SD which is a POT*/); + int max; + if (state == 0) { + s[1] = 'x'; + max = 2; + } + else if (i == 2) { + s[1] = '0' + state; + max = 6; + } + else if (IS_3POS(i)) { + s[1] = "\300-\301"[state-1]; + max = 3; + } + else { + s[1] = "\300\301"[state-1]; + max = 2; + } + s[2] = '\0'; + lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+i*18, y, s, color|(m_posHorz==current ? attr : 0)); + if (attr && m_posHorz==current) CHECK_INCDEC_MODELVAR_ZERO(event, state, max); + newStates |= (state << (3*i)); + ++current; + } + } + g_model.switchWarningState = newStates; + break; + } + + case ITEM_MODEL_POT_WARNING: + { + lcd_putsLeft(y, STR_POTWARNING); + if (attr) { + if (m_posHorz) s_editMode = 0; + if (!READ_ONLY() && m_posHorz) { + switch (event) { + case EVT_KEY_LONG(KEY_ENTER): + killEvents(event); + if (g_model.potsWarnMode == POTS_WARN_MANUAL) { + SAVE_POT_POSITION(m_posHorz-1); + AUDIO_WARNING1(); + eeDirty(EE_MODEL); + } + break; + case EVT_KEY_BREAK(KEY_ENTER): + g_model.potsWarnEnabled ^= (1 << (m_posHorz-1)); + eeDirty(EE_MODEL); + break; + } + } + } + lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, PSTR("\004""OFF\0""Man\0""Auto"), g_model.potsWarnMode, (m_posHorz == 0) ? attr : 0); + if (g_model.potsWarnMode) { + coord_t x = MODEL_SETUP_2ND_COLUMN+30; + for (int i=0; i0) { + switch (m_posHorz) { + case 0: + g_model.externalModule = checkIncDec(event, g_model.externalModule, MODULE_TYPE_NONE, MODULE_TYPE_COUNT-1, EE_MODEL, isModuleAvailable); + if (checkIncDec_Ret) { + g_model.moduleData[EXTERNAL_MODULE].rfProtocol = 0; + g_model.moduleData[EXTERNAL_MODULE].channelsStart = 0; + if (g_model.externalModule == MODULE_TYPE_PPM) + g_model.moduleData[EXTERNAL_MODULE].channelsCount = 0; + else + g_model.moduleData[EXTERNAL_MODULE].channelsCount = MAX_EXTERNAL_MODULE_CHANNELS(); + } + break; + case 1: + if (IS_MODULE_DSM2(EXTERNAL_MODULE)) + CHECK_INCDEC_MODELVAR(event, g_model.moduleData[EXTERNAL_MODULE].rfProtocol, DSM2_PROTO_LP45, DSM2_PROTO_DSMX); + else + CHECK_INCDEC_MODELVAR(event, g_model.moduleData[EXTERNAL_MODULE].rfProtocol, RF_PROTO_X16, RF_PROTO_LAST); + if (checkIncDec_Ret) { + g_model.moduleData[EXTERNAL_MODULE].channelsStart = 0; + g_model.moduleData[EXTERNAL_MODULE].channelsCount = MAX_EXTERNAL_MODULE_CHANNELS(); + } + } + } + break; + + case ITEM_MODEL_TRAINER_LABEL: + lcd_putsLeft(y, STR_TRAINER); + break; + + case ITEM_MODEL_EXTERNAL_MODULE_CHANNELS: + case ITEM_MODEL_TRAINER_CHANNELS: + { + uint8_t moduleIdx = CURRENT_MODULE_EDITED(k); + ModuleData & moduleData = g_model.moduleData[moduleIdx]; + lcd_putsLeft(y, STR_CHANNELRANGE); + if ((int8_t)PORT_CHANNELS_ROWS(moduleIdx) >= 0) { + putsStrIdx(MODEL_SETUP_2ND_COLUMN, y, STR_CH, moduleData.channelsStart+1, m_posHorz==0 ? attr : 0); + lcd_puts(MODEL_SETUP_2ND_COLUMN+30, y, "-"); + putsStrIdx(MODEL_SETUP_2ND_COLUMN+40, y, STR_CH, moduleData.channelsStart+NUM_CHANNELS(moduleIdx), m_posHorz==1 ? attr : 0); + if (attr && s_editMode>0) { + switch (m_posHorz) { + case 0: + CHECK_INCDEC_MODELVAR_ZERO(event, moduleData.channelsStart, 32-8-moduleData.channelsCount); + break; + case 1: + CHECK_INCDEC_MODELVAR(event, moduleData.channelsCount, -4, min(MAX_CHANNELS(moduleIdx), 32-8-moduleData.channelsStart)); + if ((k == ITEM_MODEL_EXTERNAL_MODULE_CHANNELS && g_model.externalModule == MODULE_TYPE_PPM) + || (k == ITEM_MODEL_TRAINER_CHANNELS) + ) + SET_DEFAULT_PPM_FRAME_LENGTH(moduleIdx); + break; + } + } + } + break; + } + + case ITEM_MODEL_EXTERNAL_MODULE_BIND: + case ITEM_MODEL_TRAINER_SETTINGS: + { + uint8_t moduleIdx = CURRENT_MODULE_EDITED(k); + ModuleData & moduleData = g_model.moduleData[moduleIdx]; + if (IS_MODULE_PPM(moduleIdx)) { + lcd_putsLeft(y, STR_PPMFRAME); + lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN, y, (int16_t)moduleData.ppmFrameLength*5 + 225, (m_posHorz<=0 ? attr : 0) | PREC1|LEFT, STR_MS); + lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN+70, y, (moduleData.ppmDelay*50)+300, (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0, "us"); + lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+90, y, moduleData.ppmPulsePol ? "+" : "-", (CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0); + + if (attr && s_editMode>0) { + switch (m_posHorz) { + case 0: + CHECK_INCDEC_MODELVAR(event, moduleData.ppmFrameLength, -20, 35); + break; + case 1: + CHECK_INCDEC_MODELVAR(event, moduleData.ppmDelay, -4, 10); + break; + case 2: + CHECK_INCDEC_MODELVAR_ZERO(event, moduleData.ppmPulsePol, 1); + break; + } + } + } + else { + horzpos_t l_posHorz = m_posHorz; + coord_t xOffsetBind = MODEL_SETUP_BIND_OFS; + if (IS_MODULE_XJT(moduleIdx) && IS_D8_RX(moduleIdx)) { + xOffsetBind = 0; + lcd_putsLeft(y, INDENT "Receiver"); + if (attr) l_posHorz += 1; + } + else { + lcd_putsLeft(y, STR_RXNUM); + } + if (IS_MODULE_XJT(moduleIdx) || IS_MODULE_DSM2(moduleIdx)) { + if (xOffsetBind) lcd_outdezNAtt(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0|LEFT, 2); + if (attr && l_posHorz==0) { + if (s_editMode>0) { + CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], IS_MODULE_DSM2(moduleIdx) ? 20 : 63); + if (checkIncDec_Ret) { + modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx]; + } + } + if (s_editMode==0 && event==EVT_KEY_BREAK(KEY_ENTER)) { + checkModelIdUnique(g_eeGeneral.currModel, moduleIdx); + } + } + lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); + lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); + uint8_t newFlag = 0; + if (attr && l_posHorz>0 && s_editMode>0) { + if (l_posHorz == 1) + newFlag = MODULE_BIND; + else if (l_posHorz == 2) { + newFlag = MODULE_RANGECHECK; + } + } + moduleFlag[moduleIdx] = newFlag; + } + } + break; + } + + case ITEM_MODEL_EXTERNAL_MODULE_FAILSAFE: + { + uint8_t moduleIdx = CURRENT_MODULE_EDITED(k); + ModuleData & moduleData = g_model.moduleData[moduleIdx]; + lcd_putsLeft(y, TR_FAILSAFE); + if (IS_MODULE_XJT(moduleIdx)) { + lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); + if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcd_putsAtt(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); + if (attr) { + if (moduleData.failsafeMode != FAILSAFE_CUSTOM) + m_posHorz = 0; + if (m_posHorz==0) { + if (s_editMode>0) { + CHECK_INCDEC_MODELVAR_ZERO(event, moduleData.failsafeMode, FAILSAFE_LAST); + if (checkIncDec_Ret) SEND_FAILSAFE_NOW(moduleIdx); + } + } + else if (m_posHorz==1) { + s_editMode = 0; + if (moduleData.failsafeMode==FAILSAFE_CUSTOM && event==EVT_KEY_FIRST(KEY_ENTER)) { + g_moduleIdx = moduleIdx; + pushMenu(menuModelFailsafe); + } + } + else { + lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN, y, LCD_W-MODEL_SETUP_2ND_COLUMN-MENUS_SCROLLBAR_WIDTH, 8, TEXT_COLOR); + } + } + } + break; + } + } + } + + if (IS_RANGECHECK_ENABLE()) { + displayPopup("RSSI :"); + lcd_outdezAtt(WARNING_LINE_X, WARNING_INFOLINE_Y, TELEMETRY_RSSI(), DBLSIZE|LEFT); + } +} + +void menuModelFailsafe(evt_t event) +{ + static bool longNames = false; + bool newLongNames = false; + uint8_t ch = 0; + + if (event == EVT_KEY_LONG(KEY_ENTER) && s_editMode) { + s_noHi = NO_HI_LEN; + g_model.moduleData[g_moduleIdx].failsafeChannels[m_posVert] = channelOutputs[m_posVert]; + eeDirty(EE_MODEL); + AUDIO_WARNING1(); + SEND_FAILSAFE_NOW(g_moduleIdx); + } + + SIMPLE_SUBMENU_NOTITLE(NUM_CHNOUT, 0); + + #define COL_W (LCD_W/2) + const uint8_t SLIDER_W = 64; + // Column separator + lcd_vline(LCD_W/2, FH, LCD_H-FH); + + if (m_posVert >= 16) { + ch = 16; + } + + // TODO lcd_putsCenter(0, FAILSAFESET); + // TODO lcd_invert_line(0); + + for (int col=0; col<2; col++) { + coord_t x = col*COL_W+1; + + // Channels + for (int line=0; line<8; line++) { + coord_t y = 9+line*7; + int32_t val; + int ofs = (col ? 0 : 1); + + if (ch < g_model.moduleData[g_moduleIdx].channelsStart || ch >= NUM_CHANNELS(g_moduleIdx) + g_model.moduleData[g_moduleIdx].channelsStart) + val = 0; + else if (s_editMode && m_posVert == ch) + val = channelOutputs[ch]; + else + val = g_model.moduleData[g_moduleIdx].failsafeChannels[8*col+line]; + + // Channel name if present, number if not + uint8_t lenLabel = ZLEN(g_model.limitData[ch].name); + if (lenLabel > 4) { + newLongNames = longNames = true; + } + + if (lenLabel > 0) + lcd_putsnAtt(x+1-ofs, y, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR | SMLSIZE); + else + putsChn(x+1-ofs, y, ch+1, SMLSIZE); + + // Value + LcdFlags flags = TINSIZE; + if (m_posVert == ch && !s_noHi) { + flags |= INVERS; + if (s_editMode) + flags |= BLINK; + } +#if defined(PPM_UNIT_US) + uint8_t wbar = (longNames ? SLIDER_W-10 : SLIDER_W); + lcd_outdezAtt(x+COL_W-4-wbar-ofs, y, PPM_CH_CENTER(ch)+val/2, flags); +#elif defined(PPM_UNIT_PERCENT_PREC1) + uint8_t wbar = (longNames ? SLIDER_W-16 : SLIDER_W-6); + lcd_outdezAtt(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val), PREC1|flags); +#else + uint8_t wbar = (longNames ? SLIDER_W-10 : SLIDER_W); + lcd_outdezAtt(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val)/10, flags); +#endif + + // Gauge + lcd_rect(x+COL_W-3-wbar-ofs, y, wbar+1, 6); + uint16_t lim = g_model.extendedLimits ? 640*2 : 512*2; + uint8_t len = limit((uint8_t)1, uint8_t((abs(val) * wbar/2 + lim/2) / lim), uint8_t(wbar/2)); + coord_t x0 = (val>0) ? x+COL_W-ofs-3-wbar/2 : x+COL_W-ofs-2-wbar/2-len; + lcd_hline(x0, y+1, len); + lcd_hline(x0, y+2, len); + lcd_hline(x0, y+3, len); + lcd_hline(x0, y+4, len); + + ch++; + } + } + + longNames = newLongNames; +} diff --git a/radio/src/gui/Horus/menu_model_telemetry.cpp b/radio/src/gui/Horus/menu_model_telemetry.cpp new file mode 100644 index 000000000..f05412ca5 --- /dev/null +++ b/radio/src/gui/Horus/menu_model_telemetry.cpp @@ -0,0 +1,728 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +enum menuModelTelemetryItems { + ITEM_TELEMETRY_PROTOCOL_TYPE, + ITEM_TELEMETRY_RSSI_LABEL, + ITEM_TELEMETRY_RSSI_ALARM1, + ITEM_TELEMETRY_RSSI_ALARM2, + ITEM_TELEMETRY_SENSORS_LABEL, + ITEM_TELEMETRY_SENSOR1, + ITEM_TELEMETRY_SENSOR2, + ITEM_TELEMETRY_SENSOR3, + ITEM_TELEMETRY_SENSOR4, + ITEM_TELEMETRY_SENSOR5, + ITEM_TELEMETRY_SENSOR6, + ITEM_TELEMETRY_SENSOR7, + ITEM_TELEMETRY_SENSOR8, + ITEM_TELEMETRY_SENSOR9, + ITEM_TELEMETRY_SENSOR10, + ITEM_TELEMETRY_SENSOR11, + ITEM_TELEMETRY_SENSOR12, + ITEM_TELEMETRY_SENSOR13, + ITEM_TELEMETRY_SENSOR14, + ITEM_TELEMETRY_SENSOR15, + ITEM_TELEMETRY_SENSOR16, + ITEM_TELEMETRY_SENSOR17, + ITEM_TELEMETRY_SENSOR18, + ITEM_TELEMETRY_SENSOR19, + ITEM_TELEMETRY_SENSOR20, + ITEM_TELEMETRY_SENSOR21, + ITEM_TELEMETRY_SENSOR22, + ITEM_TELEMETRY_SENSOR23, + ITEM_TELEMETRY_SENSOR24, + ITEM_TELEMETRY_SENSOR25, + ITEM_TELEMETRY_SENSOR26, + ITEM_TELEMETRY_SENSOR27, + ITEM_TELEMETRY_SENSOR28, + ITEM_TELEMETRY_SENSOR29, + ITEM_TELEMETRY_SENSOR30, + ITEM_TELEMETRY_SENSOR31, + ITEM_TELEMETRY_SENSOR32, + ITEM_TELEMETRY_NEWSENSOR, + CASE_VARIO(ITEM_TELEMETRY_VARIO_LABEL) +#if defined(VARIO) + ITEM_TELEMETRY_VARIO_SOURCE, +#endif + CASE_VARIO(ITEM_TELEMETRY_VARIO_RANGE) + ITEM_TELEMETRY_TOP_BAR_LABEL, + ITEM_TELEMETRY_TOP_BAR_VOLTAGE, + ITEM_TELEMETRY_TOP_BAR_ALTITUDE, + ITEM_TELEMETRY_SCREEN_LABEL1, + ITEM_TELEMETRY_SCREEN_LINE1, + ITEM_TELEMETRY_SCREEN_LINE2, + ITEM_TELEMETRY_SCREEN_LINE3, + ITEM_TELEMETRY_SCREEN_LINE4, + ITEM_TELEMETRY_SCREEN_LABEL2, + ITEM_TELEMETRY_SCREEN_LINE5, + ITEM_TELEMETRY_SCREEN_LINE6, + ITEM_TELEMETRY_SCREEN_LINE7, + ITEM_TELEMETRY_SCREEN_LINE8, + ITEM_TELEMETRY_SCREEN_LABEL3, + ITEM_TELEMETRY_SCREEN_LINE9, + ITEM_TELEMETRY_SCREEN_LINE10, + ITEM_TELEMETRY_SCREEN_LINE11, + ITEM_TELEMETRY_SCREEN_LINE12, + ITEM_TELEMETRY_SCREEN_LABEL4, + ITEM_TELEMETRY_SCREEN_LINE13, + ITEM_TELEMETRY_SCREEN_LINE14, + ITEM_TELEMETRY_SCREEN_LINE15, + ITEM_TELEMETRY_SCREEN_LINE16, + ITEM_TELEMETRY_MAX +}; + +#define TELEM_COL1 10 +#define TELEM_COL2 150 +#define TELEM_COL3 200 +#define TELEM_SCRTYPE_COL TELEM_COL2 + +#define IF_FAS_OFFSET(x) x, +#define IS_RANGE_DEFINED(k) (g_model.frsky.channels[k].ratio > 0) + +#define SENSOR_ROWS(x) (isTelemetryFieldAvailable(x) ? (uint8_t)0 : HIDDEN_ROW) +#define SENSORS_ROWS LABEL(Sensors), SENSOR_ROWS(0), SENSOR_ROWS(1), SENSOR_ROWS(2), SENSOR_ROWS(3), SENSOR_ROWS(4), SENSOR_ROWS(5), SENSOR_ROWS(6), SENSOR_ROWS(7), SENSOR_ROWS(8), SENSOR_ROWS(9), SENSOR_ROWS(10), SENSOR_ROWS(11), SENSOR_ROWS(12), SENSOR_ROWS(13), SENSOR_ROWS(14), SENSOR_ROWS(15), SENSOR_ROWS(16), SENSOR_ROWS(17), SENSOR_ROWS(18), SENSOR_ROWS(19), SENSOR_ROWS(20), SENSOR_ROWS(21), SENSOR_ROWS(22), SENSOR_ROWS(23), SENSOR_ROWS(24), SENSOR_ROWS(25), SENSOR_ROWS(26), SENSOR_ROWS(27), SENSOR_ROWS(28), SENSOR_ROWS(29), SENSOR_ROWS(30), SENSOR_ROWS(31), 0, +#define USRDATA_ROWS +#define RSSI_ROWS LABEL(RSSI), 0, 0, +#if defined(LUA) + #define SCREEN_TYPE_ROWS 1 +#else + #define SCREEN_TYPE_ROWS 0 +#endif +#define VARIO_RANGE_ROWS 3 + +#define TELEMETRY_TYPE_ROWS (g_model.externalModule == MODULE_TYPE_PPM ? (uint8_t)0 : HIDDEN_ROW), + +#if defined(LUA) + #define TELEMETRY_SCREEN_LINE(x) ((TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_NONE || TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_SCRIPT) ? HIDDEN_ROW : (uint8_t)2) +#else + #define TELEMETRY_SCREEN_LINE(x) (TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_NONE ? HIDDEN_ROW : (uint8_t)2) +#endif +#define TELEMETRY_SCREEN_ROWS(x) SCREEN_TYPE_ROWS, TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x) +#define TELEMETRY_CURRENT_EDIT_SCREEN(k) (k < ITEM_TELEMETRY_SCREEN_LABEL2 ? 0 : (k < ITEM_TELEMETRY_SCREEN_LABEL3 ? 1 : (k < ITEM_TELEMETRY_SCREEN_LABEL4 ? 2 : 3))) + +enum SensorFields { + SENSOR_FIELD_NAME, + SENSOR_FIELD_TYPE, + SENSOR_FIELD_ID, + SENSOR_FIELD_FORMULA=SENSOR_FIELD_ID, + SENSOR_FIELD_UNIT, + SENSOR_FIELD_PRECISION, + SENSOR_FIELD_PARAM1, + SENSOR_FIELD_PARAM2, + SENSOR_FIELD_PARAM3, + SENSOR_FIELD_PARAM4, + SENSOR_FIELD_AUTOOFFSET, + SENSOR_FIELD_FILTER, + SENSOR_FIELD_PERSISTENT, + SENSOR_FIELD_LOGS, + SENSOR_FIELD_MAX +}; + +bool isSensorUnit(int sensor, uint8_t unit) +{ + if (sensor == 0) + return true; + + sensor -= 1; + + return g_model.telemetrySensors[sensor].unit == unit; +} + +bool isCellsSensor(int sensor) +{ + return isSensorUnit(sensor, UNIT_CELLS); +} + +bool isGPSSensor(int sensor) +{ + return isSensorUnit(sensor, UNIT_GPS); +} + +bool isAltSensor(int sensor) +{ + return isSensorUnit(sensor, UNIT_DIST); +} + +bool isVoltsSensor(int sensor) +{ + return isSensorUnit(sensor, UNIT_VOLTS) || isSensorUnit(sensor, UNIT_CELLS); +} + +bool isCurrentSensor(int sensor) +{ + return isSensorUnit(sensor, UNIT_AMPS); +} + +bool isSensorAvailable(int sensor) +{ + if (sensor == 0) + return true; + else + return isTelemetryFieldAvailable(abs(sensor) - 1); +} + +#define SENSOR_2ND_COLUMN 80 +#define SENSOR_3RD_COLUMN 120 + +#define SENSOR_UNIT_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_PREC_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_PARAM1_ROWS (sensor->unit >= UNIT_FIRST_VIRTUAL ? HIDDEN_ROW : (uint8_t)0) +#define SENSOR_PARAM2_ROWS (sensor->unit == UNIT_RPMS || sensor->unit == UNIT_GPS || sensor->unit == UNIT_DATETIME || sensor->unit == UNIT_CELLS || (sensor->type==TELEM_TYPE_CALCULATED && (sensor->formula==TELEM_FORMULA_CONSUMPTION || sensor->formula==TELEM_FORMULA_TOTALIZE)) ? HIDDEN_ROW : (uint8_t)0) +#define SENSOR_PARAM3_ROWS (sensor->type == TELEM_TYPE_CALCULATED && sensor->formula < TELEM_FORMULA_MULTIPLY) ? (uint8_t)0 : HIDDEN_ROW +#define SENSOR_PARAM4_ROWS (sensor->type == TELEM_TYPE_CALCULATED && sensor->formula < TELEM_FORMULA_MULTIPLY) ? (uint8_t)0 : HIDDEN_ROW +#define SENSOR_AUTOOFFSET_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_FILTER_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_PERSISTENT_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) + +void menuModelSensor(evt_t event) +{ + TelemetrySensor *sensor = &g_model.telemetrySensors[s_currIdx]; + + SUBMENU(STR_MENUSENSOR, SENSOR_FIELD_MAX, DEFAULT_SCROLLBAR_X, { 0, 0, sensor->type == TELEM_TYPE_CALCULATED ? (uint8_t)0 : (uint8_t)1, SENSOR_UNIT_ROWS, SENSOR_PREC_ROWS, SENSOR_PARAM1_ROWS, SENSOR_PARAM2_ROWS, SENSOR_PARAM3_ROWS, SENSOR_PARAM4_ROWS, 0 }); + + lcd_outdezAtt(MENU_TITLE_LEFT+getTextWidth(TR_MENUSENSOR)+5, MENU_FOOTER_TOP, s_currIdx+1, HEADER_COLOR|LEFT); + putsTelemetryChannelValue(SENSOR_2ND_COLUMN, MENU_FOOTER_TOP, s_currIdx, getValue(MIXSRC_FIRST_TELEM+3*s_currIdx), HEADER_COLOR|LEFT); + + int sub = m_posVert; + + for (unsigned int i=0; i0 ? BLINK|INVERS : INVERS) : 0); + + switch (k) { + + case SENSOR_FIELD_NAME: + editSingleName(SENSOR_2ND_COLUMN, y, STR_NAME, sensor->label, TELEM_LABEL_LEN, event, attr); + break; + + case SENSOR_FIELD_TYPE: + sensor->type = selectMenuItem(SENSOR_2ND_COLUMN, y, NO_INDENT(STR_TYPE), STR_VSENSORTYPES, sensor->type, 0, 1, attr, event); + if (attr && checkIncDec_Ret) { + sensor->instance = 0; + if (sensor->type == TELEM_TYPE_CALCULATED) { + sensor->param = 0; + sensor->filter = 0; + sensor->autoOffset = 0; + } + } + break; + + case SENSOR_FIELD_ID: + if (sensor->type == TELEM_TYPE_CUSTOM) { + lcd_putsLeft(y, STR_ID); + lcd_outhex4(SENSOR_2ND_COLUMN, y, sensor->id, LEFT|(m_posHorz==0 ? attr : 0)); + lcd_outdezAtt(SENSOR_3RD_COLUMN, y, sensor->instance, LEFT|(m_posHorz==1 ? attr : 0)); + if (attr) { + switch (m_posHorz) { + case 0: + CHECK_INCDEC_MODELVAR_ZERO(event, sensor->id, 0xffff); + break; + + case 1: + CHECK_INCDEC_MODELVAR_ZERO(event, sensor->instance, 0xff); + break; + } + } + } + else { + sensor->formula = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_FORMULA, STR_VFORMULAS, sensor->formula, 0, TELEM_FORMULA_LAST, attr, event); + if (attr && checkIncDec_Ret) { + sensor->param = 0; + if (sensor->formula == TELEM_FORMULA_CELL) { + sensor->unit = UNIT_VOLTS; + sensor->prec = 2; + } + else if (sensor->formula == TELEM_FORMULA_DIST) { + sensor->unit = UNIT_DIST; + sensor->prec = 0; + } + else if (sensor->formula == TELEM_FORMULA_CONSUMPTION) { + sensor->unit = UNIT_MAH; + sensor->prec = 0; + } + } + } + break; + + case SENSOR_FIELD_UNIT: + lcd_putsLeft(y, STR_UNIT); + // TODO flash saving with selectMenuItem where I copied those 2 lines? + lcd_putsiAtt(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); + if (attr) { + CHECK_INCDEC_MODELVAR_ZERO(event, sensor->unit, UNIT_MAX); + if (checkIncDec_Ret) { + telemetryItems[s_currIdx].clear(); + } + } + break; + + case SENSOR_FIELD_PRECISION: + sensor->prec = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_PRECISION, STR_VPREC, sensor->prec, 0, 2, attr, event); + if (attr && checkIncDec_Ret) { + telemetryItems[s_currIdx].clear(); + } + break; + + case SENSOR_FIELD_PARAM1: + if (sensor->type == TELEM_TYPE_CALCULATED) { + if (sensor->formula == TELEM_FORMULA_CELL) { + lcd_putsLeft(y, STR_CELLSENSOR); + putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->cell.source ? MIXSRC_FIRST_TELEM+3*(sensor->cell.source-1) : 0, attr); + if (attr) { + sensor->cell.source = checkIncDec(event, sensor->cell.source, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isCellsSensor); + } + break; + } + else if (sensor->formula == TELEM_FORMULA_DIST) { + lcd_putsLeft(y, "GPS sensor"); + putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->dist.gps ? MIXSRC_FIRST_TELEM+3*(sensor->dist.gps-1) : 0, attr); + if (attr) { + sensor->dist.gps = checkIncDec(event, sensor->dist.gps, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isGPSSensor); + } + break; + } + else if (sensor->formula == TELEM_FORMULA_CONSUMPTION) { + lcd_putsLeft(y, "Amps sensor"); + putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->consumption.source ? MIXSRC_FIRST_TELEM+3*(sensor->consumption.source-1) : 0, attr); + if (attr) { + sensor->consumption.source = checkIncDec(event, sensor->consumption.source, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isCurrentSensor); + } + break; + } + } + else { + lcd_putsLeft(y, "Ratio"); + if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.ratio, 0, 30000); + if (sensor->custom.ratio == 0) + lcd_putsAtt(SENSOR_2ND_COLUMN, y, "-", attr); + else + lcd_outdezAtt(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); + break; + } + // no break + + case SENSOR_FIELD_PARAM2: + if (sensor->type == TELEM_TYPE_CALCULATED) { + if (sensor->formula == TELEM_FORMULA_CELL) { + sensor->cell.index = selectMenuItem(SENSOR_2ND_COLUMN, y, "Cell index", "\007Lowest\0001\0 2\0 3\0 4\0 5\0 6\0 HighestDelta\0", sensor->cell.index, 0, 8, attr, event); + break; + } + else if (sensor->formula == TELEM_FORMULA_DIST) { + lcd_putsLeft(y, "Alt sensor"); + putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->dist.alt ? MIXSRC_FIRST_TELEM+3*(sensor->dist.alt-1) : 0, attr); + if (attr) { + sensor->dist.alt = checkIncDec(event, sensor->dist.alt, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isAltSensor); + } + break; + } + } + else { + lcd_putsLeft(y, NO_INDENT(STR_OFFSET)); + if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.offset, -30000, +30000); + if (sensor->prec > 0) attr |= (sensor->prec == 2 ? PREC2 : PREC1); + lcd_outdezAtt(SENSOR_2ND_COLUMN, y, sensor->custom.offset, LEFT|attr); + break; + } + // no break + + case SENSOR_FIELD_PARAM3: + // no break + + case SENSOR_FIELD_PARAM4: + { + putsStrIdx(MENU_TITLE_LEFT, y, NO_INDENT(STR_SOURCE), k-SENSOR_FIELD_PARAM1+1); + int8_t & source = sensor->calc.sources[k-SENSOR_FIELD_PARAM1]; + if (attr) { + source = checkIncDec(event, source, -MAX_SENSORS, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); + } + if (source < 0) { + lcd_putsAtt(SENSOR_2ND_COLUMN, y, "-", attr); + putsMixerSource(SENSOR_2ND_COLUMN+5, y, MIXSRC_FIRST_TELEM+3*(-1-source), attr); + } + else { + putsMixerSource(SENSOR_2ND_COLUMN, y, source ? MIXSRC_FIRST_TELEM+3*(source-1) : 0, attr); + } + break; + } + + case SENSOR_FIELD_AUTOOFFSET: + ON_OFF_MENU_ITEM(sensor->autoOffset, SENSOR_2ND_COLUMN, y, STR_AUTOOFFSET, attr, event); + break; + + case SENSOR_FIELD_FILTER: + ON_OFF_MENU_ITEM(sensor->filter, SENSOR_2ND_COLUMN, y, STR_FILTER, attr, event); + break; + + case SENSOR_FIELD_PERSISTENT: + ON_OFF_MENU_ITEM(sensor->persistent, SENSOR_2ND_COLUMN, y, NO_INDENT(STR_PERSISTENT), attr, event); + break; + + } + } +} + +void onSensorMenu(const char *result) +{ + uint8_t index = m_posVert - ITEM_TELEMETRY_SENSOR1; + + if (index < MAX_SENSORS) { + if (result == STR_EDIT) { + pushMenu(menuModelSensor); + } + else if (result == STR_DELETE) { + delTelemetryIndex(index); + index += 1; + if (index0) ? BLINK|INVERS : INVERS); + LcdFlags attr = (sub == k ? blink : 0); + + if (k>=ITEM_TELEMETRY_SENSOR1 && k= 0) { + s_currIdx = res; + pushMenu(menuModelSensor); + } + } + break; + + case ITEM_TELEMETRY_RSSI_LABEL: + lcd_putsLeft(y, PSTR("RSSI")); + break; + + case ITEM_TELEMETRY_RSSI_ALARM1: + case ITEM_TELEMETRY_RSSI_ALARM2: { + uint8_t alarm = k-ITEM_TELEMETRY_RSSI_ALARM1; + lcd_putsLeft(y, (alarm==0 ? STR_LOWALARM : STR_CRITICALALARM)); + lcd_outdezNAtt(TELEM_COL2, y, getRssiAlarmValue(alarm), LEFT|attr, 3); + if (attr && s_editMode>0) { + CHECK_INCDEC_MODELVAR(event, g_model.frsky.rssiAlarms[alarm].value, -30, 30); + } + break; + } + +#if defined(VARIO) + case ITEM_TELEMETRY_VARIO_LABEL: + lcd_putsLeft(y, STR_VARIO); + break; + + case ITEM_TELEMETRY_VARIO_SOURCE: + lcd_putsLeft(y, STR_SOURCE); + putsMixerSource(TELEM_COL2, y, g_model.frsky.varioSource ? MIXSRC_FIRST_TELEM+3*(g_model.frsky.varioSource-1) : 0, attr); + if (attr) { + g_model.frsky.varioSource = checkIncDec(event, g_model.frsky.varioSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); + } + break; + + case ITEM_TELEMETRY_VARIO_RANGE: + lcd_putsLeft(y, STR_LIMIT); + lcd_outdezAtt(TELEM_COL2, y, -10+g_model.frsky.varioMin, (m_posHorz<=0 ? attr : 0)|LEFT); + lcd_outdezAtt(TELEM_COL2+50, y, -5+g_model.frsky.varioCenterMin, ((CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0)|PREC1); + lcd_outdezAtt(TELEM_COL2+75, y, 5+g_model.frsky.varioCenterMax, ((CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0)|PREC1); + lcd_outdezAtt(TELEM_COL2+100, y, 10+g_model.frsky.varioMax, ((CURSOR_ON_LINE() || m_posHorz==3) ? attr : 0)); + if (attr && s_editMode>0) { + switch (m_posHorz) { + case 0: + CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioMin, -7, 7); + break; + case 1: + CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioCenterMin, -16, 5+min(10, g_model.frsky.varioCenterMax+5)); + break; + case 2: + CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioCenterMax, -5+max(-10, g_model.frsky.varioCenterMin-5), +15); + break; + case 3: + CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioMax, -7, 7); + break; + } + } + break; +#endif + + case ITEM_TELEMETRY_TOP_BAR_LABEL: + lcd_putsLeft(y, "Main Telemetry Fields"); + break; + + case ITEM_TELEMETRY_TOP_BAR_VOLTAGE: + lcd_putsLeft(y, STR_VOLTAGE); + putsMixerSource(TELEM_COL2, y, g_model.frsky.voltsSource ? MIXSRC_FIRST_TELEM+3*(g_model.frsky.voltsSource-1) : 0, attr); + if (attr) { + g_model.frsky.voltsSource = checkIncDec(event, g_model.frsky.voltsSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isVoltsSensor); + } + break; + + case ITEM_TELEMETRY_TOP_BAR_ALTITUDE: + lcd_putsLeft(y, INDENT "Altitude Source"); + putsMixerSource(TELEM_COL2, y, g_model.frsky.altitudeSource ? MIXSRC_FIRST_TELEM+3*(g_model.frsky.altitudeSource-1) : 0, attr); + if (attr) { + g_model.frsky.altitudeSource = checkIncDec(event, g_model.frsky.altitudeSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isAltSensor); + } + break; + + case ITEM_TELEMETRY_SCREEN_LABEL1: + case ITEM_TELEMETRY_SCREEN_LABEL2: + case ITEM_TELEMETRY_SCREEN_LABEL3: + case ITEM_TELEMETRY_SCREEN_LABEL4: + { + uint8_t screenIndex = TELEMETRY_CURRENT_EDIT_SCREEN(k); + putsStrIdx(MENU_TITLE_LEFT, y, STR_SCREEN, screenIndex+1); + TelemetryScreenType oldScreenType = TELEMETRY_SCREEN_TYPE(screenIndex); + TelemetryScreenType newScreenType = (TelemetryScreenType)selectMenuItem(TELEM_SCRTYPE_COL, y, PSTR(""), STR_VTELEMSCREENTYPE, oldScreenType, 0, TELEMETRY_SCREEN_TYPE_MAX, (m_posHorz==0 ? attr : 0), event); + if (newScreenType != oldScreenType) { + g_model.frsky.screensType = (g_model.frsky.screensType & (~(0x03 << (2*screenIndex)))) | (newScreenType << (2*screenIndex)); + memset(&g_model.frsky.screens[screenIndex], 0, sizeof(g_model.frsky.screens[screenIndex])); + } +#if defined(LUA) + if (newScreenType == TELEMETRY_SCREEN_TYPE_SCRIPT) { + TelemetryScriptData & scriptData = g_model.frsky.screens[screenIndex].script; + + // TODO better function name for --- + // TODO function for these lines + if (ZEXIST(scriptData.file)) + lcd_putsnAtt(TELEM_SCRTYPE_COL+70, y, scriptData.file, sizeof(scriptData.file), (m_posHorz==1 ? attr : 0)); + else + lcd_putsiAtt(TELEM_SCRTYPE_COL+70, y, STR_VCSWFUNC, 0, (m_posHorz==1 ? attr : 0)); + + if (m_posHorz==1 && attr && event==EVT_KEY_BREAK(KEY_ENTER) && READ_ONLY_UNLOCKED()) { + s_editMode = 0; + if (listSdFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), g_model.frsky.screens[screenIndex].script.file)) { + menuHandler = onTelemetryScriptFileSelectionMenu; + } + else { + POPUP_WARNING(STR_NO_SCRIPTS_ON_SD); + s_menu_flags = 0; + } + } + } + else if (attr) { + MOVE_CURSOR_FROM_HERE(); + } +#endif + break; + } + + case ITEM_TELEMETRY_SCREEN_LINE1: + case ITEM_TELEMETRY_SCREEN_LINE2: + case ITEM_TELEMETRY_SCREEN_LINE3: + case ITEM_TELEMETRY_SCREEN_LINE4: + case ITEM_TELEMETRY_SCREEN_LINE5: + case ITEM_TELEMETRY_SCREEN_LINE6: + case ITEM_TELEMETRY_SCREEN_LINE7: + case ITEM_TELEMETRY_SCREEN_LINE8: + case ITEM_TELEMETRY_SCREEN_LINE9: + case ITEM_TELEMETRY_SCREEN_LINE10: + case ITEM_TELEMETRY_SCREEN_LINE11: + case ITEM_TELEMETRY_SCREEN_LINE12: + case ITEM_TELEMETRY_SCREEN_LINE13: + case ITEM_TELEMETRY_SCREEN_LINE14: + case ITEM_TELEMETRY_SCREEN_LINE15: + case ITEM_TELEMETRY_SCREEN_LINE16: + { + uint8_t screenIndex, lineIndex; + if (k < ITEM_TELEMETRY_SCREEN_LABEL2) { + screenIndex = 0; + lineIndex = k-ITEM_TELEMETRY_SCREEN_LINE1; + } + else if (k >= ITEM_TELEMETRY_SCREEN_LABEL4) { + screenIndex = 3; + lineIndex = k-ITEM_TELEMETRY_SCREEN_LINE13; + } + else if (k >= ITEM_TELEMETRY_SCREEN_LABEL3) { + screenIndex = 2; + lineIndex = k-ITEM_TELEMETRY_SCREEN_LINE9; + } + else { + screenIndex = 1; + lineIndex = k-ITEM_TELEMETRY_SCREEN_LINE5; + } + +#if defined(GAUGES) + if (IS_BARS_SCREEN(screenIndex)) { + FrSkyBarData & bar = g_model.frsky.screens[screenIndex].bars[lineIndex]; + source_t barSource = bar.source; + putsMixerSource(MENU_TITLE_LEFT+INDENT_WIDTH, y, barSource, (m_posHorz==0 ? attr : 0)); + int barMax = getMaximumValue(barSource); + int barMin = -barMax; + if (barSource) { + if (barSource <= MIXSRC_LAST_CH) { + putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+100, y, barSource, calc100toRESX(bar.barMin), (m_posHorz==1 ? attr : 0) | LEFT); + putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+200, y, barSource, calc100toRESX(bar.barMax), (m_posHorz==2 ? attr : 0) | LEFT); + } + else { + putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+100, y, barSource, bar.barMin, (m_posHorz==1 ? attr : 0) | LEFT); + putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+200, y, barSource, bar.barMax, (m_posHorz==2 ? attr : 0) | LEFT); + } + } + else if (attr) { + MOVE_CURSOR_FROM_HERE(); + } + if (attr && s_editMode>0) { + switch (m_posHorz) { + case 0: + bar.source = CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, barSource, MIXSRC_LAST_TELEM, isSourceAvailable); + if (checkIncDec_Ret) { + if (barSource <= MIXSRC_LAST_CH) { + bar.barMin = -100; + bar.barMax = 100; + } + else { + bar.barMin = 0; + bar.barMax = 0; + } + } + break; + case 1: + bar.barMin = checkIncDec(event, bar.barMin, barMin, bar.barMax, EE_MODEL|NO_INCDEC_MARKS); + break; + case 2: + bar.barMax = checkIncDec(event, bar.barMax, bar.barMin, barMax, EE_MODEL|NO_INCDEC_MARKS); + break; + } + } + } + else +#endif + { + for (int c=0; c0) { + CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, value, MIXSRC_LAST_TELEM, isSourceAvailable); + } + } + if (attr && m_posHorz == NUM_LINE_ITEMS) { + REPEAT_LAST_CURSOR_MOVE(); + } + } + break; + } + } + } +} diff --git a/radio/src/gui/Horus/menus.cpp b/radio/src/gui/Horus/menus.cpp new file mode 100644 index 000000000..ecffd63b1 --- /dev/null +++ b/radio/src/gui/Horus/menus.cpp @@ -0,0 +1,93 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +MenuFuncP g_menuStack[5]; +evt_t menuEvent = 0; +uint8_t g_menuPos[4]; +uint8_t g_menuStackPtr = 0; + +void popMenu() +{ + assert(g_menuStackPtr>0); + g_menuStackPtr = g_menuStackPtr-1; + menuEvent = EVT_ENTRY_UP; +} + +void chainMenu(MenuFuncP newMenu) +{ + g_menuStack[g_menuStackPtr] = newMenu; + menuEvent = EVT_ENTRY; +} + +void pushMenu(MenuFuncP newMenu) +{ + killEvents(KEY_ENTER); + + if (g_menuStackPtr == 0) { + if (newMenu == menuGeneralSetup) + g_menuPos[0] = 1; + if (newMenu == menuModelSelect) + g_menuPos[0] = 0; + } + else { + g_menuPos[g_menuStackPtr] = m_posVert; + } + + g_menuStackPtr++; + + assert(g_menuStackPtr < DIM(g_menuStack)); + + g_menuStack[g_menuStackPtr] = newMenu; + menuEvent = EVT_ENTRY; +} + +void menuModelNotes(evt_t event) +{ + if (event == EVT_ENTRY) { + strcpy(s_text_file, MODELS_PATH "/"); + char *buf = strcat_modelname(&s_text_file[sizeof(MODELS_PATH)], g_eeGeneral.currModel); + strcpy(buf, TEXT_EXT); + } + + menuTextView(event); +} + +void pushModelNotes() +{ + pushMenu(menuModelNotes); +} diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h new file mode 100644 index 000000000..b8164a04d --- /dev/null +++ b/radio/src/gui/Horus/menus.h @@ -0,0 +1,377 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#ifndef _MENUS_H_ +#define _MENUS_H_ + +#include "../../keys.h" + +#define NO_HI_LEN 25 + +#if defined(TRANSLATIONS_FR) + #define MENU_COLUMNS 1 + #define COLUMN_X 0 +#else + #define MENU_COLUMNS 2 +#endif + +#define MENUS_SCROLLBAR_WIDTH 2 +#define MENU_COLUMN2_X 180 +#define lcd_putsColumnLeft(x, y, str) lcd_puts((x > MENU_COLUMN2_X+MENU_TITLE_LEFT+68) ? MENU_COLUMN2_X+MENU_TITLE_LEFT : MENU_TITLE_LEFT, y, str) + +typedef int16_t vertpos_t; + +typedef evt_t & check_event_t; +#define horzpos_t int8_t + +extern vertpos_t m_posVert; +extern horzpos_t m_posHorz; +extern vertpos_t s_pgOfs; +extern uint8_t s_noHi; +extern uint8_t calibrationState; + +void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr); + +typedef void (*MenuFuncP)(evt_t event); +typedef void (*MenuFuncP_PROGMEM)(evt_t event); +extern const MenuFuncP_PROGMEM menuTabModel[]; +extern const MenuFuncP_PROGMEM menuTabGeneral[]; +extern const MenuFuncP_PROGMEM menuTabTelemetry[]; + +extern MenuFuncP g_menuStack[5]; +extern uint8_t g_menuPos[4]; +extern uint8_t g_menuStackPtr; +extern evt_t menuEvent; + +/// goto given Menu, but substitute current menu in menuStack +void chainMenu(MenuFuncP newMenu); +/// goto given Menu, store current menu in menuStack +void pushMenu(MenuFuncP newMenu); +/// return to last menu in menustack +void popMenu(); +///deliver address of last menu which was popped from +inline MenuFuncP lastPopMenu() +{ + return g_menuStack[g_menuStackPtr+1]; +} + +void drawPotsBars(); +void doMainScreenGraphics(); +void menuFirstCalib(evt_t event); + +void onMainViewMenu(const char *result); +void menuMainView(evt_t event); +void menuGeneralDiagAna(evt_t event); + +void menuTelemetryFrsky(evt_t event); +void menuGeneralSetup(evt_t event); +void menuGeneralCalib(evt_t event); +void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext); + +void menuModelSelect(evt_t event); +void menuModelCustomFunctions(evt_t event); +void menuStatisticsView(evt_t event); +void menuStatisticsDebug(evt_t event); +void menuAboutView(evt_t event); + +#if defined(DEBUG_TRACE_BUFFER) +void menuTraceBuffer(evt_t event); +#endif + +void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); + +void menuMainViewChannelsMonitor(evt_t event); +void menuChannelsView(evt_t event); + +extern int8_t checkIncDec_Ret; // global helper vars + +#define EDIT_SELECT_MENU -1 +#define EDIT_SELECT_FIELD 0 +#define EDIT_MODIFY_FIELD 1 +#define EDIT_MODIFY_STRING 2 +extern int8_t s_editMode; // global editmode + +// checkIncDec flags +#define EE_GENERAL 0x01 +#define EE_MODEL 0x02 +#define NO_INCDEC_MARKS 0x04 +#define INCDEC_SWITCH 0x08 +#define INCDEC_SOURCE 0x10 +#define INCDEC_REP10 0x40 +#define NO_DBLKEYS 0x80 + +// mawrow special values +#define TITLE_ROW ((uint8_t)-1) +#define HIDDEN_ROW ((uint8_t)-2) + +typedef bool (*IsValueAvailable)(int); +struct CheckIncDecStops { + const int count; + const int stops[]; + int min() const + { + return stops[0]; + } + int max() const + { + return stops[count-1]; + } + bool contains(int value) const + { + for (int i=0; i 0) REPEAT_LAST_CURSOR_MOVE() + +#define POS_VERT_INIT (MAXCOL((uint16_t)0) >= HIDDEN_ROW ? (MAXCOL((uint16_t)1) >= HIDDEN_ROW ? 2 : 1) : 0) +#define POS_HORZ_INIT(posVert) ((COLATTR(posVert) & NAVIGATION_LINE_BY_LINE) ? -1 : 0) +#define EDIT_MODE_INIT 0 // TODO enum + +typedef int (*FnFuncP) (int x); +void DrawFunction(FnFuncP fn, int offset=0); + +#endif // _MENUS_H_ diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp new file mode 100644 index 000000000..8ec6c69e0 --- /dev/null +++ b/radio/src/gui/Horus/navigation.cpp @@ -0,0 +1,468 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +vertpos_t s_pgOfs; +vertpos_t m_posVert; +horzpos_t m_posHorz; +int8_t s_editMode; +uint8_t s_noHi; +uint8_t calibrationState; // TODO rename this variable + +#if defined(AUTOSWITCH) +int8_t checkIncDecMovedSwitch(int8_t val) +{ + if (s_editMode>0) { + int8_t swtch = getMovedSwitch(); + if (swtch) { + div_t info = switchInfo(swtch); + if (IS_TOGGLE(info.quot)) { + if (info.rem != 0) { + val = (val == swtch ? swtch+2 : swtch); + } + } + else { + val = swtch; + } + } + } + return val; +} +#endif + +int8_t checkIncDec_Ret; + +INIT_STOPS(stops100, 3, -100, 0, 100) +INIT_STOPS(stops1000, 3, -1000, 0, 1000) +INIT_STOPS(stopsSwitch, 15, SWSRC_FIRST, CATEGORY_END(-SWSRC_FIRST_LOGICAL_SWITCH), CATEGORY_END(-SWSRC_FIRST_TRIM), CATEGORY_END(-SWSRC_LAST_SWITCH+1), 0, CATEGORY_END(SWSRC_LAST_SWITCH), CATEGORY_END(SWSRC_FIRST_TRIM-1), CATEGORY_END(SWSRC_FIRST_LOGICAL_SWITCH-1), SWSRC_LAST) + +int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags, IsValueAvailable isValueAvailable, const CheckIncDecStops &stops) +{ + int newval = val; + +#if defined(DBLKEYS) + uint32_t in = KEYS_PRESSED(); + if (!(i_flags & NO_DBLKEYS) && (EVT_KEY_MASK(event))) { + bool dblkey = true; + if (DBLKEYS_PRESSED_RGT_LFT(in)) { + if (!isValueAvailable || isValueAvailable(-val)) + newval = -val; + } + else if (DBLKEYS_PRESSED_RGT_UP(in)) { + newval = (i_max > stops.max() ? stops.max() : i_max); + while (isValueAvailable && !isValueAvailable(newval) && newval>i_min) { + --newval; + } + } + else if (DBLKEYS_PRESSED_LFT_DWN(in)) { + newval = (i_min < stops.min() ? stops.min() : i_min); + while (isValueAvailable && !isValueAvailable(newval) && newval0 && (IS_ROTARY_RIGHT(event) || event==EVT_KEY_FIRST(KEY_UP) || event==EVT_KEY_REPT(KEY_UP))) { + do { + if (IS_KEY_REPT(event) && (i_flags & INCDEC_REP10)) { + newval += min(10, i_max-val); + } + else { + newval++; + } + } while (isValueAvailable && !isValueAvailable(newval) && newval<=i_max); + + if (newval > i_max) { + newval = val; + killEvents(event); + AUDIO_WARNING2(); + } + else { + AUDIO_KEYPAD_UP(); + } + } + else if (s_editMode>0 && (IS_ROTARY_LEFT(event) || event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_REPT(KEY_DOWN))) { + do { + if (IS_KEY_REPT(event) && (i_flags & INCDEC_REP10)) { + newval -= min(10, val-i_min); + } + else { + newval--; + } + } while (isValueAvailable && !isValueAvailable(newval) && newval>=i_min); + + if (newval < i_min) { + newval = val; + killEvents(event); + AUDIO_WARNING2(); + } + else { + AUDIO_KEYPAD_DOWN(); + } + } + + if (!READ_ONLY() && i_min==0 && i_max==1 && (event==EVT_KEY_BREAK(KEY_ENTER) || IS_ROTARY_BREAK(event))) { + s_editMode = 0; + newval = !val; + } + +#if defined(AUTOSWITCH) + if (i_flags & INCDEC_SWITCH) { + newval = checkIncDecMovedSwitch(newval); + } +#endif + +#if defined(AUTOSOURCE) + if (i_flags & INCDEC_SOURCE) { + if (s_editMode>0) { + int source = GET_MOVED_SOURCE(i_min, i_max); + if (source) { + newval = source; + } +#if defined(AUTOSWITCH) + else { + unsigned int swtch = abs(getMovedSwitch()); + if (swtch) { + newval = switchToMix(swtch); + } + } +#endif + } + } +#endif + + if (newval > i_max || newval < i_min) { + newval = (newval > i_max ? i_max : i_min); + killEvents(event); + AUDIO_WARNING2(); + } + + if (newval != val) { + if (!(i_flags & NO_INCDEC_MARKS) && (newval != i_max) && (newval != i_min) && stops.contains(newval) && !IS_ROTARY_EVENT(event)) { + bool pause = (newval > val ? !stops.contains(newval+1) : !stops.contains(newval-1)); + if (pause) { + pauseEvents(event); // delay before auto-repeat continues + if (newval>val) // without AUDIO it's optimized, because the 2 sounds are the same + AUDIO_KEYPAD_UP(); + else + AUDIO_KEYPAD_DOWN(); + } + } + eeDirty(i_flags & (EE_GENERAL|EE_MODEL)); + checkIncDec_Ret = (newval > val ? 1 : -1); + } + else { + checkIncDec_Ret = 0; + } + return newval; +} + +#define CURSOR_NOT_ALLOWED_IN_ROW(row) ((int8_t)MAXCOL(row) < 0) +#define MAXCOL_RAW(row) (horTab ? pgm_read_byte(horTab+min(row, (vertpos_t)horTabMax)) : (const uint8_t)0) +#define MAXCOL(row) (MAXCOL_RAW(row) >= HIDDEN_ROW ? MAXCOL_RAW(row) : (const uint8_t)(MAXCOL_RAW(row) & (~NAVIGATION_LINE_BY_LINE))) +#define COLATTR(row) (MAXCOL_RAW(row) == (uint8_t)-1 ? (const uint8_t)0 : (const uint8_t)(MAXCOL_RAW(row) & NAVIGATION_LINE_BY_LINE)) +#define INC(val, min, max) if (valmin) {val--;} else {val=max;} + +void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t rowcount, uint16_t scrollbar_X, uint8_t flags) +{ + vertpos_t l_posVert = m_posVert; + horzpos_t l_posHorz = m_posHorz; + + uint8_t maxcol = MAXCOL(l_posVert); + + if (menuTab && !calibrationState && l_posVert<0) { + int cc = curr; + switch (event) { + case EVT_KEY_BREAK(KEY_RIGHT): + case EVT_ROTARY_RIGHT: + if (++cc == menuTabSize) + cc = 0; + break; + + case EVT_KEY_BREAK(KEY_LEFT): + case EVT_ROTARY_LEFT: + if (cc-- == 0) + cc = menuTabSize-1; + break; + + case EVT_KEY_BREAK(KEY_ENTER): + l_posVert = POS_VERT_INIT; + event = 0; + break; + } + + if (cc != curr) { + chainMenu((MenuFuncP)pgm_read_adr(&menuTab[cc])); + return; + } + } + + switch(event) + { + case EVT_ENTRY: + l_posVert = -1; + l_posHorz = POS_HORZ_INIT(l_posVert); + s_editMode = EDIT_MODE_INIT; + break; + + case EVT_ENTRY_UP: + s_editMode = 0; + l_posHorz = POS_HORZ_INIT(l_posVert); + break; + + case EVT_ROTARY_BREAK: + if (s_editMode > 1) break; + if (m_posHorz < 0 && maxcol > 0 && READ_ONLY_UNLOCKED()) { + l_posHorz = 0; + break; + } + if (READ_ONLY_UNLOCKED()) { + s_editMode = (s_editMode<=0); + } + break; + + case EVT_KEY_LONG(KEY_EXIT): + s_editMode = 0; // TODO needed? we call ENTRY_UP after which does the same + popMenu(); + break; + + case EVT_KEY_BREAK(KEY_EXIT): + if (s_editMode>0) { + s_editMode = 0; + break; + } + + if (l_posHorz >= 0 && (COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { + l_posHorz = -1; + } + else if (l_posVert >= 0) { + l_posVert = -1; + l_posHorz = 0; +#if 0 + int posVertInit = -1; + if (s_pgOfs != 0 || l_posVert != posVertInit) { + s_pgOfs = 0; + l_posVert = posVertInit; + l_posHorz = POS_HORZ_INIT(l_posVert); + } +#endif + } + else { + popMenu(); + } + break; + + case EVT_KEY_FIRST(KEY_RIGHT): + case EVT_KEY_REPT(KEY_RIGHT): + if (s_editMode != 0) break; + INC(l_posHorz, 0, maxcol); + break; + + case EVT_KEY_FIRST(KEY_LEFT): + case EVT_KEY_REPT(KEY_LEFT): + DEC(l_posHorz, 0, maxcol); + break; + break; + + case EVT_ROTARY_RIGHT: + if (s_editMode != 0) break; + if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { + if (l_posHorz >= 0) { + INC(l_posHorz, 0, maxcol); + break; + } + } + else + { + if (l_posHorz < maxcol) { + l_posHorz++; + break; + } + else { + l_posHorz = 0; + } + } + // no break + + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): + do { + INC(l_posVert, POS_VERT_INIT, rowcount-1); + } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); + s_editMode = 0; // if we go down, we must be in this mode + l_posHorz = POS_HORZ_INIT(l_posVert); + break; + + case EVT_ROTARY_LEFT: + if (s_editMode != 0) break; + if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { + if (l_posHorz >= 0) { + DEC(l_posHorz, 0, maxcol); + break; + } + } + else + { + if (l_posHorz > 0) { + l_posHorz--; + break; + } + else { + l_posHorz = 0xff; + } + } + // no break + + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): + do { + DEC(l_posVert, POS_VERT_INIT, rowcount-1); + } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); + + s_editMode = 0; // if we go up, we must be in this mode + + if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) + l_posHorz = -1; + else + l_posHorz = min((uint8_t)l_posHorz, MAXCOL(l_posVert)); + + break; + } + + int linesCount = rowcount; + + if (l_posVert <= 0 || (l_posVert==1 && MAXCOL(vertpos_t(0)) >= HIDDEN_ROW) || (l_posVert==2 && MAXCOL(vertpos_t(0)) >= HIDDEN_ROW && MAXCOL(vertpos_t(1)) >= HIDDEN_ROW)) { + s_pgOfs = 0; + if (horTab) { + linesCount = 0; + for (int i=0; i=horTabMax || horTab[i] != HIDDEN_ROW) { + linesCount++; + } + } + } + } + else if (horTab) { + if (rowcount > NUM_BODY_LINES) { + while (1) { + vertpos_t firstLine = 0; + for (int numLines=0; firstLine=horTabMax || horTab[firstLine] != HIDDEN_ROW) { + numLines++; + } + } + if (l_posVert < firstLine) { + s_pgOfs--; + } + else { + vertpos_t lastLine = firstLine; + for (int numLines=0; lastLine=horTabMax || horTab[lastLine] != HIDDEN_ROW) { + numLines++; + } + } + if (l_posVert >= lastLine) { + s_pgOfs++; + } + else { + linesCount = s_pgOfs + NUM_BODY_LINES; + for (int i=lastLine; i=horTabMax || horTab[i] != HIDDEN_ROW) { + linesCount++; + } + } + break; + } + } + } + } + } + else { + if (l_posVert>=NUM_BODY_LINES+s_pgOfs) { + s_pgOfs = l_posVert-NUM_BODY_LINES+1; + } + else if (l_posVert NUM_BODY_LINES) { + displayScrollbar(scrollbar_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, s_pgOfs, linesCount, NUM_BODY_LINES); + } + + m_posVert = l_posVert; + m_posHorz = l_posHorz; +} + +void check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount, uint16_t scrollbar_X) +{ + check(event, curr, menuTab, menuTabSize, 0, 0, rowcount, scrollbar_X); +} + +void check_submenu_simple(check_event_t event, uint8_t rowcount, uint16_t scrollbar_X) +{ + check_simple(event, 0, 0, 0, rowcount, scrollbar_X); +} + +void repeatLastCursorMove(evt_t event) +{ + if (CURSOR_MOVED_LEFT(event) || CURSOR_MOVED_RIGHT(event)) { + putEvent(event); + } + else { + m_posHorz = 0; + } +} diff --git a/radio/src/gui/Horus/popups.cpp b/radio/src/gui/Horus/popups.cpp new file mode 100644 index 000000000..9705d796b --- /dev/null +++ b/radio/src/gui/Horus/popups.cpp @@ -0,0 +1,236 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +const char *s_warning = NULL; +const char *s_warning_info; +uint8_t s_warning_info_len; +uint8_t s_warning_type; +uint8_t s_warning_result = 0; +uint8_t s_warning_info_flags = ZCHAR; +int16_t s_warning_input_value; +int16_t s_warning_input_min; +int16_t s_warning_input_max; + +void displayAlertBox() +{ + drawBlackOverlay(); + lcdDrawFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + lcd_rect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); + lcd_rect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); + // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_ALERT); +} + +void displayWarningBox() +{ + drawBlackOverlay(); + lcdDrawFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + lcd_rect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); + lcd_rect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); + // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_WARNING); +} + +void displayMessageBox() +{ + drawBlackOverlay(); + lcdDrawFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + lcd_rect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, WARNING_COLOR); + lcd_rect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, WARNING_COLOR); + // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_MESSAGE); +} + +void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SOUND_ARG) +{ + displayAlertBox(); + +#if defined(TRANSLATIONS_FR) || defined(TRANSLATIONS_IT) || defined(TRANSLATIONS_CZ) + lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y, STR_WARNING, ALARM_COLOR|DBLSIZE); + lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y+15, title, ALARM_COLOR|DBLSIZE); +#else + lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y, title, ALARM_COLOR|DBLSIZE); + lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y+15, STR_WARNING, ALARM_COLOR|DBLSIZE); +#endif + + if (t) lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y, t); + if (last) { + lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y+12, last); + AUDIO_ERROR_MESSAGE(sound); + } + + lcdRefresh(); + lcdSetContrast(); + clearKeyEvents(); +} + +void displayPopup(const char *title) +{ + displayMessageBox(); + lcd_putsnAtt(WARNING_LINE_X, WARNING_LINE_Y, title, WARNING_LINE_LEN, DBLSIZE|WARNING_COLOR); + lcdRefresh(); +} + +void displayWarning(evt_t event) +{ + s_warning_result = false; + if (s_warning_type == WARNING_TYPE_INPUT) + displayMessageBox(); + else + displayWarningBox(); + lcd_putsnAtt(WARNING_LINE_X, WARNING_LINE_Y, s_warning, WARNING_LINE_LEN, DBLSIZE | (s_warning_type == WARNING_TYPE_INPUT ? WARNING_COLOR : ALARM_COLOR)); + if (s_warning_info) { + lcd_putsnAtt(WARNING_LINE_X, WARNING_INFOLINE_Y, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); + } + lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y+12, s_warning_type == WARNING_TYPE_ASTERISK ? STR_EXIT : STR_POPUPS); + switch (event) { +#if defined(ROTARY_ENCODER_NAVIGATION) + case EVT_ROTARY_BREAK: +#endif + case EVT_KEY_BREAK(KEY_ENTER): + if (s_warning_type == WARNING_TYPE_ASTERISK) + break; + s_warning_result = true; + // no break +#if defined(ROTARY_ENCODER_NAVIGATION) + case EVT_ROTARY_LONG: + killEvents(event); +#endif + case EVT_KEY_BREAK(KEY_EXIT): + s_warning = NULL; + s_warning_type = WARNING_TYPE_ASTERISK; + break; + default: + if (s_warning_type != WARNING_TYPE_INPUT) break; + s_editMode = EDIT_MODIFY_FIELD; + s_warning_input_value = checkIncDec(event, s_warning_input_value, s_warning_input_min, s_warning_input_max); + s_editMode = EDIT_SELECT_FIELD; + break; + } +} + +void (*popupFunc)(evt_t event) = NULL; + +const char *s_menu[MENU_MAX_LINES]; +uint8_t s_menu_item = 0; +uint16_t s_menu_count = 0; +uint8_t s_menu_flags = 0; +uint16_t s_menu_offset = 0; +void (*menuHandler)(const char *result); +const char * displayMenu(evt_t event) +{ + const char * result = NULL; + + uint8_t display_count = min(s_menu_count, (uint16_t)MENU_MAX_LINES); + + int y = (LCD_H - (display_count*(FH+1))) / 2; + drawBlackOverlay(); + lcdDrawFilledRect(MENU_X, y, MENU_W, display_count * (FH+1), TEXT_BGCOLOR); + lcd_rect(MENU_X, y, MENU_W, display_count * (FH+1), SOLID, WARNING_COLOR); + + for (uint8_t i=0; i display_count) { + displayScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); + } + + switch(event) { +#if defined(ROTARY_ENCODER_NAVIGATION) + CASE_EVT_ROTARY_LEFT +#endif + case EVT_KEY_FIRST(KEY_MOVE_UP): + case EVT_KEY_REPT(KEY_MOVE_UP): + if (s_menu_item > 0) { + s_menu_item--; + } + else if (s_menu_offset > 0) { + s_menu_offset--; + result = STR_UPDATE_LIST; + } + else { + s_menu_item = display_count - 1; + if (s_menu_count > MENU_MAX_LINES) { + s_menu_offset = s_menu_count - display_count; + result = STR_UPDATE_LIST; + } + } + break; + +#if defined(ROTARY_ENCODER_NAVIGATION) + CASE_EVT_ROTARY_RIGHT +#endif + case EVT_KEY_FIRST(KEY_MOVE_DOWN): + case EVT_KEY_REPT(KEY_MOVE_DOWN): + if (s_menu_item < display_count - 1 && s_menu_offset + s_menu_item + 1 < s_menu_count) { + s_menu_item++; + } + else if (s_menu_count > s_menu_offset + display_count) { + s_menu_offset++; + result = STR_UPDATE_LIST; + } + else { + s_menu_item = 0; + if (s_menu_offset) { + s_menu_offset = 0; + result = STR_UPDATE_LIST; + } + } + break; + CASE_EVT_ROTARY_BREAK + case EVT_KEY_BREAK(KEY_ENTER): + result = s_menu[s_menu_item]; + // no break +#if defined(ROTARY_ENCODER_NAVIGATION) + CASE_EVT_ROTARY_LONG + killEvents(event); +#endif + case EVT_KEY_BREAK(KEY_EXIT): + s_menu_count = 0; + s_menu_item = 0; + s_menu_flags = 0; + s_menu_offset = 0; + break; + } + + return result; +} diff --git a/radio/src/gui/Horus/splash.cpp b/radio/src/gui/Horus/splash.cpp new file mode 100644 index 000000000..b439781dc --- /dev/null +++ b/radio/src/gui/Horus/splash.cpp @@ -0,0 +1,57 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +const uint16_t LBM_SPLASH[] = { +#include "../../bitmaps/Horus/splash.lbm" +}; + +#if defined(SPLASH) +void displaySplash() +{ + lcdDrawBitmap((LCD_W-256)/2, (LCD_H-256)/2, LBM_SPLASH); + +#if MENUS_LOCK == 1 + if (readonly == false) { + lcdDrawFilledRect((LCD_W-(sizeof(TR_UNLOCKED)-1)*FW)/2 - 9, 50, (sizeof(TR_UNLOCKED)-1)*FW+16, 11, SOLID, ERASE|ROUND); + lcd_puts((LCD_W-(sizeof(TR_UNLOCKED)-1)*FW)/2 , 53, STR_UNLOCKED); + } +#endif + + lcdRefresh(); +} +#endif diff --git a/radio/src/gui/Horus/view_about.cpp b/radio/src/gui/Horus/view_about.cpp new file mode 100644 index 000000000..2ac6ad383 --- /dev/null +++ b/radio/src/gui/Horus/view_about.cpp @@ -0,0 +1,159 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +enum AboutScreens { + ABOUT_OPENTX, + ABOUT_BERTRAND, + ABOUT_ANDRE, + ABOUT_MIKE, + ABOUT_KJELL, + ABOUT_MARTIN, + ABOUT_ROMOLO, + ABOUT_ROB, + ABOUT_HARDWARE, + ABOUT_PARENTS, + ABOUT_END, + ABOUT_COUNT, +}; + +#define ABOUT_X 62 +#define ABOUT_INDENT 16 + +void menuAboutView(evt_t event) +{ + static uint8_t screenIndex = 0; + static uint8_t greyIndex = 0; + + switch(event) + { + case EVT_ENTRY: + screenIndex = 0; + greyIndex = 0; + break; + case EVT_KEY_FIRST(KEY_DOWN): + screenIndex < ABOUT_PARENTS ? screenIndex++ : screenIndex = ABOUT_OPENTX; + greyIndex = 0; + break; + case EVT_KEY_FIRST(KEY_UP): + screenIndex > ABOUT_OPENTX ? screenIndex-- : screenIndex = ABOUT_PARENTS; + greyIndex = 0; + break; + case EVT_KEY_FIRST(KEY_EXIT): + chainMenu(menuMainView); + break; + } + + drawMenuTemplate("About", event); + + uint8_t screenDuration = 150; + + switch (screenIndex) { + case ABOUT_OPENTX: + case ABOUT_END: + lcd_puts(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_OPENTX_1); + lcd_puts(ABOUT_X, MENU_CONTENT_TOP + FH, STR_ABOUT_OPENTX_2); + lcd_puts(ABOUT_X, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_OPENTX_3); + lcd_puts(ABOUT_X, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_OPENTX_4); + lcd_puts(ABOUT_X, MENU_CONTENT_TOP + 4*FH, STR_ABOUT_OPENTX_5); + screenDuration = 255; + break; + + case ABOUT_BERTRAND: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_BERTRAND_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_BERTRAND_2); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_BERTRAND_3); + break; + + case ABOUT_ANDRE: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ANDRE_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ANDRE_2); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ANDRE_3); + break; + + case ABOUT_MIKE: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MIKE_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MIKE_2); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_MIKE_3); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_MIKE_4); + break; + + case ABOUT_KJELL: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_KJELL_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_KJELL_2); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_KJELL_3); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_KJELL_4); + break; + + case ABOUT_MARTIN: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MARTIN_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MARTIN_2); + break; + + case ABOUT_ROMOLO: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROMOLO_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROMOLO_2); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ROMOLO_3); + break; + + case ABOUT_ROB: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROB_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROB_2); + break; + + case ABOUT_HARDWARE: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, "FrSky", INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_HARDWARE_2); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_HARDWARE_3); + break; + + case ABOUT_PARENTS: + lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_PARENTS_1, INVERS); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_PARENTS_2); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_PARENTS_3); + lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_PARENTS_4); + screenDuration = 255; + break; + } + + if (++greyIndex == screenDuration) { + greyIndex = 0; + if (++screenIndex == ABOUT_COUNT) { + chainMenu(menuMainView); + } + } +} diff --git a/radio/src/gui/Horus/view_channels.cpp b/radio/src/gui/Horus/view_channels.cpp new file mode 100644 index 000000000..e2a2bb2c0 --- /dev/null +++ b/radio/src/gui/Horus/view_channels.cpp @@ -0,0 +1,122 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +bool isChannelUsed(int channel); +int getChannelsUsed(); + +void menuChannelsView(evt_t event) +{ + const int CHANNEL_MARGIN = 16; + const int CHANNEL_HEIGHT = 25; + const int CHANNEL_WIDTH = (LCD_W - 3*CHANNEL_MARGIN) / 2; + const int CHANNEL_PADDING_HORZ = 4; + const int CHANNEL_PADDING_VERT = 2; + const int BAR_WIDTH = CHANNEL_WIDTH-2*CHANNEL_PADDING_HORZ; + + static int view = 0; + int x=CHANNEL_MARGIN, y=CHANNEL_MARGIN; + + int viewMax = (getChannelsUsed() - 1) / 8; + if (viewMax <= 0 || view > viewMax) { + view = 0; + } + else { + switch (event) { + case EVT_KEY_FIRST(KEY_RIGHT): + if (++view > viewMax) + view = 0; + break; + case EVT_KEY_FIRST(KEY_LEFT): + if (--view < 0) + view = viewMax; + break; + } + } + + int skipCount = view*8; + for (int ch=0, index=0; index skipCount) { + // The black background + lcdDrawFilledRect(x, y, CHANNEL_WIDTH, CHANNEL_HEIGHT, TEXT_BGCOLOR); + + // The label + unsigned int lenLabel = ZLEN(g_model.limitData[ch].name); + if (lenLabel > 0) + lcd_putsnAtt(x+CHANNEL_PADDING_HORZ, y+CHANNEL_PADDING_VERT, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR); + else + putsChn(x+CHANNEL_PADDING_HORZ, y+CHANNEL_PADDING_VERT, ch+1, 0); + + int32_t val = channelOutputs[ch]; + + // The bar + lcdDrawFilledRect(x+CHANNEL_PADDING_HORZ, y+15, BAR_WIDTH, 6, TEXT_INVERTED_BGCOLOR); + unsigned int lim = g_model.extendedLimits ? 640*2 : 512*2; + unsigned int len = limit(1, (abs(val) * BAR_WIDTH/2 + lim/2) / lim, BAR_WIDTH/2); + unsigned int x0 = (val>0) ? x+CHANNEL_PADDING_HORZ-1+BAR_WIDTH/2 : x+CHANNEL_PADDING_HORZ+BAR_WIDTH/2+1-len; + lcdDrawFilledRect(x0, y+16, len, 4, TEXT_BGCOLOR); + + y += CHANNEL_HEIGHT + CHANNEL_MARGIN; + if (y >= 4*(CHANNEL_HEIGHT + CHANNEL_MARGIN)) { + x += CHANNEL_WIDTH + CHANNEL_MARGIN; + y = CHANNEL_MARGIN; + } + } + } + } + +#if 0 + +#if defined(CHANNELS_MONITOR_INV_HIDE) + //if channel output is inverted, show it with oposite sign + if (g_model.limitData[ch].revert) val = -val; +#endif + + // Value +#if defined(PPM_UNIT_US) + uint8_t wbar = (longNames ? 54 : 64); + lcd_outdezAtt(x+LCD_W/2-3-wbar-ofs, y+1, PPM_CH_CENTER(ch)+val/2, TINSIZE); +#elif defined(PPM_UNIT_PERCENT_PREC1) + uint8_t wbar = (longNames ? 48 : 58); + lcd_outdezAtt(x+LCD_W/2-3-wbar-ofs, y+1, calcRESXto1000(val), PREC1|TINSIZE); +#else + uint8_t wbar = (longNames ? 54 : 64); + lcd_outdezAtt(x+LCD_W/2-3-wbar-ofs, y+1, calcRESXto1000(val)/10, TINSIZE); // G: Don't like the decimal part* +#endif +#endif +} diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp new file mode 100644 index 000000000..6d75f4aae --- /dev/null +++ b/radio/src/gui/Horus/view_main.cpp @@ -0,0 +1,374 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define BIGSIZE MIDSIZE +#define BAR_HEIGHT (BOX_WIDTH-9) +#define LBOX_CENTERX (BOX_WIDTH/2 + 17) +#define RBOX_CENTERX (LCD_W-LBOX_CENTERX) +#define BITMAP_X ((LCD_W-64)/2) +#define BITMAP_Y (LCD_H/2) +#define TRIM_LH_X (32+9) +#define TRIM_LV_X 10 +#define TRIM_RV_X (LCD_W-11) +#define TRIM_RH_X (LCD_W-32-9) +#define TRIM_LEN 27 + +void drawPotsBars() +{ + // Optimization by Mike Blandford + uint8_t x, i, len ; // declare temporary variables + for (x=LCD_W/2-9, i=NUM_STICKS; i 125) { + exttrim = true; + } + + if (val < -(TRIM_LEN+1)*4) { + val = -(TRIM_LEN+1); + } + else if (val > (TRIM_LEN+1)*4) { + val = TRIM_LEN+1; + } + else { + val /= 4; + } + + if (vert[i]) { + ym = 31; + lcd_vline(xm, ym-TRIM_LEN, TRIM_LEN*2); + if (i!=2 || !g_model.thrTrim) { + lcd_vline(xm-1, ym-1, 3); + lcd_vline(xm+1, ym-1, 3); + } + ym -= val; + + // TODO lcdDrawFilledRect(xm-3, ym-3, 7, 7, SOLID, att); + if (dir >= 0) { + lcd_hline(xm-1, ym-1, 3); + } + if (dir <= 0) { + lcd_hline(xm-1, ym+1, 3); + } + if (exttrim) { + lcd_hline(xm-1, ym, 3); + } + } + else { + ym = 60; + lcd_hline(xm-TRIM_LEN, ym, TRIM_LEN*2); + lcd_hline(xm-1, ym-1, 3); + lcd_hline(xm-1, ym+1, 3); + xm += val; + + // TODO lcdDrawFilledRect(xm-3, ym-3, 7, 7, SOLID, att); + if (dir >= 0) { + lcd_vline(xm+1, ym-1, 3); + } + if (dir <= 0) { + lcd_vline(xm-1, ym-1, 3); + } + if (exttrim) { + lcd_vline(xm, ym-1, 3); + } + } + lcd_square(xm-3, ym-3, 7, att); + } +} + +void displayTimers() +{ + // const int TIMERS_W = 56; + const int TIMERS_H = 30; + const int TIMERS_MARGIN = 16; + const int TIMERS_PADDING = 4; + + for (int i=0; i 0) + lcd_putsnAtt(TIMERS_MARGIN+TIMERS_PADDING, y+2, timerData.name, LEN_TIMER_NAME, color|ZCHAR); + else + putsTimerMode(TIMERS_MARGIN+TIMERS_PADDING, y+2, timerData.mode, color); + } + } +} + +void displayMainTelemetryFields() +{ + const int ALTITUDE_Y = 16; + const int VOLTS_Y = 16+16+30; + const int ALTITUDE_W = 56; + const int ALTITUDE_X = LCD_W-ALTITUDE_Y-ALTITUDE_W; + const int ALTITUDE_H = 30; + const int PADDING = 4; + + if (g_model.frsky.voltsSource) { + TelemetryItem & item = telemetryItems[g_model.frsky.voltsSource-1]; + if (item.isAvailable()) { + int32_t value = item.value; + TelemetrySensor & sensor = g_model.telemetrySensors[g_model.frsky.altitudeSource-1]; + LcdFlags att = 0; + if (sensor.prec == 2) { + att |= PREC1; + value /= 10; + } + else if (sensor.prec == 1) { + att |= PREC1; + } + att |= (item.isOld() ? ALARM_COLOR : TEXT_COLOR); + lcdDrawFilledRect(ALTITUDE_X, VOLTS_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); + lcd_putsAtt(ALTITUDE_X+PADDING, VOLTS_Y+2, "Voltage", att); + putsValueWithUnit(ALTITUDE_X+PADDING, VOLTS_Y+12, value, UNIT_VOLTS, DBLSIZE|LEFT|att); + } + } + + if (g_model.frsky.altitudeSource) { + TelemetryItem & item = telemetryItems[g_model.frsky.altitudeSource-1]; + if (item.isAvailable()) { + int32_t value = item.value; + TelemetrySensor & sensor = g_model.telemetrySensors[g_model.frsky.altitudeSource-1]; + if (sensor.prec) value /= sensor.prec == 2 ? 100 : 10; + LcdFlags att = (item.isOld() ? ALARM_COLOR : TEXT_COLOR); + lcdDrawFilledRect(ALTITUDE_X, ALTITUDE_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); + lcd_putsAtt(ALTITUDE_X+PADDING, ALTITUDE_Y+2, "Alt", att); + putsValueWithUnit(ALTITUDE_X+PADDING, ALTITUDE_Y+12, value, UNIT_METERS, DBLSIZE|LEFT|att); + } + } +} + +bool isViewAvailable(int index) +{ + if (index <= VIEW_CHANNELS) + return true; + else + return TELEMETRY_SCREEN_TYPE(index-VIEW_TELEM1) != TELEMETRY_SCREEN_TYPE_NONE; +} + +void displayMainViewIndex() +{ + int x = LCD_W - 14; + + for (int i=VIEW_COUNT; i>=0; --i) { + if (isViewAvailable(i)) { + if (g_eeGeneral.view == i) { + lcdDrawFilledRect(x, MENU_FOOTER_TOP+17, 9, 9, TEXT_INVERTED_BGCOLOR); + } + else { + lcdDrawRect(x+1, MENU_FOOTER_TOP+18, 7, 7, TEXT_COLOR); + } + x -= 11; + } + } +} + +void onMainViewMenu(const char *result) +{ + if (result == STR_RESET_TIMER1) { + timerReset(0); + } + else if (result == STR_RESET_TIMER2) { + timerReset(1); + } +#if defined(CPUARM) + else if (result == STR_RESET_TIMER3) { + timerReset(2); + } + else if (result == STR_VIEW_NOTES) { + pushModelNotes(); + } + else if (result == STR_RESET_SUBMENU) { + MENU_ADD_ITEM(STR_RESET_FLIGHT); + MENU_ADD_ITEM(STR_RESET_TIMER1); + MENU_ADD_ITEM(STR_RESET_TIMER2); + MENU_ADD_ITEM(STR_RESET_TIMER3); +#if defined(FRSKY) + MENU_ADD_ITEM(STR_RESET_TELEMETRY); +#endif + } +#endif +#if defined(FRSKY) + else if (result == STR_RESET_TELEMETRY) { + telemetryReset(); + } +#endif + else if (result == STR_RESET_FLIGHT) { + flightReset(); + } + else if (result == STR_STATISTICS) { + chainMenu(menuStatisticsView); + } +#if defined(CPUARM) + else if (result == STR_ABOUT_US) { + chainMenu(menuAboutView); + } +#endif +} + +void displayTelemetryScreen(int index, unsigned int evt); + +void menuMainView(evt_t event) +{ + // clear the screen + lcdDrawFilledRect(0, 0, LCD_W, LCD_H, TEXT_BGCOLOR); + + switch (event) { + case EVT_ENTRY: + killEvents(KEY_EXIT); + killEvents(KEY_UP); + killEvents(KEY_DOWN); + break; + + case EVT_KEY_LONG(KEY_ENTER): + killEvents(event); + if (modelHasNotes()) { + MENU_ADD_ITEM(STR_VIEW_NOTES); + } + MENU_ADD_ITEM(STR_RESET_SUBMENU); + MENU_ADD_ITEM(STR_STATISTICS); + MENU_ADD_ITEM(STR_ABOUT_US); + menuHandler = onMainViewMenu; + break; + + case EVT_KEY_BREAK(KEY_MENU): + pushMenu(menuModelSetup); + break; + + case EVT_KEY_LONG(KEY_MENU): + killEvents(event); + pushMenu(menuGeneralSetup); + break; + + case EVT_KEY_BREAK(KEY_DOWN): + eeDirty(EE_GENERAL); + g_eeGeneral.view = circularIncDec(g_eeGeneral.view, +1, 0, VIEW_COUNT-1, isViewAvailable); + break; + + case EVT_KEY_BREAK(KEY_UP): + killEvents(event); + eeDirty(EE_GENERAL); + g_eeGeneral.view = circularIncDec(g_eeGeneral.view, -1, 0, VIEW_COUNT-1, isViewAvailable); + break; + + case EVT_KEY_FIRST(KEY_EXIT): +#if defined(GVARS) + if (s_gvar_timer > 0) { + s_gvar_timer = 0; + } +#endif + AUDIO_KEYPAD_UP(); + break; + } + +#if 0 + displayMainViewIndex(); + + if (g_eeGeneral.view == VIEW_TIMERS_ALTITUDE) { + displayTimers(); + displayMainTelemetryFields(); + } + else if (g_eeGeneral.view == VIEW_CHANNELS) { + menuChannelsView(event); + } + else { + displayTelemetryScreen(g_eeGeneral.view - VIEW_TELEM1, event); + } +#endif + +#if defined(GVARS) + if (s_gvar_timer > 0) { + s_gvar_timer--; + displayMessageBox(); + putsStrIdx(WARNING_LINE_X, WARNING_LINE_Y, STR_GV, s_gvar_last+1, DBLSIZE|YELLOW); + lcd_putsnAtt(WARNING_LINE_X+45, WARNING_LINE_Y, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, DBLSIZE|YELLOW|ZCHAR); + lcd_outdezAtt(WARNING_LINE_X, WARNING_INFOLINE_Y, GVAR_VALUE(s_gvar_last, getGVarFlightPhase(mixerCurrentFlightMode, s_gvar_last)), DBLSIZE|LEFT); + } +#endif +} + +void menuMainViewChannelsMonitor(evt_t event) +{ + switch(event) { + case EVT_KEY_BREAK(KEY_EXIT): + chainMenu(menuMainView); + event = 0; + break; + } + + return menuChannelsView(event); +} diff --git a/radio/src/gui/Horus/view_statistics.cpp b/radio/src/gui/Horus/view_statistics.cpp new file mode 100644 index 000000000..c0fc2965a --- /dev/null +++ b/radio/src/gui/Horus/view_statistics.cpp @@ -0,0 +1,213 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +void menuStatisticsView(evt_t event) +{ + drawMenuTemplate("Statistics", event); + + switch(event) + { + case EVT_KEY_FIRST(KEY_UP): + chainMenu(menuStatisticsDebug); + break; + + case EVT_KEY_LONG(KEY_MENU): + g_eeGeneral.globalTimer = 0; + eeDirty(EE_GENERAL); + sessionTimer = 0; + break; + + case EVT_KEY_FIRST(KEY_EXIT): + chainMenu(menuMainView); + break; + } + + lcd_putsAtt( 10, MENU_CONTENT_TOP + FH*0, "\037\145TOT:\037\317BATT:", HEADER_COLOR); + lcd_putsAtt( 10, MENU_CONTENT_TOP + FH*1, "TM1:\037\145TM2:", HEADER_COLOR); + lcd_putsAtt( 10, MENU_CONTENT_TOP + FH*2, "THR:\037\145TH%:", HEADER_COLOR); + + putsTimer( 45, MENU_CONTENT_TOP + FH*1, timersStates[0].val); + putsTimer( 140, MENU_CONTENT_TOP + FH*1, timersStates[1].val); + putsTimer( 45, MENU_CONTENT_TOP + FH*2, s_timeCumThr); + putsTimer( 140, MENU_CONTENT_TOP + FH*2, s_timeCum16ThrP/16); + putsTimer( 140, MENU_CONTENT_TOP + FH*0, sessionTimer); + putsTimer( 250, MENU_CONTENT_TOP + 0*FH, g_eeGeneral.globalTimer+sessionTimer, TIMEHOUR); + +#if defined(THRTRACE) + coord_t traceRd = (s_traceCnt < 0 ? s_traceWr : 0); + const coord_t x = 4; + const coord_t y = 200; + lcd_hlineStip(x-3, y, MAXTRACE+3+3, SOLID, TEXT_COLOR); + lcd_vlineStip(x, y-96, 96+3, SOLID, TEXT_COLOR); + + for (coord_t i=0; i=MAXTRACE) traceRd = 0; + if (traceRd==s_traceWr) break; + } +#endif +} + +#define MENU_DEBUG_COL1_OFS (11*10-2) +#define MENU_DEBUG_Y_MIXMAX (MENU_CONTENT_TOP + 2*FH) +#define MENU_DEBUG_Y_LUA (MENU_CONTENT_TOP + 3*FH) +#define MENU_DEBUG_Y_FREE_RAM (MENU_CONTENT_TOP + 4*FH) +#define MENU_DEBUG_Y_STACK (MENU_CONTENT_TOP + 5*FH) +#define MENU_DEBUG_Y_RTOS (MENU_CONTENT_TOP + 6*FH) + +void menuStatisticsDebug(evt_t event) +{ + drawMenuTemplate(STR_MENUDEBUG, event); + + switch(event) + { + case EVT_KEY_LONG(KEY_ENTER): + g_eeGeneral.mAhUsed = 0; + g_eeGeneral.globalTimer = 0; + eeDirty(EE_GENERAL); + sessionTimer = 0; + killEvents(event); + AUDIO_KEYPAD_UP(); + break; + case EVT_KEY_FIRST(KEY_ENTER): +#if defined(LUA) + maxLuaInterval = 0; + maxLuaDuration = 0; +#endif + maxMixerDuration = 0; + AUDIO_KEYPAD_UP(); + break; + +#if defined(DEBUG_TRACE_BUFFER) + case EVT_KEY_FIRST(KEY_UP): + pushMenu(menuTraceBuffer); + return; +#endif + + case EVT_KEY_FIRST(KEY_DOWN): + chainMenu(menuStatisticsView); + break; + case EVT_KEY_FIRST(KEY_EXIT): + chainMenu(menuMainView); + break; + } + + lcd_putsLeft(MENU_DEBUG_Y_FREE_RAM, "Free Mem"); + lcd_outdezAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_FREE_RAM, availableMemory(), LEFT, "b"); + +#if defined(LUA) + lcd_putsLeft(MENU_DEBUG_Y_LUA, "Lua scripts"); + lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_LUA+1, "[Duration]", HEADER_COLOR|SMLSIZE); + lcd_outdezAtt(MENU_DEBUG_COL1_OFS+30, MENU_DEBUG_Y_LUA, 10*maxLuaDuration, LEFT); + lcd_putsAtt(MENU_DEBUG_COL1_OFS+60, MENU_DEBUG_Y_LUA+1, "[Interval]", HEADER_COLOR|SMLSIZE); + lcd_outdezAtt(MENU_DEBUG_COL1_OFS+90, MENU_DEBUG_Y_LUA, 10*maxLuaInterval, LEFT); +#endif + + lcd_putsLeft(MENU_DEBUG_Y_MIXMAX, STR_TMIXMAXMS); + lcd_outdezAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_MIXMAX, DURATION_MS_PREC2(maxMixerDuration), PREC2|LEFT, "ms"); + + lcd_putsLeft(MENU_DEBUG_Y_RTOS, STR_FREESTACKMINB); + lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_RTOS+1, "[Menus]", HEADER_COLOR|SMLSIZE); + lcd_outdezAtt(MENU_DEBUG_COL1_OFS+30, MENU_DEBUG_Y_RTOS, menusStack.available(), LEFT); + lcd_putsAtt(MENU_DEBUG_COL1_OFS+60, MENU_DEBUG_Y_RTOS+1, "[Mix]", HEADER_COLOR|SMLSIZE); + lcd_outdezAtt(MENU_DEBUG_COL1_OFS+90, MENU_DEBUG_Y_RTOS, mixerStack.available(), LEFT); + lcd_putsAtt(MENU_DEBUG_COL1_OFS+120, MENU_DEBUG_Y_RTOS+1, "[Audio]", HEADER_COLOR|SMLSIZE); + lcd_outdezAtt(MENU_DEBUG_COL1_OFS+150, MENU_DEBUG_Y_RTOS, audioStack.available(), LEFT); + + lcd_putsCenter(7*FH+1, STR_MENUTORESET); + // lcd_status_line(); +} + + +#if defined(DEBUG_TRACE_BUFFER) +#include "stamp-opentx.h" + +void menuTraceBuffer(evt_t event) +{ + switch(event) + { + case EVT_KEY_LONG(KEY_ENTER): + dumpTraceBuffer(); + killEvents(event); + break; + } + + SIMPLE_SUBMENU("Trace Buffer " VERS_STR, TRACE_BUFFER_LEN); + /* RTC time */ + struct gtm t; + gettime(&t); + putsTime(LCD_W+1, 0, t, TIMEBLINK); + + uint8_t y = 0; + uint8_t k = 0; + int8_t sub = m_posVert; + + lcd_putc(0, FH, '#'); + lcd_puts(4*10, FH, "Time"); + lcd_puts(14*10, FH, "Event"); + lcd_puts(20*10, FH, "Data"); + + for (uint8_t i=0; itime % SECS_PER_DAY; + putsTimer(4*10, y, tme, TIMEHOUR|LEFT); + //event + lcd_outdezNAtt(14*10, y, te->event, LEADING0|LEFT, 3); + //data + lcd_putsn (20*10, y, "0x", 2); + lcd_outhex4(22*10-2, y, (uint16_t)(te->data >> 16)); + lcd_outhex4(25*10, y, (uint16_t)(te->data & 0xFFFF)); + } + + } + + +} +#endif //#if defined(DEBUG_TRACE_BUFFER) diff --git a/radio/src/gui/Horus/view_telemetry.cpp b/radio/src/gui/Horus/view_telemetry.cpp new file mode 100644 index 000000000..2e76051b0 --- /dev/null +++ b/radio/src/gui/Horus/view_telemetry.cpp @@ -0,0 +1,202 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define STATUS_BAR_Y (7*FH+1) +#define TELEM_2ND_COLUMN (11*10) + +#if defined(FRSKY_HUB) && defined(GAUGES) +bar_threshold_t barsThresholds[THLD_MAX]; +#endif + +NOINLINE uint8_t getRssiAlarmValue(uint8_t alarm) +{ + return (45 - 3*alarm + g_model.frsky.rssiAlarms[alarm].value); +} + +int barCoord(int value, int min, int max, int width) +{ + if (value <= min) + return 0; + else if (value >= max) + return width; + else + return (width * (value - min) + (max - min)/2) / (max - min); +} + +void displayGaugesTelemetryScreen(FrSkyScreenData & screen) +{ + const int GAUGE_MARGIN = 16; + const int GAUGE_HEIGHT = 25; + const int GAUGE_WIDTH = LCD_W - 2*GAUGE_MARGIN; + const int GAUGE_PADDING_HORZ = 4; + const int GAUGE_PADDING_VERT = 2; + const int VAL_WIDTH = 50; + const int BAR_WIDTH = GAUGE_WIDTH-2*GAUGE_PADDING_HORZ-VAL_WIDTH; + + for (int i=0; i<4; ++i) { + FrSkyBarData & bar = screen.bars[i]; + source_t source = bar.source; + getvalue_t barMin = bar.barMin; + getvalue_t barMax = bar.barMax; + if (source <= MIXSRC_LAST_CH) { + barMin = calc100toRESX(barMin); + barMax = calc100toRESX(barMax); + } + if (source && barMax > barMin) { + int y = GAUGE_MARGIN + i*(GAUGE_HEIGHT+GAUGE_MARGIN); + + // The black background + lcdDrawFilledRect(GAUGE_MARGIN, y, GAUGE_WIDTH, GAUGE_HEIGHT, TEXT_BGCOLOR); + + getvalue_t value = getValue(source); + LcdFlags color = TEXT_COLOR; + if (source >= MIXSRC_FIRST_TELEM) { + TelemetryItem & telemetryItem = telemetryItems[(source-MIXSRC_FIRST_TELEM)/3]; // TODO macro to convert a source to a telemetry index + if (!telemetryItem.isAvailable()) { + continue; + } + else if (telemetryItem.isOld()) { + color = ALARM_COLOR; + } + } + else if (source >= MIXSRC_FIRST_TIMER && source <= MIXSRC_LAST_TIMER) { + if (value < 0) { + putsMixerSource(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+GAUGE_PADDING_VERT, source, ALARM_COLOR); + putsTimer(GAUGE_MARGIN+GAUGE_WIDTH-VAL_WIDTH, y+GAUGE_PADDING_VERT+3, -value, LEFT|DBLSIZE|ALARM_COLOR); + // TODO lcdDrawFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+15, BAR_WIDTH, 6, ALARM_BGCOLOR); + continue; + } + } + + // The label + putsMixerSource(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+GAUGE_PADDING_VERT, source, color); + + // The value + putsChannel(GAUGE_MARGIN+GAUGE_WIDTH-VAL_WIDTH, y+GAUGE_PADDING_VERT+3, source, LEFT|DBLSIZE|NO_UNIT|color); + + // The bar + // TODO lcdDrawFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+15, BAR_WIDTH, 6, SOLID, color); + //uint8_t thresholdX = 0; + int width = barCoord(value, barMin, barMax, BAR_WIDTH-2); + //uint8_t barShade = SOLID; + lcdDrawFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ+1, y+16, width, 4, TEXT_BGCOLOR); +/* for (uint8_t j=24; j<99; j+=25) { + if (j>thresholdX || j>width) { + lcd_vline(j*BAR_WIDTH/100+BAR_LEFT+1, y+1, barHeight); + } + } + if (thresholdX) { + lcd_vlineStip(BAR_LEFT+1+thresholdX, y-2, barHeight+3, DOTTED); + lcd_hline(BAR_LEFT+thresholdX, y-2, 3); + } + */ + } + } +} + +void displayNumbersTelemetryScreen(FrSkyScreenData & screen) +{ + const int NUMBERS_W = 56; + const int NUMBERS_H = 30; + const int NUMBERS_MARGIN = 16; + const int NUMBERS_PADDING = 4; + + for (int i=0; i<4; i++) { + for (int j=0; j= MIXSRC_FIRST_TELEM) { + TelemetryItem & telemetryItem = telemetryItems[(field-MIXSRC_FIRST_TELEM)/3]; // TODO macro to convert a source to a telemetry index + if (!telemetryItem.isAvailable()) { + continue; + } + else if (telemetryItem.isOld()) { + color = RED; + } + } + else if (field >= MIXSRC_FIRST_TIMER && field <= MIXSRC_LAST_TIMER) { + TimerState & timerState = timersStates[field-MIXSRC_FIRST_TIMER]; + if (timerState.val < 0) { + color = RED; + } + putsMixerSource(x+NUMBERS_PADDING, y+2, field, color); + putsTimer(x+NUMBERS_PADDING, y+12, abs(timerState.val), LEFT|DBLSIZE|color); + continue; + } + putsMixerSource(x+NUMBERS_PADDING, y+2, field, color); + putsChannel(x+NUMBERS_PADDING, y+12, field, LEFT|DBLSIZE|NO_UNIT|color); + } + } + } +} + +void displayCustomTelemetryScreen(uint8_t index) +{ + FrSkyScreenData & screen = g_model.frsky.screens[index]; + +#if defined(GAUGES) + if (IS_BARS_SCREEN(index)) { + return displayGaugesTelemetryScreen(screen); + } +#endif + + displayNumbersTelemetryScreen(screen); +} + +void displayTelemetryScreen(int index, unsigned int evt) +{ +#if defined(LUA) + if (TELEMETRY_SCREEN_TYPE(index) == TELEMETRY_SCREEN_TYPE_SCRIPT) { + luaTask(evt, RUN_TELEM_FG_SCRIPT, true); + return; + } +#endif + + if (TELEMETRY_SCREEN_TYPE(index) == TELEMETRY_SCREEN_TYPE_NONE) { + return; + } + + if (index < MAX_TELEMETRY_SCREENS) { + return displayCustomTelemetryScreen(index); + } +} diff --git a/radio/src/gui/Horus/view_text.cpp b/radio/src/gui/Horus/view_text.cpp new file mode 100644 index 000000000..b7381d019 --- /dev/null +++ b/radio/src/gui/Horus/view_text.cpp @@ -0,0 +1,161 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +#define TEXT_FILE_MAXSIZE 2048 + +char s_text_file[TEXT_FILENAME_MAXLEN]; +char s_text_screen[LCD_LINES-1][LCD_COLS+1]; + +void readTextFile(int & lines_count) +{ + FIL file; + int result; + char c; + unsigned int sz; + int line_length = 0; + int escape = 0; + char escape_chars[2]; + int current_line = 0; + + memset(s_text_screen, 0, sizeof(s_text_screen)); + + result = f_open(&file, s_text_file, FA_OPEN_EXISTING | FA_READ); + if (result == FR_OK) { + for (int i=0; i=s_pgOfs && current_line-s_pgOfs0 && escape<3) { + escape_chars[escape-1] = c; + if (escape == 2 && !strncmp(escape_chars, "up", 2)) { + c = '\300'; + escape = 0; + } + else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) { + c = '\301'; + escape = 0; + } + else { + escape++; + continue; + } + } + else if (c=='~') { + c = 'z'+1; + } + else if (c=='\t') { + c = 0x1D; //tab + } + escape = 0; + s_text_screen[current_line-s_pgOfs][line_length++] = c; + } + } + if (c != '\n') { + current_line += 1; + } + f_close(&file); + } + + if (lines_count == 0) { + lines_count = current_line; + } +} + +void menuTextView(evt_t event) +{ + static int lines_count; + + drawMenuTemplate("TEXT VIEWER", event); + + switch (event) { + case EVT_ENTRY: + s_pgOfs = 0; + lines_count = 0; + readTextFile(lines_count); + break; + + case EVT_KEY_FIRST(KEY_UP): + if (s_pgOfs == 0) + break; + else + s_pgOfs--; + readTextFile(lines_count); + break; + + case EVT_KEY_FIRST(KEY_DOWN): + if (s_pgOfs+NUM_BODY_LINES >= lines_count) + break; + else + ++s_pgOfs; + readTextFile(lines_count); + break; + + case EVT_KEY_BREAK(KEY_EXIT): + popMenu(); + break; + } + + for (int i=0; i NUM_BODY_LINES) { + displayScrollbar(LCD_W-5, 30, MENU_FOOTER_TOP-34, s_pgOfs, lines_count, NUM_BODY_LINES); + } +} + +void pushMenuTextView(const char *filename) +{ + if (strlen(filename) < TEXT_FILENAME_MAXLEN) { + strcpy(s_text_file, filename); + pushMenu(menuTextView); + } +} diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp new file mode 100644 index 000000000..de9846e35 --- /dev/null +++ b/radio/src/gui/Horus/widgets.cpp @@ -0,0 +1,315 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" +#include + +#define COLUMN_HEADER_X 150 +void displayHeader(const char *header) +{ + lcd_putsAtt(COLUMN_HEADER_X, MENU_FOOTER_TOP, header, HEADER_COLOR); +} + +void displayColumnHeader(const char * const *headers, uint8_t index) +{ + displayHeader(headers[index]); +} + +void drawStick(coord_t centrex, int16_t xval, int16_t yval) +{ +#define BOX_CENTERY (170 - FH - BOX_WIDTH/2) +#define MARKER_WIDTH 5 + + lcd_square(centrex-BOX_WIDTH/2, BOX_CENTERY-BOX_WIDTH/2, BOX_WIDTH, TEXT_COLOR); + lcd_vlineStip(centrex, BOX_CENTERY-1, 3, SOLID, TEXT_COLOR); + lcd_hlineStip(centrex-1, BOX_CENTERY, 3, SOLID, TEXT_COLOR); + lcd_square(centrex + (xval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, BOX_CENTERY - (yval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, MARKER_WIDTH, ROUND|TEXT_COLOR); + +#undef BOX_CENTERY +#undef MARKER_WIDTH +} + +void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) +{ + if (attr) { + lcdDrawFilledRect(x-1, y+2, 13, 13, TEXT_INVERTED_BGCOLOR); + lcdDrawFilledRect(x+1, y+4, 9, 9, TEXT_BGCOLOR); + if (value) { + lcdDrawFilledRect(x+2, y+5, 7, 7, TEXT_INVERTED_BGCOLOR); + } + } + else { + if (value) { + lcdDrawFilledRect(x+2, y+5, 7, 7, SCROLLBOX_COLOR); + lcdDrawRect(x, y+3, 11, 11, LINE_COLOR); + } + else { + lcdDrawRect(x, y+3, 11, 11, LINE_COLOR); + } + } +} + +void displayScreenIndex(uint8_t index, uint8_t count) +{ + // char str[10]; + // sprintf(str, "%d/%d", index+1, count); + // lcd_putsAtt(LCD_W-8-getTextWidth(str), MENU_FOOTER_TOP, str, m_posVert < 0 ? INVERS : TEXT_COLOR); +} + +void displayScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) +{ + lcdDrawVerticalLine(x, y, h, LINE_COLOR); + coord_t yofs = (h*offset + count/2) / count; + coord_t yhgt = (h*visible + count/2) / count; + if (yhgt + yofs > h) + yhgt = h - yofs; + lcdDrawFilledRect(x-1, y + yofs, 3, yhgt, SCROLLBOX_COLOR); +} + +void displayProgressBar(const char *label) +{ + lcd_putsLeft(4*FH, label); + lcd_rect(3, 6*FH+4, 204, 7); + lcdRefresh(); +} + +void updateProgressBar(int num, int den) +{ + if (num > 0 && den > 0) { + int width = (200*num)/den; + lcd_hline(5, 6*FH+6, width, FORCE); + lcd_hline(5, 6*FH+7, width, FORCE); + lcd_hline(5, 6*FH+8, width, FORCE); + lcdRefresh(); + } +} + +void title(const pm_char * s) +{ + int width = getTextWidth(s); + lcd_putsAtt(MENU_TITLE_LEFT, MENU_TITLE_TOP+2, s, MENU_TITLE_COLOR); +} + +const uint8_t LBM_MODEL_SETUP_ICON[] = { +#include "../../bitmaps/Horus/mask_model_setup.lbm" +}; + +const uint8_t LBM_HELI_ICON[] = { +#include "../../bitmaps/Horus/mask_heli.lbm" +}; + +const uint8_t LBM_FLIGHT_MODES_ICON[] = { +#include "../../bitmaps/Horus/mask_flight_modes.lbm" +}; + +const uint8_t LBM_INPUTS_ICON[] = { +#include "../../bitmaps/Horus/mask_inputs.lbm" +}; + +const uint8_t LBM_MIXER_ICON[] = { +#include "../../bitmaps/Horus/mask_mixer.lbm" +}; + +const uint8_t LBM_OUTPUTS_ICON[] = { +#include "../../bitmaps/Horus/mask_outputs.lbm" +}; + +const uint8_t LBM_CURVES_ICON[] = { +#include "../../bitmaps/Horus/mask_curves.lbm" +}; + +const uint8_t LBM_GVARS_ICON[] = { +#include "../../bitmaps/Horus/mask_gvars.lbm" +}; + +const uint8_t LBM_LOGICAL_SWITCHES_ICON[] = { +#include "../../bitmaps/Horus/mask_logical_switches.lbm" +}; + +const uint8_t LBM_SPECIAL_FUNCTIONS_ICON[] = { +#include "../../bitmaps/Horus/mask_special_functions.lbm" +}; + +const uint8_t LBM_LUA_SCRIPTS_ICON[] = { +#include "../../bitmaps/Horus/mask_lua_scripts.lbm" +}; + +const uint8_t LBM_TELEMETRY_ICON[] = { +#include "../../bitmaps/Horus/mask_telemetry.lbm" +}; +const uint8_t * const LBM_MODEL_ICONS[] = { + LBM_MODEL_SETUP_ICON, + CASE_HELI(LBM_HELI_ICON) + CASE_FLIGHT_MODES(LBM_FLIGHT_MODES_ICON) + LBM_INPUTS_ICON, + LBM_MIXER_ICON, + LBM_OUTPUTS_ICON, + CASE_CURVES(LBM_CURVES_ICON) + CASE_GVARS(LBM_GVARS_ICON) + LBM_LOGICAL_SWITCHES_ICON, + LBM_SPECIAL_FUNCTIONS_ICON, +#if defined(LUA_MODEL_SCRIPTS) + LBM_LUA_SCRIPTS_ICON, +#endif + LBM_TELEMETRY_ICON +}; + +const uint8_t LBM_BALL[] = { +#include "../../bitmaps/Horus/mask_ball.lbm" +}; + +#define MENU_ICONS_SPACING 34 + +void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCount) +{ + // clear the screen + lcdDrawFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); + lcdDrawFilledRect(0, MENU_TITLE_TOP, LCD_W, MENU_TITLE_HEIGHT, TITLE_BGCOLOR); + lcdDrawFilledRect(0, MENU_BODY_TOP, LCD_W, MENU_BODY_HEIGHT, TEXT_BGCOLOR); + lcdDrawFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); + + lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-11, 0, 36, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); + + for (int i=0; i 0) { + displayScreenIndex(pageIndex, pageCount); + } +} + +select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, evt_t event) +{ + lcd_putsColumnLeft(x, y, label); + if (values) lcd_putsiAtt(x, y, values, value-min, attr); + if (attr) value = checkIncDec(event, value, min, max, (g_menuPos[0] == 0) ? EE_MODEL : EE_GENERAL); + return value; +} + +uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, const pm_char *label, LcdFlags attr, evt_t event ) +{ + lcdDrawCheckBox(x, y, value, attr); + return selectMenuItem(x, y, label, NULL, value, 0, 1, attr, event); +} + +int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, evt_t event) +{ + lcd_putsColumnLeft(x, y, STR_SWITCH); + putsSwitches(x, y, value, attr); + if (attr) CHECK_INCDEC_MODELSWITCH(event, value, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); + return value; +} + +void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) +{ + const int width = 50; + lcd_hlineStip(x, y+5, width, SOLID, TEXT_COLOR); + if (attr && (!(attr & BLINK) || !BLINK_ON_PHASE)) { + lcdDrawFilledRect(x+value*(width-5)/max, y, 5, 11, TEXT_INVERTED_BGCOLOR); + } + else { + lcdDrawFilledRect(x+value*(width-5)/max, y, 5, 11, LINE_COLOR); + } +} + +#if defined(GVARS) +bool noZero(int val) +{ + return val != 0; +} + +int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t max, LcdFlags attr, uint8_t editflags, evt_t event) +{ + uint16_t delta = GV_GET_GV1_VALUE(max); + bool invers = (attr & INVERS); + + // TRACE("gvarMenuItem(val=%d min=%d max=%d)", value, min, max); + + if (invers && event == EVT_KEY_LONG(KEY_ENTER)) { + s_editMode = !s_editMode; + if (attr & PREC1) + value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode)*10 : delta); + else + value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode) : delta); + eeDirty(EE_MODEL); + } + + if (GV_IS_GV_VALUE(value, min, max)) { + if (attr & LEFT) + attr -= LEFT; /* because of ZCHAR */ + else + x -= 20; + + attr &= ~PREC1; + + int8_t idx = (int16_t) GV_INDEX_CALC_DELTA(value, delta); + if (idx >= 0) ++idx; // transform form idx=0=GV1 to idx=1=GV1 in order to handle double keys invert + if (invers) { + CHECK_INCDEC_MODELVAR_CHECK(event, idx, -MAX_GVARS, MAX_GVARS, noZero); + if (idx == 0) idx = 1; // handle reset to zero, map to GV1 + } + if (idx < 0) { + value = (int16_t) GV_CALC_VALUE_IDX_NEG(idx, delta); + idx = -idx; + putsStrIdx(x, y, STR_GV, idx, attr, "-"); + } + else { + putsStrIdx(x, y, STR_GV, idx, attr); + value = (int16_t) GV_CALC_VALUE_IDX_POS(idx-1, delta); + } + } + else { + lcd_outdezAtt(x, y, value, attr, "%"); + if (invers) value = checkIncDec(event, value, min, max, EE_MODEL | editflags); + } + return value; +} +#else +int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t max, LcdFlags attr, evt_t event) +{ + lcd_outdezAtt(x, y, value, attr, "%"); + if (attr&INVERS) value = checkIncDec(event, value, min, max, EE_MODEL); + return value; +} +#endif diff --git a/radio/src/gui/gui.h b/radio/src/gui/gui.h index 6b2f02df6..0d9baa30c 100644 --- a/radio/src/gui/gui.h +++ b/radio/src/gui/gui.h @@ -37,7 +37,11 @@ #ifndef _GUI_H_ #define _GUI_H_ -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + #include "Horus/gui.h" +#elif defined(PCBFLAMENCO) + #include "Flamenco/gui.h" +#elif defined(PCBTARANIS) #include "Taranis/gui.h" #else #include "9X/gui.h" diff --git a/radio/src/haptic.cpp b/radio/src/haptic.cpp index 2a8e661bd..3856f0d9a 100644 --- a/radio/src/haptic.cpp +++ b/radio/src/haptic.cpp @@ -54,7 +54,8 @@ void hapticQueue::heartbeat() #else if (buzzTimeLeft > 0) { buzzTimeLeft--; // time gets counted down -#if defined(PCBSKY9X) || (defined(PCBTARANIS) && defined(REVPLUS)) +#if defined(PCBSKY9X) || (defined(PCBTARANIS) && defined(REVPLUS)) || defined(PCBFLAMENCO) || defined(PCBHORUS) + // TODO define HAPTIC_PWM option hapticOn(HAPTIC_STRENGTH() * 20); #else if (hapticTick-- > 0) { diff --git a/radio/src/keys.cpp b/radio/src/keys.cpp index a6adb6e91..f4bfa790a 100644 --- a/radio/src/keys.cpp +++ b/radio/src/keys.cpp @@ -36,15 +36,15 @@ #include "opentx.h" -uint8_t s_evt; +evt_t s_evt; struct t_inactivity inactivity = {0}; #if defined(CPUARM) -uint8_t getEvent(bool trim) +evt_t getEvent(bool trim) { - uint8_t evt = s_evt; + evt_t evt = s_evt; int8_t k = EVT_KEY_MASK(s_evt) - TRM_BASE; - bool trim_evt = (k>=0 && k<8); + bool trim_evt = (k>=0 && k 0 || defined(ROTARY_ENCODER_NAVIGATION) BTN_REa, #endif @@ -72,7 +92,24 @@ enum EnumKeys { NUM_KEYS, SW_BASE=NUM_KEYS, -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + SW_SA0=SW_BASE, + SW_SA1, + SW_SA2, + SW_SB0, + SW_SB2, + SW_SC0, + SW_SC1, + SW_SC2, + SW_SC3, + SW_SC4, + SW_SC5, + SW_SE0, + SW_SE2, + SW_SF0, + SW_SF1, + SW_SF2, +#elif defined(PCBTARANIS) || defined(PCBHORUS) SW_SA0=SW_BASE, SW_SA1, SW_SA2, @@ -145,26 +182,42 @@ enum EnumKeys { #define EVT_KEY_MASK(e) ((e) & 0x1f) +#if defined(PCBHORUS) +#define _MSK_KEY_BREAK 0x0200 +#define _MSK_KEY_REPT 0x0400 +#define _MSK_KEY_FIRST 0x0600 +#define _MSK_KEY_LONG 0x0800 +#define _MSK_KEY_FLAGS 0x0e00 +#define EVT_ENTRY 0x1000 +#define EVT_ENTRY_UP 0x2000 +#define EVT_MENU_UP 0x4000 +#else #define _MSK_KEY_BREAK 0x20 #define _MSK_KEY_REPT 0x40 #define _MSK_KEY_FIRST 0x60 #define _MSK_KEY_LONG 0x80 +#define _MSK_KEY_FLAGS 0xe0 +#define EVT_ENTRY 0xbf +#define EVT_ENTRY_UP 0xbe +#define EVT_MENU_UP 0xbd +#endif #define EVT_KEY_BREAK(key) ((key)|_MSK_KEY_BREAK) #define EVT_KEY_FIRST(key) ((key)|_MSK_KEY_FIRST) #define EVT_KEY_REPT(key) ((key)|_MSK_KEY_REPT) #define EVT_KEY_LONG(key) ((key)|_MSK_KEY_LONG) -#define IS_KEY_BREAK(evt) (((evt)&0xe0) == _MSK_KEY_BREAK) -#define IS_KEY_FIRST(evt) (((evt)&0xe0) == _MSK_KEY_FIRST) -#define IS_KEY_LONG(evt) (((evt)&0xe0) == _MSK_KEY_LONG) -#define IS_KEY_REPT(evt) (((evt)&0xe0) == _MSK_KEY_REPT) +#define IS_KEY_BREAK(evt) (((evt) & _MSK_KEY_FLAGS) == _MSK_KEY_BREAK) +#define IS_KEY_FIRST(evt) (((evt) & _MSK_KEY_FLAGS) == _MSK_KEY_FIRST) +#define IS_KEY_LONG(evt) (((evt) & _MSK_KEY_FLAGS) == _MSK_KEY_LONG) +#define IS_KEY_REPT(evt) (((evt) & _MSK_KEY_FLAGS) == _MSK_KEY_REPT) -#define EVT_ENTRY 0xbf -#define EVT_ENTRY_UP 0xbe -#define EVT_MENU_UP 0xbd - -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + #define EVT_ROTARY_BREAK EVT_KEY_BREAK(KEY_ENTER) + #define EVT_ROTARY_LONG EVT_KEY_LONG(KEY_ENTER) + #define EVT_ROTARY_LEFT 0xDF00 + #define EVT_ROTARY_RIGHT 0xDE00 +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define EVT_ROTARY_BREAK EVT_KEY_BREAK(KEY_ENTER) #define EVT_ROTARY_LONG EVT_KEY_LONG(KEY_ENTER) #else @@ -174,7 +227,19 @@ enum EnumKeys { #define EVT_ROTARY_RIGHT 0xde #endif -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + #define IS_ROTARY_LEFT(evt) (evt==EVT_KEY_FIRST(KEY_MINUS) || evt==EVT_KEY_REPT(KEY_MINUS)) + #define IS_ROTARY_RIGHT(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) + #define IS_ROTARY_UP(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) + #define IS_ROTARY_DOWN(evt) (evt==EVT_KEY_FIRST(KEY_MINUS) || evt==EVT_KEY_REPT(KEY_MINUS)) + #define IS_ROTARY_BREAK(evt) (evt==EVT_KEY_BREAK(KEY_ENTER)) + #define IS_ROTARY_LONG(evt) (evt==EVT_KEY_LONG(KEY_ENTER)) + #define IS_ROTARY_EVENT(evt) (0) + #define CASE_EVT_ROTARY_BREAK /*case EVT_KEY_BREAK(KEY_ENTER):*/ + #define CASE_EVT_ROTARY_LONG /*case EVT_KEY_LONG(KEY_ENTER):*/ + #define CASE_EVT_ROTARY_LEFT case EVT_KEY_FIRST(KEY_MOVE_UP): case EVT_KEY_REPT(KEY_MOVE_UP): + #define CASE_EVT_ROTARY_RIGHT case EVT_KEY_FIRST(KEY_MOVE_DOWN): case EVT_KEY_REPT(KEY_MOVE_DOWN): +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) #define IS_ROTARY_LEFT(evt) (evt==EVT_KEY_FIRST(KEY_MINUS) || evt==EVT_KEY_REPT(KEY_MINUS)) #define IS_ROTARY_RIGHT(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) #define IS_ROTARY_UP(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) @@ -247,7 +312,13 @@ class Key extern Key keys[NUM_KEYS]; -extern uint8_t s_evt; +#if defined(PCBHORUS) +typedef uint16_t evt_t; +#else +typedef uint8_t evt_t; +#endif + +extern evt_t s_evt; #define putEvent(evt) s_evt = evt @@ -256,10 +327,10 @@ void killEvents(uint8_t enuk); #if defined(CPUARM) bool clearKeyEvents(); - uint8_t getEvent(bool trim=false); + evt_t getEvent(bool trim=false); #else void clearKeyEvents(); - uint8_t getEvent(); + evt_t getEvent(); #endif uint8_t keyDown(); diff --git a/radio/src/logs.cpp b/radio/src/logs.cpp index 6d0ee2aac..075350856 100644 --- a/radio/src/logs.cpp +++ b/radio/src/logs.cpp @@ -41,7 +41,7 @@ FIL g_oLogFile = {0}; const pm_char * g_logError = NULL; uint8_t logDelay; -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define get2PosState(sw) (switchState(SW_ ## sw ## 0) ? -1 : 1) #else #define get2PosState(sw) (switchState(SW_ ## sw) ? -1 : 1) @@ -49,7 +49,7 @@ uint8_t logDelay; #define get3PosState(sw) (switchState(SW_ ## sw ## 0) ? -1 : (switchState(SW_ ## sw ## 2) ? 1 : 0)) -const pm_char *openLogs() +const pm_char * openLogs() { // Determine and set log file filename FRESULT result; @@ -330,7 +330,14 @@ void writeLogs() f_printf(&g_oLogFile, "%d,", calibratedStick[i]); } -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + int result = f_printf(&g_oLogFile, "%d,%d,%d,%d\n", + get3PosState(SA), + get3PosState(SB), + // get3PosState(SC), + get2PosState(SE), + get3PosState(SF)); +#elif defined(PCBTARANIS) || defined(PCBHORUS) int result = f_printf(&g_oLogFile, "%d,%d,%d,%d,%d,%d,%d,%d\n", get3PosState(SA), get3PosState(SB), diff --git a/radio/src/lua_api.cpp b/radio/src/lua_api.cpp index 4b2d99839..7cd53d466 100644 --- a/radio/src/lua_api.cpp +++ b/radio/src/lua_api.cpp @@ -52,7 +52,11 @@ extern "C" { } #endif -#include "lua_exports.inc" // this line must be after lua headers +#if defined(PCBFLAMENCO) + #include "lua_exports_flamenco.inc" // this line must be after lua headers +#elif defined(PCBTARANIS) + #include "lua_exports_taranis.inc" // this line must be after lua headers +#endif #define lua_registernumber(L, n, i) (lua_pushnumber(L, (i)), lua_setglobal(L, (n))) #define lua_registerint(L, n, i) (lua_pushinteger(L, (i)), lua_setglobal(L, (n))) @@ -463,7 +467,11 @@ static int luaLcdDrawTimer(lua_State *L) int y = luaL_checkinteger(L, 2); int seconds = luaL_checkinteger(L, 3); unsigned int att = luaL_optunsigned(L, 4, 0); +#if defined(PCBFLAMENCO) + putsTimer(x, y, seconds, att|LEFT); +#else putsTimer(x, y, seconds, att|LEFT, att); +#endif return 0; } @@ -530,6 +538,7 @@ static int luaLcdDrawSource(lua_State *L) return 0; } +#if !defined(PCBFLAMENCO) static int luaLcdDrawPixmap(lua_State *L) { if (!luaLcdAllowed) return 0; @@ -543,6 +552,7 @@ static int luaLcdDrawPixmap(lua_State *L) } return 0; } +#endif static int luaLcdDrawRectangle(lua_State *L) { @@ -586,6 +596,7 @@ static int luaLcdDrawGauge(lua_State *L) return 0; } +#if !defined(PCBFLAMENCO) static int luaLcdDrawScreenTitle(lua_State *L) { if (!luaLcdAllowed) return 0; @@ -599,6 +610,7 @@ static int luaLcdDrawScreenTitle(lua_State *L) return 0; } +#endif static int luaLcdDrawCombobox(lua_State *L) { @@ -652,7 +664,11 @@ static int luaModelGetInfo(lua_State *L) { lua_newtable(L); lua_pushtablezstring(L, "name", g_model.header.name); +#if defined(PCBFLAMENCO) + lua_pushtableinteger(L, "bitmap", g_model.header.bitmap); +#else lua_pushtablenzstring(L, "bitmap", g_model.header.bitmap); +#endif return 1; } @@ -668,8 +684,12 @@ static int luaModelSetInfo(lua_State *L) memcpy(modelHeaders[g_eeGeneral.currModel].name, g_model.header.name, sizeof(g_model.header.name)); } else if (!strcmp(key, "bitmap")) { +#if defined(PCBFLAMENCO) + g_model.header.bitmap = luaL_checkinteger(L, -1); +#else const char * name = luaL_checkstring(L, -1); strncpy(g_model.header.bitmap, name, sizeof(g_model.header.bitmap)); +#endif } } eeDirty(EE_MODEL); @@ -1456,6 +1476,26 @@ const luaR_value_entry opentxConstants[] = { { "MIDSIZE", MIDSIZE }, { "SMLSIZE", SMLSIZE }, { "INVERS", INVERS }, +#if defined(PCBFLAMENCO) + { "WHITE", WHITE }, + { "BLACK", BLACK }, + { "YELLOW", YELLOW }, + { "BLUE", BLUE }, + { "GREY_DEFAULT", GREY_DEFAULT }, + { "DARKGREY", DARKGREY }, + { "RED", RED }, + { "LIGHTGREY", LIGHTGREY }, + { "WHITE_ON_BLACK", WHITE_ON_BLACK }, + { "RED_ON_BLACK", RED_ON_BLACK }, + { "BLUE_ON_BLACK", BLUE_ON_BLACK }, + { "GREY_ON_BLACK", GREY_ON_BLACK }, + { "LIGHTGREY_ON_BLACK", LIGHTGREY_ON_BLACK }, + { "YELLOW_ON_BLACK", YELLOW_ON_BLACK }, + { "WHITE_ON_DARKGREY", WHITE_ON_DARKGREY }, + { "WHITE_ON_BLUE", WHITE_ON_BLUE }, + { "BLACK_ON_YELLOW", BLACK_ON_YELLOW }, + { "LIGHTGREY_ON_YELLOW", LIGHTGREY_ON_YELLOW }, +#endif { "BOLD", BOLD }, { "BLINK", BLINK }, { "FIXEDWIDTH", FIXEDWIDTH }, @@ -1473,11 +1513,15 @@ const luaR_value_entry opentxConstants[] = { { "MIXSRC_SA", MIXSRC_SA }, { "MIXSRC_SB", MIXSRC_SB }, { "MIXSRC_SC", MIXSRC_SC }, +#if defined(PCBTARANIS) { "MIXSRC_SD", MIXSRC_SD }, +#endif { "MIXSRC_SE", MIXSRC_SE }, { "MIXSRC_SF", MIXSRC_SF }, +#if defined(PCBTARANIS) { "MIXSRC_SG", MIXSRC_SG }, { "MIXSRC_SH", MIXSRC_SH }, +#endif { "MIXSRC_CH1", MIXSRC_CH1 }, { "SWSRC_LAST", SWSRC_LAST_LOGICAL_SWITCH }, { "EVT_MENU_BREAK", EVT_KEY_BREAK(KEY_MENU) }, @@ -1553,8 +1597,10 @@ const luaL_Reg lcdLib[] = { { "drawChannel", luaLcdDrawChannel }, { "drawSwitch", luaLcdDrawSwitch }, { "drawSource", luaLcdDrawSource }, +#if !defined(PCBFLAMENCO) { "drawPixmap", luaLcdDrawPixmap }, { "drawScreenTitle", luaLcdDrawScreenTitle }, +#endif { "drawCombobox", luaLcdDrawCombobox }, { NULL, NULL } /* sentinel */ }; @@ -1945,12 +1991,12 @@ void luaDoOneRunStandalone(uint8_t evt) return; } else if (luaDisplayStatistics) { - lcd_hline(0, 7*FH-1, lcdLastPos+FW, ERASE); + lcd_hline(0, 7*FH-1, lcdLastPos+6, ERASE); lcd_puts(0, 7*FH, "GV Use: "); lcd_outdezAtt(lcdLastPos, 7*FH, luaGetMemUsed(), LEFT); lcd_putc(lcdLastPos, 7*FH, 'b'); - lcd_hline(0, 7*FH-2, lcdLastPos+FW, FORCE); - lcd_vlineStip(lcdLastPos+FW, 7*FH-2, FH+2, SOLID, FORCE); + lcd_hline(0, 7*FH-2, lcdLastPos+6, FORCE); + lcd_vlineStip(lcdLastPos+6, 7*FH-2, FH+2, SOLID, FORCE); } } } @@ -2021,8 +2067,12 @@ bool luaDoOneRunPermanentScript(uint8_t evt, int i, uint32_t scriptType) TelemetryScriptData & script = g_model.frsky.screens[sid.reference-SCRIPT_TELEMETRY_FIRST].script; filename = script.file; #endif - if ((scriptType & RUN_TELEM_FG_SCRIPT) && + if ((scriptType & RUN_TELEM_FG_SCRIPT) && +#if defined(PCBFLAMENCO) + (g_menuStack[0]==menuMainView && sid.reference==SCRIPT_TELEMETRY_FIRST+g_eeGeneral.view-VIEW_TELEM1)) { +#else (g_menuStack[0]==menuTelemetryFrsky && sid.reference==SCRIPT_TELEMETRY_FIRST+s_frsky_view)) { +#endif lua_rawgeti(L, LUA_REGISTRYINDEX, sid.run); lua_pushinteger(L, evt); inputsCount = 1; diff --git a/radio/src/main_arm.cpp b/radio/src/main_arm.cpp index bed1ce257..c692ba244 100644 --- a/radio/src/main_arm.cpp +++ b/radio/src/main_arm.cpp @@ -89,6 +89,7 @@ void checkSpeakerVolume() } } +#if defined(EEPROM) void checkEeprom() { if (!usbPlugged()) { @@ -98,38 +99,70 @@ void checkEeprom() eeCheck(false); } } +#endif -void perMain() +#if defined(PCBFLAMENCO) +void guiMain(evt_t evt) { -#if defined(PCBSKY9X) && !defined(REVA) - calcConsumption(); -#endif - checkSpeakerVolume(); - checkEeprom(); - sdMountPoll(); - writeLogs(); - handleUsbConnection(); - checkTrainerSettings(); - checkBattery(); + lcd_clear(); - uint8_t evt = getEvent(false); - if (evt && (g_eeGeneral.backlightMode & e_backlight_mode_keys)) backlightOn(); // on keypress turn the light on - checkBacklight(); -#if defined(NAVIGATION_STICKS) - uint8_t sticks_evt = getSticksNavigationEvent(); - if (sticks_evt) evt = sticks_evt; -#endif - -#if defined(USB_MASS_STORAGE) - if (usbPlugged()) { - // disable access to menus - lcd_clear(); - menuMainView(0); - lcdRefresh(); - return; +#if defined(LUA) + uint32_t t0 = get_tmr10ms(); + static uint32_t lastLuaTime = 0; + uint16_t interval = (lastLuaTime == 0 ? 0 : (t0 - lastLuaTime)); + lastLuaTime = t0; + if (interval > maxLuaInterval) { + maxLuaInterval = interval; } + + // run Lua scripts that don't use LCD (to use CPU time while LCD DMA is running) + luaTask(0, RUN_MIX_SCRIPT | RUN_FUNC_SCRIPT | RUN_TELEM_BG_SCRIPT, false); + + // wait for LCD DMA to finish before continuing, because code from this point + // is allowed to change the contents of LCD buffer + // + // WARNING: make sure no code above this line does any change to the LCD display buffer! + // + + bool scriptWasRun = luaTask(evt, RUN_STNDAL_SCRIPT, true); + + t0 = get_tmr10ms() - t0; + if (t0 > maxLuaDuration) { + maxLuaDuration = t0; + } + + if (!scriptWasRun) #endif + { + // normal GUI from menus + const char *warn = s_warning; + uint8_t menu = s_menu_count; + // lcd_clear(); + if (menuEvent) { + m_posVert = menuEvent == EVT_ENTRY_UP ? g_menuPos[g_menuStackPtr] : 0; + m_posHorz = 0; + evt = menuEvent; + menuEvent = 0; + AUDIO_MENUS(); + } + g_menuStack[g_menuStackPtr]((warn || menu) ? 0 : evt); + if (warn) DISPLAY_WARNING(evt); + if (menu) { + const char * result = displayMenu(evt); + if (result) { + menuHandler(result); + putEvent(EVT_MENU_UP); + } + } + drawStatusLine(); + } + + lcdRefresh(); +} +#else +void guiMain(evt_t evt) +{ #if defined(LUA) uint32_t t0 = get_tmr10ms(); static uint32_t lastLuaTime = 0; @@ -172,9 +205,11 @@ void perMain() // normal GUI from menus const char *warn = s_warning; uint8_t menu = s_menu_count; +#if !defined(COLORLCD) if (refreshScreen) { lcd_clear(); } +#endif if (menuEvent) { m_posVert = menuEvent == EVT_ENTRY_UP ? g_menuPos[g_menuStackPtr] : 0; m_posHorz = 0; @@ -195,19 +230,44 @@ void perMain() } lcdRefresh(); - -#if defined(REV9E) && !defined(SIMU) - topLcdRefreshStart(); - setTopFirstTimer(getValue(MIXSRC_FIRST_TIMER+g_model.topLcdTimer)); - setTopSecondTimer(g_eeGeneral.globalTimer + sessionTimer); - setTopRssi(TELEMETRY_RSSI()); - setTopBatteryValue(g_vbat100mV); - setTopBatteryState(GET_TXBATT_BARS(), IS_TXBATT_WARNING()); - topLcdRefreshEnd(); +} #endif -#if defined(REV9E) && !defined(SIMU) - bluetoothWakeup(); +void perMain() +{ +#if defined(PCBSKY9X) && !defined(REVA) + calcConsumption(); +#endif + checkSpeakerVolume(); +#if defined(EEPROM) + checkEeprom(); +#endif + sdMountPoll(); + writeLogs(); + handleUsbConnection(); + checkTrainerSettings(); + checkBattery(); + + evt_t evt = getEvent(false); + if (evt && (g_eeGeneral.backlightMode & e_backlight_mode_keys)) backlightOn(); // on keypress turn the light on + doLoopCommonActions(); +#if defined(NAVIGATION_STICKS) + uint8_t sticks_evt = getSticksNavigationEvent(); + if (sticks_evt) evt = sticks_evt; +#endif + +#if defined(USB_MASS_STORAGE) + if (usbPlugged()) { + // disable access to menus + lcd_clear(); + menuMainView(0); + lcdRefresh(); + return; + } +#endif + +#if defined(GUI) + guiMain(evt); #endif #if defined(PCBTARANIS) @@ -217,4 +277,18 @@ void perMain() } #endif +#if defined(PCBTARANIS) && defined(REV9E) && !defined(SIMU) + topLcdRefreshStart(); + setTopFirstTimer(getValue(MIXSRC_FIRST_TIMER+g_model.topLcdTimer)); + setTopSecondTimer(g_eeGeneral.globalTimer + sessionTimer); + setTopRssi(TELEMETRY_RSSI()); + setTopBatteryValue(g_vbat100mV); + setTopBatteryState(GET_TXBATT_BARS(), IS_TXBATT_WARNING()); + topLcdRefreshEnd(); +#endif + +#if defined(PCBTARANIS) && defined(REV9E) && !defined(SIMU) + bluetoothWakeup(); +#endif + } diff --git a/radio/src/main_avr.cpp b/radio/src/main_avr.cpp index ac8f0385c..2d2daa8ad 100644 --- a/radio/src/main_avr.cpp +++ b/radio/src/main_avr.cpp @@ -110,11 +110,11 @@ void perMain() writeLogs(); #endif - uint8_t evt = getEvent(); + evt_t evt = getEvent(); evt = checkTrim(evt); if (evt && (g_eeGeneral.backlightMode & e_backlight_mode_keys)) backlightOn(); // on keypress turn the light on - checkBacklight(); + doLoopCommonActions(); #if defined(FRSKY) || defined(MAVLINK) telemetryWakeup(); diff --git a/radio/src/mixer.cpp b/radio/src/mixer.cpp index 6185447e8..a6dad5ffb 100644 --- a/radio/src/mixer.cpp +++ b/radio/src/mixer.cpp @@ -281,7 +281,14 @@ getvalue_t getValue(mixsrc_t i) else if (i<=MIXSRC_TrimAil) return calc1000toRESX((int16_t)8 * getTrimValue(mixerCurrentFlightMode, i-MIXSRC_TrimRud)); -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + else if (i==MIXSRC_SA) return (switchState(SW_SA0) ? -1024 : (switchState(SW_SA1) ? 0 : 1024)); + else if (i==MIXSRC_SB) return (switchState(SW_SB0) ? -1024 : 1024); + else if (i==MIXSRC_SC) return (switchState(SW_SC0) ? -1024 : (switchState(SW_SC1) ? 0 : 1024)); + // else if (i==MIXSRC_SD) return (switchState(SW_SD0) ? -1024 : 1024); + else if (i==MIXSRC_SE) return (switchState(SW_SE0) ? -1024 : 1024); + else if (i==MIXSRC_SF) return (switchState(SW_SF0) ? -1024 : (switchState(SW_SF1) ? 0 : 1024)); +#elif defined(PCBTARANIS) || defined(PCBHORUS) else if ((i >= MIXSRC_FIRST_SWITCH) && (i <= MIXSRC_LAST_SWITCH)) { mixsrc_t sw = i-MIXSRC_FIRST_SWITCH; if (SWITCH_EXISTS(sw)) { diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 389fd66e7..bef3ff4ff 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -61,10 +61,7 @@ #define WARN_MEM (!(g_eeGeneral.warnOpts & WARN_MEM_BIT)) #define BEEP_VAL ( (g_eeGeneral.warnOpts & WARN_BVAL_BIT) >>3 ) -#if defined(PCBTARANIS) - #define EEPROM_VER 218 - #define FIRST_CONV_EEPROM_VER 216 -#elif defined(PCBSKY9X) +#if defined(CPUARM) #define EEPROM_VER 218 #define FIRST_CONV_EEPROM_VER 216 #elif defined(CPUM2560) || defined(CPUM2561) @@ -110,7 +107,35 @@ #define NUM_STICKS 4 -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + #define MAX_MODELS 60 + #define NUM_CHNOUT 32 // number of real output channels CH1-CH32 + #define MAX_FLIGHT_MODES 9 + #define MAX_MIXERS 64 + #define MAX_EXPOS 64 + #define NUM_LOGICAL_SWITCH 32 // number of custom switches + #define NUM_CFN 64 // number of functions assigned to switches + #define MAX_SCRIPTS 7 + #define MAX_INPUTS 32 + #define NUM_TRAINER 16 + #define NUM_POTS 3 + #define NUM_XPOTS 0 + #define MAX_SENSORS 32 +#elif defined(PCBFLAMENCO) + #define MAX_MODELS 60 + #define NUM_CHNOUT 32 // number of real output channels CH1-CH32 + #define MAX_FLIGHT_MODES 9 + #define MAX_MIXERS 64 + #define MAX_EXPOS 64 + #define NUM_LOGICAL_SWITCH 32 // number of custom switches + #define NUM_CFN 64 // number of functions assigned to switches + #define MAX_SCRIPTS 7 + #define MAX_INPUTS 32 + #define NUM_TRAINER 16 + #define NUM_POTS 3 + #define NUM_XPOTS 0 + #define MAX_SENSORS 32 +#elif defined(PCBTARANIS) #define MAX_MODELS 60 #define NUM_CHNOUT 32 // number of real output channels CH1-CH32 #define MAX_FLIGHT_MODES 9 @@ -210,7 +235,27 @@ struct CurveInfo { extern CurveInfo curveInfo(uint8_t idx); #endif -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + #define LEN_MODEL_NAME 12 + #define LEN_TIMER_NAME 8 + #define LEN_FLIGHT_MODE_NAME 10 + #define LEN_EXPOMIX_NAME 6 + #define LEN_CHANNEL_NAME 6 + #define LEN_INPUT_NAME 4 + #define MAX_CURVES 32 + #define NUM_POINTS 512 + #define CURVDATA CurveInfo +#elif defined(PCBFLAMENCO) + #define LEN_MODEL_NAME 12 + #define LEN_TIMER_NAME 8 + #define LEN_FLIGHT_MODE_NAME 10 + #define LEN_EXPOMIX_NAME 6 + #define LEN_CHANNEL_NAME 6 + #define LEN_INPUT_NAME 4 + #define MAX_CURVES 32 + #define NUM_POINTS 512 + #define CURVDATA CurveInfo +#elif defined(PCBTARANIS) #define LEN_MODEL_NAME 12 #define LEN_TIMER_NAME 8 #define LEN_FLIGHT_MODE_NAME 10 @@ -301,7 +346,18 @@ PACK(typedef struct { int8_t value:6; }) FrSkyRSSIAlarm; -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) || defined(PCBHORUS) +enum MainViews { + VIEW_BLANK, + VIEW_TIMERS_ALTITUDE, + VIEW_CHANNELS, + VIEW_TELEM1, + VIEW_TELEM2, + VIEW_TELEM3, + VIEW_TELEM4, + VIEW_COUNT +}; +#elif defined(PCBTARANIS) enum MainViews { VIEW_TIMERS, VIEW_INPUTS, @@ -365,20 +421,45 @@ enum BeeperMode { #define swarnenable_t uint8_t #endif -#if defined(PCBTARANIS) - enum UartModes { - UART_MODE_NONE, - UART_MODE_TELEMETRY_MIRROR, - UART_MODE_TELEMETRY, - UART_MODE_SBUS_TRAINER, - // UART_MODE_CPPM_TRAINER, - #if defined(DEBUG) - UART_MODE_DEBUG, - #endif - UART_MODE_COUNT, - UART_MODE_MAX = UART_MODE_COUNT-1 - }; +enum UartModes { +#if defined(CLI) || defined(DEBUG) + UART_MODE_DEBUG, +#else + UART_MODE_NONE, +#endif + UART_MODE_TELEMETRY_MIRROR, + UART_MODE_TELEMETRY, + UART_MODE_SBUS_TRAINER, + // UART_MODE_CPPM_TRAINER, + UART_MODE_COUNT, + UART_MODE_MAX = UART_MODE_COUNT-1 +}; +#if defined(PCBHORUS) + #define LEN_SWITCH_NAME 3 + #define LEN_ANA_NAME 3 + #define EXTRA_GENERAL_FIELDS \ + EXTRA_GENERAL_FIELDS_ARM \ + uint8_t serial2Mode:6; \ + uint8_t spare:2; \ + CustomFunctionData customFn[NUM_CFN]; \ + uint32_t switchConfig; \ + uint8_t potsType; /*two bits for every pot*/\ + char switchNames[NUM_SWITCHES][LEN_SWITCH_NAME]; \ + char anaNames[NUM_STICKS+NUM_POTS][LEN_ANA_NAME]; +#elif defined(PCBFLAMENCO) + #define LEN_SWITCH_NAME 3 + #define LEN_ANA_NAME 3 + #define EXTRA_GENERAL_FIELDS \ + EXTRA_GENERAL_FIELDS_ARM \ + uint8_t serial2Mode:6; \ + uint8_t spare:2; \ + CustomFunctionData customFn[NUM_CFN]; \ + uint32_t switchConfig; \ + uint8_t potsType; /*two bits for every pot*/\ + char switchNames[NUM_SWITCHES][LEN_SWITCH_NAME]; \ + char anaNames[NUM_STICKS+NUM_POTS][LEN_ANA_NAME]; +#elif defined(PCBTARANIS) #define LEN_SWITCH_NAME 3 #define LEN_ANA_NAME 3 #define LEN_BLUETOOTH_NAME 10 @@ -391,7 +472,6 @@ enum BeeperMode { #else #define BLUETOOTH_FIELDS #endif - #define EXTRA_GENERAL_FIELDS \ EXTRA_GENERAL_FIELDS_ARM \ uint8_t serial2Mode:6; \ @@ -442,7 +522,35 @@ PACK(typedef struct { int8_t inputs[MAX_SCRIPT_INPUTS]; }) ScriptData; -#if defined(PCBTARANIS) +enum PotsWarnMode { + POTS_WARN_OFF, + POTS_WARN_MANUAL, + POTS_WARN_AUTO +}; + +#if defined(PCBHORUS) + enum ModuleIndex { + EXTERNAL_MODULE, + TRAINER_MODULE, + }; + enum TrainerMode { + TRAINER_MODE_MASTER, + TRAINER_MODE_SLAVE + }; + #define MODELDATA_BITMAP uint8_t bitmap; + #define MODELDATA_EXTRA uint8_t externalModule:3; uint8_t trainerMode:3; uint8_t potsWarnMode:2; ModuleData moduleData[NUM_MODULES+1]; char curveNames[MAX_CURVES][6]; ScriptData scriptsData[MAX_SCRIPTS]; char inputNames[MAX_INPUTS][LEN_INPUT_NAME]; uint8_t potsWarnEnabled; int8_t potsWarnPosition[NUM_POTS]; +#elif defined(PCBFLAMENCO) + enum ModuleIndex { + EXTERNAL_MODULE, + TRAINER_MODULE, + }; + enum TrainerMode { + TRAINER_MODE_MASTER, + TRAINER_MODE_SLAVE + }; + #define MODELDATA_BITMAP uint8_t bitmap; + #define MODELDATA_EXTRA uint8_t externalModule:3; uint8_t trainerMode:3; uint8_t potsWarnMode:2; ModuleData moduleData[NUM_MODULES+1]; char curveNames[MAX_CURVES][6]; ScriptData scriptsData[MAX_SCRIPTS]; char inputNames[MAX_INPUTS][LEN_INPUT_NAME]; uint8_t potsWarnEnabled; int8_t potsWarnPosition[NUM_POTS]; +#elif defined(PCBTARANIS) enum ModuleIndex { INTERNAL_MODULE, EXTERNAL_MODULE, @@ -455,11 +563,6 @@ PACK(typedef struct { TRAINER_MODE_MASTER_CPPM_EXTERNAL_MODULE, TRAINER_MODE_MASTER_BATTERY_COMPARTMENT, }; - enum PotsWarnMode { - POTS_WARN_OFF, - POTS_WARN_MANUAL, - POTS_WARN_AUTO - }; #define IS_TRAINER_EXTERNAL_MODULE() (g_model.trainerMode == TRAINER_MODE_MASTER_SBUS_EXTERNAL_MODULE || g_model.trainerMode == TRAINER_MODE_MASTER_CPPM_EXTERNAL_MODULE) #define MODELDATA_BITMAP char bitmap[LEN_BITMAP_NAME]; #define MODELDATA_EXTRA uint8_t spare:3; uint8_t trainerMode:3; uint8_t potsWarnMode:2; ModuleData moduleData[NUM_MODULES+1]; char curveNames[MAX_CURVES][6]; ScriptData scriptsData[MAX_SCRIPTS]; char inputNames[MAX_INPUTS][LEN_INPUT_NAME]; uint8_t potsWarnEnabled; int8_t potsWarnPosition[NUM_POTS]; @@ -494,7 +597,7 @@ enum BacklightMode { #define XPOTS_MULTIPOS_COUNT 6 -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) PACK(typedef struct { uint8_t count; uint8_t steps[XPOTS_MULTIPOS_COUNT-1]; @@ -720,7 +823,7 @@ PACK(typedef struct { #define CFN_GVAR_CST_MAX 125 #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) enum SwitchConfig { SWITCH_NONE, SWITCH_TOGGLE, @@ -815,7 +918,7 @@ PACK(typedef struct { #define MODE_CURVE 1 #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) PACK(typedef struct { uint16_t mode:2; uint16_t scale:14; @@ -876,7 +979,7 @@ PACK(typedef struct { #define EXPO_MODE_ENABLE(ed, v) (((v)<0 && ((ed)->mode&1)) || ((v)>=0 && ((ed)->mode&2))) #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define limit_min_max_t int16_t #define LIMIT_EXT_PERCENT 150 #define LIMIT_EXT_MAX (LIMIT_EXT_PERCENT*10) @@ -900,7 +1003,7 @@ PACK(typedef struct { #define LIMIT_OFS_RESX(lim) calc1000toRESX(LIMIT_OFS(lim)) #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) PACK(typedef struct { int32_t min:11; int32_t max:11; @@ -943,7 +1046,7 @@ PACK(typedef struct { #define SLOW_STEP 10 #define DELAY_MAX (25*DELAY_STEP) /* 25 seconds */ #define SLOW_MAX (25*SLOW_STEP) /* 25 seconds */ -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) PACK(typedef struct { int16_t weight:11; // GV1=-1024, -GV1=1023 uint16_t destCh:5; @@ -1422,7 +1525,7 @@ PACK(typedef struct { ls_telemetry_value_t barMax; // ditto for max display (would usually = ratio) }) FrSkyBarData; -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define NUM_LINE_ITEMS 3 #else #define NUM_LINE_ITEMS 2 @@ -1432,7 +1535,7 @@ PACK(typedef struct { source_t sources[NUM_LINE_ITEMS]; }) FrSkyLineData; -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define MAX_TELEM_SCRIPT_INPUTS 8 PACK(typedef struct { char file[LEN_SCRIPT_FILENAME]; @@ -1443,7 +1546,7 @@ PACK(typedef struct { typedef union { FrSkyBarData bars[4]; FrSkyLineData lines[4]; -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) TelemetryScriptData script; #endif } FrSkyScreenData; @@ -1602,7 +1705,7 @@ PACK(typedef struct { #define TRIMS_ARRAY_SIZE 5 #define trim_t int16_t #else - #if defined(PCBTARANIS) + #if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) PACK(typedef struct { int16_t value:11; uint16_t mode:5; @@ -1643,7 +1746,25 @@ enum SwitchSources { SWSRC_FIRST_SWITCH, -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + SWSRC_SA0 = SWSRC_FIRST_SWITCH, + SWSRC_SA1, + SWSRC_SA2, + SWSRC_SB0, + SWSRC_SB2, + SWSRC_SC0, + SWSRC_SC1, + SWSRC_SC2, + SWSRC_SC3, + SWSRC_SC4, + SWSRC_SC5, + SWSRC_SE0, + SWSRC_SE2, + SWSRC_SF0, + SWSRC_SF1, + SWSRC_SF2, + SWSRC_LAST_SWITCH = SWSRC_SF2, +#elif defined(PCBTARANIS) SWSRC_SA0 = SWSRC_FIRST_SWITCH, SWSRC_SA1, SWSRC_SA2, @@ -1809,7 +1930,12 @@ enum MixSources { MIXSRC_Ail, LUA_EXPORT("ail", "Aileron") MIXSRC_FIRST_POT, -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + MIXSRC_POT1 = MIXSRC_FIRST_POT, LUA_EXPORT("sd", "Potentiometer D") + MIXSRC_SLIDER1, LUA_EXPORT("ls", "Left slider") + MIXSRC_SLIDER2, LUA_EXPORT("rs", "Right slider") + MIXSRC_LAST_POT = MIXSRC_SLIDER2, +#elif defined(PCBTARANIS) MIXSRC_POT1 = MIXSRC_FIRST_POT, LUA_EXPORT("s1", "Potentiometer 1") MIXSRC_POT2, LUA_EXPORT("s2", "Potentiometer 2") MIXSRC_POT3, LUA_EXPORT("s3", "Potentiometer 3") @@ -1864,7 +1990,13 @@ enum MixSources { MIXSRC_FIRST_SWITCH, -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + MIXSRC_SA = MIXSRC_FIRST_SWITCH, LUA_EXPORT("sa", "Switch A") + MIXSRC_SB, LUA_EXPORT("sb", "Switch B") + MIXSRC_SC, LUA_EXPORT("sc", "Switch C") + MIXSRC_SE, LUA_EXPORT("se", "Switch E") + MIXSRC_SF, LUA_EXPORT("sf", "Switch F") +#elif defined(PCBTARANIS) || defined(PCBHORUS) MIXSRC_SA = MIXSRC_FIRST_SWITCH, LUA_EXPORT("sa", "Switch A") MIXSRC_SB, LUA_EXPORT("sb", "Switch B") MIXSRC_SC, LUA_EXPORT("sc", "Switch C") @@ -2109,7 +2241,11 @@ PACK(typedef struct { enum ThrottleSources { THROTTLE_SOURCE_THR, THROTTLE_SOURCE_FIRST_POT, -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBFLAMENCO) + THROTTLE_SOURCE_SD, + THROTTLE_SOURCE_LS, + THROTTLE_SOURCE_RS, +#elif defined(PCBTARANIS) && defined(REV9E) THROTTLE_SOURCE_F1 = THROTTLE_SOURCE_FIRST_POT, THROTTLE_SOURCE_F2, THROTTLE_SOURCE_F3, diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 2e24f5b99..2e0a493d2 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -262,7 +262,11 @@ void generalDefault() g_eeGeneral.variant = EEPROM_VARIANT; g_eeGeneral.contrast = 25; -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + g_eeGeneral.vBatWarn = 33; + g_eeGeneral.vBatMin = -60; // 0 is 9.0V + g_eeGeneral.vBatMax = -78; // 0 is 12.0V +#elif defined(PCBTARANIS) g_eeGeneral.potsConfig = 0x05; // S1 and S2 = pots with detent g_eeGeneral.slidersConfig = 0x03; // LS and RS = sliders with detent #endif @@ -289,11 +293,15 @@ void generalDefault() g_eeGeneral.stickMode = DEFAULT_MODE-1; #endif -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + g_eeGeneral.templateSetup = 21; /* AETR */ +#elif defined(PCBTARANIS) g_eeGeneral.templateSetup = 17; /* TAER */ #endif -#if !defined(CPUM64) +#if defined(PCBFLAMENCO) + g_eeGeneral.inactivityTimer = 50; +#elif !defined(CPUM64) g_eeGeneral.backlightMode = e_backlight_mode_all; g_eeGeneral.lightAutoOff = 2; g_eeGeneral.inactivityTimer = 10; @@ -963,6 +971,25 @@ void checkBacklight() } } +#if defined(PCBFLAMENCO) +void checkUsbChip() +{ + uint8_t reg = i2cReadBQ24195(0x00); + if (reg & 0x80) { + i2cWriteBQ24195(0x00, reg & 0x7F); + } +} +#endif + +void doLoopCommonActions() +{ + checkBacklight(); + +#if defined(PCBFLAMENCO) + checkUsbChip(); +#endif +} + void backlightOn() { lightOffCounter = ((uint16_t)g_eeGeneral.lightAutoOff*250) << 1; @@ -1058,7 +1085,7 @@ void doSplash() } #endif - checkBacklight(); + doLoopCommonActions(); } } } @@ -1223,7 +1250,7 @@ void checkTHR() break; } - checkBacklight(); + doLoopCommonActions(); wdt_reset(); } @@ -1255,7 +1282,7 @@ void alert(const pm_char * t, const pm_char *s MESSAGE_SOUND_ARG) if (keyDown()) return; // wait for key release - checkBacklight(); + doLoopCommonActions(); wdt_reset(); @@ -1286,7 +1313,7 @@ int8_t trimGvar[NUM_STICKS] = { -1, -1, -1, -1 }; #if defined(CPUARM) void checkTrims() { - uint8_t event = getEvent(true); + evt_t event = getEvent(true); if (event && !IS_KEY_BREAK(event)) { int8_t k = EVT_KEY_MASK(event) - TRM_BASE; #else @@ -2009,7 +2036,7 @@ void checkBattery() if (counter-- == 0) { counter = 10; int32_t instant_vbat = anaIn(TX_VOLTAGE); -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) instant_vbat = (instant_vbat + instant_vbat*(g_eeGeneral.txVoltageCalibration)/128) * BATT_SCALE; instant_vbat >>= 11; instant_vbat += 2; // because of the diode @@ -2382,7 +2409,11 @@ uint16_t stackAvailable() void opentxInit(OPENTX_INIT_ARGS) { + TRACE("opentxInit()"); + +#if defined(EEPROM) eeReadAll(); +#endif #if defined(CPUARM) if (UNEXPECTED_SHUTDOWN()) { @@ -2401,10 +2432,14 @@ void opentxInit(OPENTX_INIT_ARGS) } #endif + TRACE("setVolume()"); + #if defined(VOICE) setVolume(g_eeGeneral.speakerVolume+VOLUME_LEVEL_DEF); #endif + TRACE("audioQueue.start()"); + #if defined(CPUARM) audioQueue.start(); setBacklight(g_eeGeneral.backlightBright); @@ -2431,10 +2466,12 @@ void opentxInit(OPENTX_INIT_ARGS) unexpectedShutdown = 1; #endif #if defined(CPUARM) + TRACE("eeLoadModel(g_eeGeneral.currModel)"); eeLoadModel(g_eeGeneral.currModel); #endif } else { + TRACE("opentxStart()"); opentxStart(); } @@ -2450,7 +2487,7 @@ void opentxInit(OPENTX_INIT_ARGS) #endif backlightOn(); -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) serial2Init(g_eeGeneral.serial2Mode, MODEL_TELEMETRY_PROTOCOL()); #endif @@ -2462,9 +2499,12 @@ void opentxInit(OPENTX_INIT_ARGS) doMixerCalculations(); #endif + TRACE("startPulses()"); startPulses(); wdt_enable(WDTO_500MS); + + TRACE("opentxInit() end!"); } #if !defined(SIMU) @@ -2488,10 +2528,15 @@ int main(void) boardInit(); -#if defined(GUI) && !defined(PCBTARANIS) +#if defined(GUI) && !defined(PCBTARANIS) && !defined(PCBFLAMENCO) && !defined(PCBHORUS) + // TODO remove this lcdInit(); #endif +#if defined(COLORLCD) + lcdColorsInit(); +#endif + stackPaint(); #if defined(GUI) @@ -2502,7 +2547,7 @@ int main(void) #endif #if defined(GUI) && !defined(PCBTARANIS) - lcdSetRefVolt(25); + // lcdSetRefVolt(25); #endif #if defined(PCBTARANIS) @@ -2550,6 +2595,10 @@ int main(void) uint8_t shutdown_state = 0; #endif +#if defined(PCBFLAMENCO) + menuEntryTime = get_tmr10ms() - 200; +#endif + while (1) { #if defined(CPUM2560) if ((shutdown_state=pwrCheck()) > e_power_trainer) @@ -2625,7 +2674,7 @@ uint32_t pwrCheck() lcdRefreshWait(); lcd_clear(); POPUP_CONFIRMATION("Confirm Shutdown"); - uint8_t evt = getEvent(false); + evt_t evt = getEvent(false); DISPLAY_WARNING(evt); lcdRefresh(); if (s_warning_result == true) { diff --git a/radio/src/opentx.h b/radio/src/opentx.h index 8badd74a6..f9c2eb077 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -283,7 +283,11 @@ #define RESXul 1024ul #define RESXl 1024l -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + #include "targets/Horus/board_horus.h" +#elif defined(PCBFLAMENCO) + #include "targets/Flamenco/board_flamenco.h" +#elif defined(PCBTARANIS) #include "targets/taranis/board_taranis.h" #elif defined(PCBSKY9X) #include "targets/sky9x/board_sky9x.h" @@ -333,7 +337,25 @@ #include #endif -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + #define NUM_SWITCHES 5 // 8 physical switches + 6 possible from 3POS + #define NUM_SW_SRCRAW 8 + // #define SWSRC_THR SWSRC_SF2 + // #define SWSRC_GEA SWSRC_SG2 + // #define SWSRC_ID0 SWSRC_SA0 + // #define SWSRC_ID1 SWSRC_SA1 + // #define SWSRC_ID2 SWSRC_SA2 + // #define SW_DSM2_BIND SW_SH2 +#elif defined(PCBFLAMENCO) + #define NUM_SWITCHES 5 // 8 physical switches + 6 possible from 3POS + #define NUM_SW_SRCRAW 8 + // #define SWSRC_THR SWSRC_SF2 + // #define SWSRC_GEA SWSRC_SG2 + // #define SWSRC_ID0 SWSRC_SA0 + // #define SWSRC_ID1 SWSRC_SA1 + // #define SWSRC_ID2 SWSRC_SA2 + // #define SW_DSM2_BIND SW_SH2 +#elif defined(PCBTARANIS) #if defined(REV9E) #define NUM_SWITCHES 18 // yes, it's a lot! #else @@ -357,7 +379,11 @@ #define NUM_PSWITCH (SWSRC_LAST_SWITCH-SWSRC_FIRST_SWITCH+1) #define NUM_POTSSW (NUM_XPOTS*6) -#if defined(PCBTARANIS) +// TODO in keys.h! +#if defined(PCBHORUS) + #define KEY_PLUS KEY_RIGHT + #define KEY_MINUS KEY_LEFT +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) #define KEY_RIGHT KEY_PLUS #define KEY_LEFT KEY_MINUS #define KEY_UP KEY_PLUS @@ -376,7 +402,15 @@ #define memclear(p, s) memset(p, 0, s) #endif -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBHORUS) + #define IS_POT_AVAILABLE(x) (true) + #define IS_POT_MULTIPOS(x) (x==0) + #define IS_POT_WITHOUT_DETENT(x) (x==0) +#elif defined(PCBFLAMENCO) + #define IS_POT_AVAILABLE(x) (true) + #define IS_POT_MULTIPOS(x) (x==0) + #define IS_POT_WITHOUT_DETENT(x) (x==0) +#elif defined(PCBTARANIS) && defined(REV9E) #define IS_SLIDER_AVAILABLE(x) ((x)==SLIDER1 || (x)==SLIDER2 || (g_eeGeneral.slidersConfig & (0x01 << ((x)-SLIDER3)))) #define IS_POT_AVAILABLE(x) ((x)=SLIDER1 && IS_SLIDER_AVAILABLE(x))) #define IS_POT_MULTIPOS(x) ((x)>=POT1 && (x)<=POT_LAST && ((g_eeGeneral.potsConfig>>(2*((x)-POT1)))&0x03)==POT_MULTIPOS_SWITCH) @@ -450,6 +484,10 @@ #define STICK_SCROLL_DISABLE() #endif +#if defined(CLI) +#include "cli.h" +#endif + #include "eeprom_common.h" #if defined(EEPROM_RLC) @@ -486,7 +524,17 @@ #define MAX_TRAINER_CHANNELS() (8) #endif -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + #define IS_MODULE_PPM(idx) (idx==TRAINER_MODULE || (idx==EXTERNAL_MODULE && g_model.externalModule==MODULE_TYPE_PPM)) + #define IS_MODULE_XJT(idx) ((g_model.externalModule==MODULE_TYPE_XJT) && (g_model.moduleData[idx].rfProtocol != RF_PROTO_OFF)) + #if defined(DSM2) + #define IS_MODULE_DSM2(idx) (idx==EXTERNAL_MODULE && g_model.externalModule==MODULE_TYPE_DSM2) + #else + #define IS_MODULE_DSM2(idx) (false) + #endif + #define MAX_EXTERNAL_MODULE_CHANNELS() ((g_model.externalModule == MODULE_TYPE_XJT) ? maxChannelsXJT[1+g_model.moduleData[1].rfProtocol] : maxChannelsModules[g_model.externalModule]) + #define MAX_CHANNELS(idx) (idx==EXTERNAL_MODULE ? MAX_EXTERNAL_MODULE_CHANNELS() : MAX_TRAINER_CHANNELS()) +#elif defined(PCBTARANIS) #if defined(TARANIS_INTERNAL_PPM) #define IS_MODULE_PPM(idx) (idx==TRAINER_MODULE || (idx==INTERNAL_MODULE && g_model.moduleData[INTERNAL_MODULE].type==MODULE_TYPE_PPM)|| (idx==EXTERNAL_MODULE && g_model.moduleData[EXTERNAL_MODULE].type==MODULE_TYPE_PPM)) #define IS_MODULE_XJT(idx) (((idx==INTERNAL_MODULE && g_model.moduleData[INTERNAL_MODULE].type==MODULE_TYPE_XJT)|| (idx==EXTERNAL_MODULE && g_model.moduleData[EXTERNAL_MODULE].type==MODULE_TYPE_XJT)) && (g_model.moduleData[idx].rfProtocol != RF_PROTO_OFF)) @@ -589,7 +637,9 @@ enum BaseCurves { #define THRCHK_DEADBAND 16 -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + #define SPLASH_NEEDED() (true) +#elif defined(PCBTARANIS) #define SPLASH_NEEDED() (g_eeGeneral.splashMode != 3) #elif defined(CPUARM) #define SPLASH_NEEDED() (g_model.moduleData[EXTERNAL_MODULE].type != MODULE_TYPE_DSM2 && !g_eeGeneral.splashMode) @@ -599,13 +649,13 @@ enum BaseCurves { #if defined(FSPLASH) #define SPLASH_TIMEOUT (g_eeGeneral.splashMode == 0 ? 60000/*infinite=10mn*/ : ((4*100) * (g_eeGeneral.splashMode & 0x03))) -#elif defined(PCBTARANIS) +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define SPLASH_TIMEOUT (g_eeGeneral.splashMode==-4 ? 1500 : (g_eeGeneral.splashMode<=0 ? (400-g_eeGeneral.splashMode*200) : (400-g_eeGeneral.splashMode*100))) #else #define SPLASH_TIMEOUT (4*100) // 4 seconds #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define IS_RE_NAVIGATION_ENABLE() true #define NAVIGATION_RE_IDX() 0 #elif defined(ROTARY_ENCODERS) @@ -618,7 +668,7 @@ enum BaseCurves { #define HEART_TIMER_10MS 1 #define HEART_TIMER_PULSES 2 // when multiple modules this is the first one -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) #define HEART_WDT_CHECK (HEART_TIMER_10MS + (HEART_TIMER_PULSES << 0) + (HEART_TIMER_PULSES << 1)) #else #define HEART_WDT_CHECK (HEART_TIMER_10MS + HEART_TIMER_PULSES) @@ -658,7 +708,7 @@ int zchar2str(char *dest, const char *src, int size); #include "keys.h" #include "pwr.h" -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) div_t switchInfo(int switchPosition); #endif @@ -768,7 +818,7 @@ void logicalSwitchesReset(); #define LS_RECURSIVE_EVALUATION_RESET() s_last_switch_used = 0 #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) void getSwitchesPosition(bool startup); #else #define getSwitchesPosition(...) @@ -777,7 +827,7 @@ void logicalSwitchesReset(); extern swarnstate_t switches_states; swsrc_t getMovedSwitch(); -#if defined(PCBTARANIS) +#if defined(VIRTUALINPUTS) #define GET_MOVED_SOURCE_PARAMS uint8_t min int8_t getMovedSource(GET_MOVED_SOURCE_PARAMS); #define GET_MOVED_SOURCE(min, max) getMovedSource(min) @@ -793,7 +843,7 @@ swsrc_t getMovedSwitch(); #define getFlightMode() 0 #endif -#if !defined(PCBTARANIS) +#if !defined(VIRTUALINPUTS) uint8_t getTrimFlightPhase(uint8_t phase, uint8_t idx); #else #define getTrimFlightPhase(phase, idx) (phase) @@ -809,7 +859,7 @@ swsrc_t getMovedSwitch(); trim_t getRawTrimValue(uint8_t phase, uint8_t idx); int getTrimValue(uint8_t phase, uint8_t idx); -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) bool setTrimValue(uint8_t phase, uint8_t idx, int trim); #else void setTrimValue(uint8_t phase, uint8_t idx, int trim); @@ -988,7 +1038,17 @@ enum Analogs { STICK2, STICK3, STICK4, -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + POT1, + POT2, + POT_LAST = POT2, + SLIDER1, + SLIDER2, + SWITCHES1, + SWITCHES2, + SWITCHES3, + SWITCHES4, +#elif defined(PCBTARANIS) POT1, POT2, POT3, @@ -1027,6 +1087,7 @@ enum Analogs { }; void checkBacklight(); +void doLoopCommonActions(); #if defined(PCBSTD) && defined(VOICE) && !defined(SIMU) #define BACKLIGHT_ON() (Voice.Backlight = 1) @@ -1427,7 +1488,7 @@ enum AUDIO_SOUNDS { #if defined(CPUARM) AU_TRIM_END, #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) AU_STICK1_MIDDLE, AU_STICK2_MIDDLE, AU_STICK3_MIDDLE, @@ -1549,7 +1610,7 @@ union ReusableBuffer int16_t loVals[NUM_STICKS+NUM_POTS]; int16_t hiVals[NUM_STICKS+NUM_POTS]; uint8_t state; -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) struct { uint8_t stepsCount; int16_t steps[XPOTS_MULTIPOS_COUNT]; @@ -1698,7 +1759,7 @@ extern uint8_t s_frsky_view; #define EARTH_RADIUSKM ((uint32_t)6371) #define EARTH_RADIUS ((uint32_t)111194) -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) double gpsToDouble(bool neg, int16_t bp, int16_t ap); #endif void getGpsPilotPosition(); diff --git a/radio/src/pulses/dsm2_arm.cpp b/radio/src/pulses/dsm2_arm.cpp index de4739f27..774a5caf8 100644 --- a/radio/src/pulses/dsm2_arm.cpp +++ b/radio/src/pulses/dsm2_arm.cpp @@ -51,7 +51,7 @@ uint8_t dsm2BindTimer = DSM2_BIND_TIMEOUT; #define BITLEN_DSM2 (8*2) //125000 Baud => 8uS per bit -#if defined(PCBTARANIS) +#if !defined(PPM_PIN_HW_SERIAL) void _send_1(uint8_t v) { if (modulePulsesData[EXTERNAL_MODULE].dsm2.index == 0) @@ -129,7 +129,7 @@ void setupPulsesDSM2(unsigned int port) { static uint8_t dsmDat[2+6*2]={0xFF,0x00, 0x00,0xAA, 0x05,0xFF, 0x09,0xFF, 0x0D,0xFF, 0x13,0x54, 0x14,0xAA}; -#if defined(PCBSKY9X) +#if defined(PPM_PIN_HW_SERIAL) modulePulsesData[EXTERNAL_MODULE].dsm2.serialByte = 0 ; modulePulsesData[EXTERNAL_MODULE].dsm2.serialBitCount = 0 ; #else @@ -139,7 +139,7 @@ void setupPulsesDSM2(unsigned int port) modulePulsesData[EXTERNAL_MODULE].dsm2.ptr = modulePulsesData[EXTERNAL_MODULE].dsm2.pulses; -#if defined(PCBTARANIS) +#if !defined(PPM_PIN_HW_SERIAL) modulePulsesData[EXTERNAL_MODULE].dsm2.value = 100; *modulePulsesData[EXTERNAL_MODULE].dsm2.ptr++ = modulePulsesData[EXTERNAL_MODULE].dsm2.value; #endif @@ -156,7 +156,7 @@ void setupPulsesDSM2(unsigned int port) break; } -#if defined(PCBTARANIS) +#if !defined(PPM_PIN_HW_SERIAL) if (moduleFlag[port] == MODULE_BIND) dsmDat[0] |= DSM2_SEND_BIND; else if (moduleFlag[port] == MODULE_RANGECHECK) diff --git a/radio/src/pulses/pulses.h b/radio/src/pulses/pulses.h index ad88fe702..9c7ef11f2 100644 --- a/radio/src/pulses/pulses.h +++ b/radio/src/pulses/pulses.h @@ -55,7 +55,7 @@ enum ModuleFlag #define IS_RANGECHECK_ENABLE() (moduleFlag[0] == MODULE_RANGECHECK) #endif -#if defined(DSM2) && !defined(PCBTARANIS) +#if defined(DSM2) && !defined(PCBTARANIS) && !defined(PCBFLAMENCO) #define DSM2_BIND_TIMEOUT 255 // 255*11ms extern uint8_t dsm2BindTimer; #endif diff --git a/radio/src/pulses/pxx_arm.cpp b/radio/src/pulses/pxx_arm.cpp index c581a4584..99faa62d5 100644 --- a/radio/src/pulses/pxx_arm.cpp +++ b/radio/src/pulses/pxx_arm.cpp @@ -81,7 +81,7 @@ void crc(uint8_t data, unsigned int port) modulePulsesData[port].pxx.pcmCrc = (modulePulsesData[port].pxx.pcmCrc<<8) ^ (CRCTable[((modulePulsesData[port].pxx.pcmCrc>>8)^data) & 0xFF]); } -#if defined(PCBTARANIS) +#if !defined(PPM_PIN_HW_SERIAL) void putPcmPart(uint8_t value, unsigned int port) { diff --git a/radio/src/serial.cpp b/radio/src/serial.cpp index e64e0ec6d..3e868c20f 100644 --- a/radio/src/serial.cpp +++ b/radio/src/serial.cpp @@ -39,7 +39,7 @@ #include #include -#define PRINTF_BUFFER_SIZE 256 +#define PRINTF_BUFFER_SIZE 128 void serialPutc(char c) { diff --git a/radio/src/simu.cpp b/radio/src/simu.cpp index e8b9a2bf8..76c65aa5b 100644 --- a/radio/src/simu.cpp +++ b/radio/src/simu.cpp @@ -63,8 +63,10 @@ class Open9xSim: public FXMainWindow Open9xSim(){}; Open9xSim(FXApp* a); ~Open9xSim(); + void updateKeysAndSwitches(bool start=false); long onKeypress(FXObject*,FXSelector,void*); long onTimeout(FXObject*,FXSelector,void*); + void createBitmap(int index, uint16_t *data, int x, int y, int w, int h); void makeSnapshot(const FXDrawable* drawable); void doEvents(); void refreshDisplay(); @@ -93,7 +95,7 @@ Open9xSim::Open9xSim(FXApp* a): FXMainWindow(a, "OpenTX Simu", NULL, NULL, DECOR_ALL, 20, 90, 0, 0) { firstTime = true; - memset(displayBuf, 0, DISPLAY_BUFER_SIZE); + memset(displayBuf, 0, DISPLAY_BUFFER_SIZE); bmp = new FXPPMImage(getApp(),NULL,IMAGE_OWNED|IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP, W2, H2); #if defined(SIMU_AUDIO) @@ -137,7 +139,9 @@ Open9xSim::Open9xSim(FXApp* a): bmf = new FXImageFrame(this,bmp); - getApp()->addTimeout(this,2,100); + updateKeysAndSwitches(true); + + getApp()->addTimeout(this, 2, 100); } Open9xSim::~Open9xSim() @@ -163,6 +167,31 @@ Open9xSim::~Open9xSim() #endif } +void Open9xSim::createBitmap(int index, uint16_t *data, int x, int y, int w, int h) +{ + FXPNGImage snapshot(getApp(), NULL, IMAGE_OWNED, w, h); + + for (int i=0; i>8)/0x0f, 255*((z&0x0F0)>>4)/0x0f, 255*(z&0x00F)/0x0f); + snapshot.setPixel(i, j, color); + } + } + + FXFileStream stream; + char buf[32]; + sprintf(buf,"%02d.png", index); + if (stream.open(buf, FXStreamSave)) { + snapshot.savePixels(stream); + stream.close(); + TRACE("Bitmap %d (w=%d, h=%d) created", index, w, h); + } + else { + TRACE("Bitmap %d (w=%d, h=%d) error", index, w, h); + } +} + void Open9xSim::makeSnapshot(const FXDrawable* drawable) { // Construct and create an FXImage object @@ -215,30 +244,120 @@ long Open9xSim::onKeypress(FXObject*,FXSelector,void*v) return 0; } -long Open9xSim::onTimeout(FXObject*, FXSelector, void*) +void Open9xSim::updateKeysAndSwitches(bool start) { - if(hasFocus()) { - static int keys1[]={ -#if defined(PCBTARANIS) - KEY_Page_Up, KEY_MENU, - KEY_Page_Down, KEY_PAGE, - KEY_Return, KEY_ENTER, - KEY_BackSpace, KEY_EXIT, - KEY_Up, KEY_PLUS, - KEY_Down, KEY_MINUS, + static int keys1[] = { +#if defined(PCBHORUS) + KEY_Page_Up, KEY_MENU, + KEY_Return, KEY_ENTER, + KEY_BackSpace, KEY_EXIT, + KEY_Up, KEY_UP, + KEY_Down, KEY_DOWN, + KEY_Right, KEY_RIGHT, + KEY_Left, KEY_LEFT, +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) + KEY_Page_Up, KEY_MENU, + KEY_Page_Down, KEY_PAGE, + KEY_Return, KEY_ENTER, + KEY_BackSpace, KEY_EXIT, + KEY_Up, KEY_PLUS, + KEY_Down, KEY_MINUS, #else - KEY_Return, KEY_MENU, - KEY_BackSpace, KEY_EXIT, - KEY_Right, KEY_RIGHT, - KEY_Left, KEY_LEFT, - KEY_Up, KEY_UP, - KEY_Down, KEY_DOWN, + KEY_Return, KEY_MENU, + KEY_BackSpace, KEY_EXIT, + KEY_Right, KEY_RIGHT, + KEY_Left, KEY_LEFT, + KEY_Up, KEY_UP, + KEY_Down, KEY_DOWN, #endif #if defined(ROTARY_ENCODER_NAVIGATION) - KEY_F, BTN_REa, + KEY_F, BTN_REa, #endif - }; + }; + for (unsigned int i=0; igetKeyState(keys1[i])); + } + +#ifdef __APPLE__ + // gruvin: Can't use Function keys on the Mac -- too many other app conflicts. + // The ordering of these keys, Q/W,E/R,T/Y,U/I matches the on screen + // order of trim sliders + static FXuint trimKeys[] = { KEY_E, KEY_R, KEY_U, KEY_I, KEY_R, KEY_E, KEY_Y, KEY_T, KEY_Q, KEY_W }; +#else + static FXuint trimKeys[] = { KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8 }; +#endif + + for (unsigned i=0; igetKeyState(trimKeys[i])); + } + +#define SWITCH_KEY(key, swtch, states) \ + static bool state##key = 0; \ + static int8_t state_##swtch = 2; \ + static int8_t inc_##swtch = 1; \ + if (getApp()->getKeyState(KEY_##key)) { \ + if (!state##key) { \ + state_##swtch = (state_##swtch+inc_##swtch); \ + if (state_##swtch == 1+states) inc_##swtch = -1; \ + else if (state_##swtch == 2) inc_##swtch = 1; \ + state##key = true; \ + } \ + } \ + else { \ + state##key = false; \ + } \ + simuSetSwitch(swtch, state_##swtch-states); + +#if defined(PCBFLAMENCO) + SWITCH_KEY(A, 0, 3); + SWITCH_KEY(B, 1, 2); + // SWITCH_KEY(C, 2, 3); + // SWITCH_KEY(D, 3, 3); + SWITCH_KEY(E, 4, 2); + SWITCH_KEY(F, 5, 3); +#elif defined(PCBTARANIS) && defined(REV9E) + SWITCH_KEY(A, 0, 3); + SWITCH_KEY(B, 1, 3); + SWITCH_KEY(C, 2, 3); + SWITCH_KEY(D, 3, 3); + SWITCH_KEY(E, 4, 3); + SWITCH_KEY(F, 5, 3); + SWITCH_KEY(G, 6, 3); + SWITCH_KEY(H, 7, 3); + SWITCH_KEY(I, 8, 3); + SWITCH_KEY(J, 9, 3); + SWITCH_KEY(K, 10, 3); + SWITCH_KEY(L, 11, 3); + SWITCH_KEY(M, 12, 3); + SWITCH_KEY(N, 13, 3); + SWITCH_KEY(O, 14, 3); + SWITCH_KEY(P, 15, 3); + SWITCH_KEY(Q, 16, 3); + SWITCH_KEY(R, 17, 3); +#elif defined(PCBTARANIS) + SWITCH_KEY(A, 0, 3); + SWITCH_KEY(B, 1, 3); + SWITCH_KEY(C, 2, 3); + SWITCH_KEY(D, 3, 3); + SWITCH_KEY(E, 4, 3); + SWITCH_KEY(F, 5, 2); + SWITCH_KEY(G, 6, 3); + SWITCH_KEY(H, 7, 2); +#else + SWITCH_KEY(1, 0, 2); + SWITCH_KEY(2, 1, 2); + SWITCH_KEY(3, 2, 2); + SWITCH_KEY(4, 3, 3); + SWITCH_KEY(5, 4, 2); + SWITCH_KEY(6, 5, 2); + SWITCH_KEY(7, 6, 2); +#endif +} + +long Open9xSim::onTimeout(FXObject*, FXSelector, void*) +{ + if (hasFocus()) { #if defined(PCBSKY9X) && !defined(REVX) Coproc_temp = 23; Coproc_maxtemp = 28; @@ -249,23 +368,8 @@ long Open9xSim::onTimeout(FXObject*, FXSelector, void*) maxTemperature = 42; #endif - for (unsigned int i=0; igetKeyState(keys1[i])); - } - -#ifdef __APPLE__ - // gruvin: Can't use Function keys on the Mac -- too many other app conflicts. - // The ordering of these keys, Q/W,E/R,T/Y,U/I matches the on screen - // order of trim sliders - static FXuint trimKeys[] = { KEY_E, KEY_R, KEY_U, KEY_I, KEY_R, KEY_E, KEY_Y, KEY_T, KEY_Q, KEY_W }; -#else - static FXuint trimKeys[] = { KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8 }; -#endif - - for (unsigned i=0; igetKeyState(trimKeys[i])); - } - + updateKeysAndSwitches(); + #if defined(ROTARY_ENCODER_NAVIGATION) static bool rotencAction = false; if (getApp()->getKeyState(KEY_G)) { @@ -281,24 +385,6 @@ long Open9xSim::onTimeout(FXObject*, FXSelector, void*) } #endif -#define SWITCH_KEY(key, swtch, states) \ - static bool state##key = 0; \ - static int8_t state_##swtch = 2; \ - static int8_t inc_##swtch = 1; \ - if (getApp()->getKeyState(KEY_##key)) { \ - if (!state##key) { \ - state_##swtch = (state_##swtch+inc_##swtch); \ - if (state_##swtch >= 1+states) inc_##swtch = -1; \ - else if (state_##swtch <= 2) inc_##swtch = 1; \ - /* TRACE("switch " #swtch ": state: %d, inc: %d", state_##swtch, inc_##swtch); */ \ - state##key = true; \ - } \ - } \ - else { \ - state##key = false; \ - } \ - simuSetSwitch(swtch, state_##swtch-states); - #if defined(PCBTARANIS) && defined(REV9E) SWITCH_KEY(A, 0, 3); SWITCH_KEY(B, 1, 3); @@ -337,7 +423,7 @@ long Open9xSim::onTimeout(FXObject*, FXSelector, void*) SWITCH_KEY(7, 6, 2); #endif } - + per10ms(); refreshDisplay(); getApp()->addTimeout(this, 2, 10); @@ -369,7 +455,19 @@ void Open9xSim::refreshDisplay() #endif for (int x=0; x>11)/0x1f, 255*((z&0x07E0)>>5)/0x3F, 255*(z&0x001F)/0x01F); + setPixel(x, y, color); + } +#elif defined(PCBFLAMENCO) + display_t z = lcd_buf[y * LCD_W + x]; + if (1) { + FXColor color = FXRGB(255*((z&0xF00)>>8)/0x0f, 255*((z&0x0F0)>>4)/0x0f, 255*(z&0x00F)/0x0f); + setPixel(x, y, color); + } +#elif defined(PCBTARANIS) display_t * p = &lcd_buf[y / 2 * LCD_W + x]; uint8_t z = (y & 1) ? (*p >> 4) : (*p & 0x0F); if (z) { @@ -459,7 +557,7 @@ uint16_t anaIn(uint8_t chan) return th9xSim->sliders[chan]->getValue(); else if (chanknobs[chan-NUM_STICKS]->getValue(); -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) else if (chan == TX_VOLTAGE) return 1000; #elif defined(PCBSKY9X) @@ -477,3 +575,8 @@ uint16_t anaIn(uint8_t chan) else return 0; } + +void createBitmap(int index, uint16_t *data, int x, int y, int w, int h) +{ + th9xSim->createBitmap(index, data, x, y, w, h); +} diff --git a/radio/src/switches.cpp b/radio/src/switches.cpp index 8ab5ebf58..0c3988fc4 100644 --- a/radio/src/switches.cpp +++ b/radio/src/switches.cpp @@ -71,6 +71,113 @@ volatile GETSWITCH_RECURSIVE_TYPE s_last_switch_value = 0; #endif +#if defined(PCBFLAMENCO) || defined(PCBHORUS) +tmr10ms_t potsLastposStart[1]; +uint8_t potsPos[1]; +tmr10ms_t switchesMidposStart[2]; +uint64_t switchesPos = 0; +div_t switchInfo(int switchPosition) +{ + const div_t infos[] = { + { 0, 0 }, { 0, 1 }, { 0, 2 }, + { 1, 0 }, { 1, 1 }, + { 2, 0 }, { 2, 1 }, { 2, 2 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, + { 4, 0 }, { 4, 1 }, + { 5, 0 }, { 5, 1 }, { 5, 2 }, + }; + return infos[switchPosition-SWSRC_FIRST_SWITCH]; +} + +uint32_t check2PosSwitchPosition(EnumKeys sw) +{ + uint32_t result; + uint32_t index; + + if (switchState(sw)) + index = sw - SW_SA0; + else + index = sw - SW_SA0 + 1; + + result = ((uint32_t)1 << index); + + if (!(switchesPos & result)) { + PLAY_SWITCH_MOVED(index); + } + + return result; +} + +uint32_t check3PosSwitchPosition(int idx, EnumKeys sw, bool startup) +{ + uint32_t result; + uint32_t index; + + if (switchState(sw)) { + index = sw - SW_SA0; + result = (1 << index); + switchesMidposStart[idx] = 0; + } + else if (switchState(EnumKeys(sw+2))) { + index = sw - SW_SA0 + 2; + result = (1 << index); + switchesMidposStart[idx] = 0; + } + else if (startup || (switchesPos & (1 << (sw - SW_SA0 + 1))) || g_eeGeneral.switchesDelay==SWITCHES_DELAY_NONE || (switchesMidposStart[idx] && (tmr10ms_t)(get_tmr10ms() - switchesMidposStart[idx]) > SWITCHES_DELAY())) { + index = sw - SW_SA0 + 1; + result = (1 << index); + switchesMidposStart[idx] = 0; + } + else { + index = sw - SW_SA0 + 1; + if (!switchesMidposStart[idx]) { + switchesMidposStart[idx] = get_tmr10ms(); + } + result = (switchesPos & (0x7 << (sw - SW_SA0))); + } + + if (!(switchesPos & result)) { + PLAY_SWITCH_MOVED(index); + } + + return result; +} + +#define CHECK_2POS(sw) newPos |= check2PosSwitchPosition(sw ## 0) +#define CHECK_3POS(idx, sw) newPos |= check3PosSwitchPosition(idx, sw ## 0, startup) + +void getSwitchesPosition(bool startup) +{ + uint64_t newPos = 0; + CHECK_3POS(0, SW_SA); + CHECK_2POS(SW_SB); + CHECK_2POS(SW_SE); + CHECK_3POS(1, SW_SF); + switchesPos = newPos; + + int i = 0; + StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[POT1+i]; + if (calib->count>0 && calib->countcount); + uint8_t previousPos = potsPos[i] >> 4; + uint8_t previousStoredPos = potsPos[i] & 0x0F; + if (startup) { + potsPos[i] = (pos << 4) | pos; + } + else if (pos != previousPos) { + potsLastposStart[i] = get_tmr10ms(); + potsPos[i] = (pos << 4) | previousStoredPos; + } + else if (g_eeGeneral.switchesDelay==SWITCHES_DELAY_NONE || (tmr10ms_t)(get_tmr10ms() - potsLastposStart[i]) > SWITCHES_DELAY()) { + potsLastposStart[i] = 0; + potsPos[i] = (pos << 4) | pos; + if (previousStoredPos != pos) { + PLAY_SWITCH_MOVED(SWSRC_LAST_SWITCH+i*XPOTS_MULTIPOS_COUNT+pos); + } + } + } +} +#endif + #if defined(PCBTARANIS) #if defined(REV9E) tmr10ms_t switchesMidposStart[16]; @@ -200,6 +307,7 @@ void getSwitchesPosition(bool startup) } } + getvalue_t getValueForLogicalSwitch(mixsrc_t i) { getvalue_t result = getValue(i); @@ -462,7 +570,7 @@ bool getSwitch(swsrc_t swtch) result = true; } else if (cs_idx <= SWSRC_LAST_SWITCH) { -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) // TODO || defined(PCBFLAMENCO) if (flags & GETSWITCH_MIDPOS_DELAY) result = SWITCH_POSITION(cs_idx-SWSRC_FIRST_SWITCH); else @@ -486,7 +594,7 @@ bool getSwitch(swsrc_t swtch) } #endif } -#if defined(PCBTARANIS) +#if NUM_XPOTS > 0 else if (cs_idx <= SWSRC_LAST_MULTIPOS_SWITCH) { result = POT_POSITION(cs_idx-SWSRC_FIRST_MULTIPOS_SWITCH); } @@ -568,7 +676,28 @@ swsrc_t getMovedSwitch() static tmr10ms_t s_move_last_time = 0; swsrc_t result = 0; -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) +#if 0 + for (int i=0; i> (i*3); + uint8_t next = (1024+getValue(MIXSRC_SA+i)) / 1024; + if (prev != next) { + switches_states = (switches_states & (~mask)) | ((swarnstate_t)next << (i*3)); + if (i == 0) + result = 1+next; + else if (i == 1) + result = 1+(3*1)+(next!=0); + else if (i == 3) + result = 12+(next!=0); + else if (i == 4) + result = 14+next; + else + result = 0; + } + } +#endif +#elif defined(PCBTARANIS) for (int i=0; i 1)) { @@ -692,25 +817,44 @@ void checkSwitches() } // first - display warning -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) +#if defined(COLORLCD) + if ((last_bad_switches != switches_states) || (last_bad_pots != bad_pots)) { + int x = WARNING_LINE_X, y = WARNING_INFOLINE_Y-5; +#elif defined(PCBTARANIS) if ((last_bad_switches != switches_states) || (last_bad_pots != bad_pots)) { MESSAGE(STR_SWITCHWARN, NULL, STR_PRESSANYKEYTOSKIP, ((last_bad_switches == 0xff) || (last_bad_pots == 0xff)) ? AU_SWITCH_ALERT : AU_NONE); int x = 60, y = 4*FH+3; +#endif int numWarnings = 0; for (int i=0; i 5) { - lcd_putsAtt(x, y, "...", 0); - break; + if (++numWarnings < 6) { + putsSwitches(x, y, SWSRC_FIRST_SWITCH+i*3, attr); + x += 20; + } + else if (numWarnings == 6) { + lcd_putsAtt(x, y, "...", ALARM_COLOR); } - char c = "\300-\301"[(states & mask) >> (i*2)]; - putsMixerSource(x, y, MIXSRC_FIRST_SWITCH+i, attr); - lcd_putcAtt(lcdNextPos, y, c, attr); - x = lcdNextPos + 3; } +#else + LcdFlags attr = ((states & mask) == (switches_states & mask)) ? 0 : INVERS; + if (attr) { + if (++numWarnings < 7) { + char c = "\300-\301"[(states & mask) >> (i*2)]; + putsMixerSource(x, y, MIXSRC_FIRST_SWITCH+i, attr); + lcd_putcAtt(lcdNextPos, y, c, attr); + x = lcdNextPos + 3; + } + else if (numWarnings == 7) { + lcd_putsAtt(x, y, "...", 0); + } + } +#endif } } if (g_model.potsWarnMode) { @@ -719,17 +863,34 @@ void checkSwitches() x = 60; } for (int i=0; i 1) { +#if defined(COLORLCD) + char s[8]; + // TODO add an helper + strncpy(s, &STR_VSRCRAW[1+(NUM_STICKS+1+i)*STR_VSRCRAW[0]], STR_VSRCRAW[0]); + s[int(STR_VSRCRAW[0])] = '\0'; +#else lcd_putsiAtt(x, y, STR_VSRCRAW, NUM_STICKS+1+i, INVERS); if (IS_POT(POT1+i)) lcd_putcAtt(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? 126 : 127, INVERS); else lcd_putcAtt(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? '\300' : '\301', INVERS); +#endif +#if defined(COLORLCD) + if (++numWarnings < 6) { + lcd_putsAtt(x, y, s, ALARM_COLOR); + } + else if (numWarnings == 6) { + lcd_putsAtt(x, y, "...", ALARM_COLOR); + } + x += 20; +#else x = lcdNextPos + 3; +#endif } } @@ -761,30 +922,9 @@ void checkSwitches() last_bad_switches = 0xff; } #else + if (pwrCheck()==e_power_off || keyDown()) return; - if (keyDown()) { - return; - } - -#if defined(PCBTARANIS) && defined(REV9E) - uint32_t pwr_check = pwrCheck(); - if (pwr_check == e_power_off) { - return; - } - else if (pwr_check == e_power_press) { - refresh = true; - } - else if (pwr_check == e_power_on && refresh) { - last_bad_switches = 0xff; - refresh = false; - } -#else - if (pwrCheck() == e_power_off) { - return; - } -#endif - - checkBacklight(); + doLoopCommonActions(); wdt_reset(); diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 74213bdfa..f41d475c2 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -64,7 +64,7 @@ int g_snapshot_idx = 0; #if defined(CPUSTM32) uint32_t Peri1_frequency, Peri2_frequency; -GPIO_TypeDef gpioa, gpiob, gpioc, gpiod, gpioe, gpiof, gpiog; +GPIO_TypeDef gpioa, gpiob, gpioc, gpiod, gpioe, gpiof, gpiog, gpioh, gpioi, gpioj; TIM_TypeDef tim1, tim2, tim3, tim4, tim5, tim6, tim7, tim8, tim9, tim10; RCC_TypeDef rcc; DMA_Stream_TypeDef dma2_stream2, dma2_stream6; @@ -152,7 +152,13 @@ void simuSetKey(uint8_t key, bool state) switch (key) { KEY_CASE(KEY_MENU, KEYS_GPIO_REG_MENU, KEYS_GPIO_PIN_MENU) KEY_CASE(KEY_EXIT, KEYS_GPIO_REG_EXIT, KEYS_GPIO_PIN_EXIT) -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + KEY_CASE(KEY_ENTER, KEYS_GPIO_REG_ENTER, KEYS_GPIO_PIN_ENTER) + KEY_CASE(KEY_RIGHT, KEYS_GPIO_REG_RIGHT, KEYS_GPIO_PIN_RIGHT) + KEY_CASE(KEY_LEFT, KEYS_GPIO_REG_LEFT, KEYS_GPIO_PIN_LEFT) + KEY_CASE(KEY_UP, KEYS_GPIO_REG_UP, KEYS_GPIO_PIN_UP) + KEY_CASE(KEY_DOWN, KEYS_GPIO_REG_DOWN, KEYS_GPIO_PIN_DOWN) +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) KEY_CASE(KEY_ENTER, KEYS_GPIO_REG_ENTER, KEYS_GPIO_PIN_ENTER) KEY_CASE(KEY_PAGE, KEYS_GPIO_REG_PAGE, KEYS_GPIO_PIN_PAGE) KEY_CASE(KEY_MINUS, KEYS_GPIO_REG_MINUS, KEYS_GPIO_PIN_MINUS) @@ -186,6 +192,12 @@ void simuSetTrim(uint8_t trim, bool state) TRIM_CASE(5, TRIMS_GPIO_REG_RVU, TRIMS_GPIO_PIN_RVU) TRIM_CASE(6, TRIMS_GPIO_REG_RHL, TRIMS_GPIO_PIN_RHL) TRIM_CASE(7, TRIMS_GPIO_REG_RHR, TRIMS_GPIO_PIN_RHR) +#if defined(HORUS) + TRIM_CASE(8, TRIMS_GPIO_REG_LSU, TRIMS_GPIO_PIN_LSU) + TRIM_CASE(9, TRIMS_GPIO_REG_LSD, TRIMS_GPIO_PIN_RVU) + TRIM_CASE(10, TRIMS_GPIO_REG_RSU, TRIMS_GPIO_PIN_RHL) + TRIM_CASE(11, TRIMS_GPIO_REG_RSD, TRIMS_GPIO_PIN_RHR) +#endif } } @@ -194,7 +206,14 @@ void simuSetSwitch(uint8_t swtch, int8_t state) { // TRACE("simuSetSwitch(%d, %d)", swtch, state); switch (swtch) { -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBFLAMENCO) + // SWITCH_3_CASE(0, SWITCHES_GPIO_REG_A_L, SWITCHES_GPIO_REG_A_H, SWITCHES_GPIO_PIN_A_L, SWITCHES_GPIO_PIN_A_H) + // SWITCH_CASE(1, SWITCHES_GPIO_REG_B, SWITCHES_GPIO_PIN_B) + // SWITCH_3_CASE(2, SWITCHES_GPIO_REG_C_L, SWITCHES_GPIO_REG_C_H, SWITCHES_GPIO_PIN_C_L, SWITCHES_GPIO_PIN_C_H) + // SWITCH_3_CASE(3, SWITCHES_GPIO_REG_D_L, SWITCHES_GPIO_REG_D_H, SWITCHES_GPIO_PIN_D_L, SWITCHES_GPIO_PIN_D_H) + // SWITCH_CASE(4, SWITCHES_GPIO_REG_E, SWITCHES_GPIO_PIN_E) + // SWITCH_3_CASE(5, SWITCHES_GPIO_REG_F_H, SWITCHES_GPIO_REG_F_L, SWITCHES_GPIO_PIN_F_H, SWITCHES_GPIO_PIN_F_L) +#elif defined(PCBTARANIS) && defined(REV9E) SWITCH_3_CASE(0, SWITCHES_GPIO_REG_A_L, SWITCHES_GPIO_REG_A_H, SWITCHES_GPIO_PIN_A_L, SWITCHES_GPIO_PIN_A_H) SWITCH_3_CASE(1, SWITCHES_GPIO_REG_B_L, SWITCHES_GPIO_REG_B_H, SWITCHES_GPIO_PIN_B_L, SWITCHES_GPIO_PIN_B_H) SWITCH_3_CASE(2, SWITCHES_GPIO_REG_C_L, SWITCHES_GPIO_REG_C_H, SWITCHES_GPIO_PIN_C_L, SWITCHES_GPIO_PIN_C_H) @@ -213,7 +232,7 @@ void simuSetSwitch(uint8_t swtch, int8_t state) SWITCH_3_CASE(15, SWITCHES_GPIO_REG_P_L, SWITCHES_GPIO_REG_P_H, SWITCHES_GPIO_PIN_P_L, SWITCHES_GPIO_PIN_P_H) SWITCH_3_CASE(16, SWITCHES_GPIO_REG_Q_L, SWITCHES_GPIO_REG_Q_H, SWITCHES_GPIO_PIN_Q_L, SWITCHES_GPIO_PIN_Q_H) SWITCH_3_CASE(17, SWITCHES_GPIO_REG_R_L, SWITCHES_GPIO_REG_R_H, SWITCHES_GPIO_PIN_R_L, SWITCHES_GPIO_PIN_R_H) -#elif defined(PCBTARANIS) +#elif defined(PCBTARANIS) || defined(PCBHORUS) SWITCH_3_CASE(0, SWITCHES_GPIO_REG_A_L, SWITCHES_GPIO_REG_A_H, SWITCHES_GPIO_PIN_A_L, SWITCHES_GPIO_PIN_A_H) SWITCH_3_CASE(1, SWITCHES_GPIO_REG_B_L, SWITCHES_GPIO_REG_B_H, SWITCHES_GPIO_PIN_B_L, SWITCHES_GPIO_PIN_B_H) SWITCH_3_CASE(2, SWITCHES_GPIO_REG_C_L, SWITCHES_GPIO_REG_C_H, SWITCHES_GPIO_PIN_C_L, SWITCHES_GPIO_PIN_C_H) @@ -271,7 +290,7 @@ uint16_t getTmr16KHz() return get_tmr10ms() * 160; } -#if !defined(PCBTARANIS) +#if !defined(PCBTARANIS) && !defined(PCBHORUS) bool eeprom_thread_running = true; void *eeprom_write_function(void *) { @@ -329,6 +348,10 @@ void *main_thread(void *) try { #endif +#if defined(COLORLCD) + lcdColorsInit(); +#endif + #if defined(CPUARM) stackPaint(); #endif @@ -339,7 +362,9 @@ void *main_thread(void *) g_menuStack[0] = menuMainView; g_menuStack[1] = menuModelSelect; +#if defined(EEPROM) eeReadAll(); // load general setup and selected model +#endif #if defined(SIMU_DISKIO) f_mount(&g_FATFS_Obj, "", 1); @@ -364,6 +389,10 @@ void *main_thread(void *) s_current_protocol[0] = 0; +#if defined(PCBFLAMENCO) + menuEntryTime = get_tmr10ms() - 200; +#endif + while (main_thread_running) { #if defined(CPUARM) doMixerCalculations(); @@ -603,7 +632,7 @@ void StartEepromThread(const char *filename) sem_init(eeprom_write_sem, 0, 0); #endif -#if !defined(PCBTARANIS) +#if !defined(PCBTARANIS) && !defined(PCBHORUS) eeprom_thread_running = true; assert(!pthread_create(&eeprom_thread_pid, NULL, &eeprom_write_function, NULL)); #endif @@ -611,7 +640,7 @@ void StartEepromThread(const char *filename) void StopEepromThread() { -#if !defined(PCBTARANIS) +#if !defined(PCBTARANIS) && !defined(PCBFLAMENCO) && !defined(PCBHORUS) eeprom_thread_running = false; sem_post(eeprom_write_sem); pthread_join(eeprom_thread_pid, NULL); @@ -640,7 +669,7 @@ void eepromReadBlock (uint8_t * pointer_ram, uint32_t pointer_eeprom, uint32_t s } } -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) void eepromWriteBlock(uint8_t * pointer_ram, uint32_t pointer_eeprom, uint32_t size) { assert(size); @@ -1198,9 +1227,11 @@ uint32_t sdGetSpeed() bool lcd_refresh = true; display_t lcd_buf[DISPLAY_BUF_SIZE]; +#if !defined(PCBFLAMENCO) && !defined(PCBHORUS) void lcdSetRefVolt(uint8_t val) { } +#endif void adcPrepareBandgap() { @@ -1214,11 +1245,15 @@ void lcdOff() void lcdRefresh() { +#if defined(PCBFLAMENCO) + TW8823_SendScreen(); +#endif + memcpy(lcd_buf, displayBuf, sizeof(lcd_buf)); lcd_refresh = true; } -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) void pwrInit() { } void pwrOff() { } #if defined(REV9E) @@ -1275,3 +1310,8 @@ void turnBacklightOff(void) #endif // #if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) +void i2cWriteTW8823(unsigned char, unsigned char) { } +uint8_t i2cReadBQ24195(uint8_t) { return 0; } +void i2cWriteBQ24195(uint8_t, uint8_t) { } +#endif diff --git a/radio/src/targets/simu/simpgmspace.h b/radio/src/targets/simu/simpgmspace.h index 0b7523494..936ff29cd 100644 --- a/radio/src/targets/simu/simpgmspace.h +++ b/radio/src/targets/simu/simpgmspace.h @@ -101,7 +101,7 @@ typedef const int16_t pm_int16_t; typedef const int8_t pm_int8_t; #if defined(CPUSTM32) -extern GPIO_TypeDef gpioa, gpiob, gpioc, gpiod, gpioe, gpiof, gpiog; +extern GPIO_TypeDef gpioa, gpiob, gpioc, gpiod, gpioe, gpiof, gpiog, gpioh, gpioi, gpioj; extern TIM_TypeDef tim1, tim2, tim3, tim4, tim5, tim6, tim7, tim8, tim9, tim10; extern USART_TypeDef Usart0, Usart1, Usart2, Usart3, Usart4; extern RCC_TypeDef rcc; @@ -114,6 +114,9 @@ extern DMA_TypeDef dma2; #undef GPIOE #undef GPIOF #undef GPIOG +#undef GPIOH +#undef GPIOI +#undef GPIOJ #define GPIOA (&gpioa) #define GPIOB (&gpiob) #define GPIOC (&gpioc) @@ -121,6 +124,9 @@ extern DMA_TypeDef dma2; #define GPIOE (&gpioe) #define GPIOF (&gpiof) #define GPIOG (&gpiog) +#define GPIOH (&gpioh) +#define GPIOI (&gpioi) +#define GPIOJ (&gpioj) #undef TIM1 #undef TIM2 #undef TIM3 @@ -392,23 +398,24 @@ extern OS_MutexID audioMutex; #define CoInitOS(...) #define CoStartOS(...) -#define CoCreateTask(...) (0) -#define CoCreateMutex(...) PTHREAD_MUTEX_INITIALIZER +#define CoCreateTask(...) 0 +#define CoCreateTaskEx(...) 0 +#define CoCreateMutex(...) PTHREAD_MUTEX_INITIALIZER #define CoSetFlag(...) #define CoClearFlag(...) #define CoSetTmrCnt(...) #define CoEnterISR(...) #define CoExitISR(...) #define CoStartTmr(...) -#define CoWaitForSingleFlag(...) 0 -#define CoEnterMutexSection(m) pthread_mutex_lock(&(m)) -#define CoLeaveMutexSection(m) pthread_mutex_unlock(&(m)) +#define CoWaitForSingleFlag(...) 0 +#define CoEnterMutexSection(m) pthread_mutex_lock(&(m)) +#define CoLeaveMutexSection(m) pthread_mutex_unlock(&(m)) #define CoTickDelay(...) -#define CoCreateFlag(...) 0 -#define CoGetOSTime(...) 0 +#define CoCreateFlag(...) 0 +#define CoGetOSTime(...) 0 #define UART_Stop(...) #define UART3_Stop(...) -#define USART_GetITStatus(...) 0 +#define USART_GetITStatus(...) 0 #if defined(CPUSTM32) inline void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) { } diff --git a/radio/src/tasks_arm.cpp b/radio/src/tasks_arm.cpp index 7b990e387..26442ae0b 100644 --- a/radio/src/tasks_arm.cpp +++ b/radio/src/tasks_arm.cpp @@ -97,6 +97,9 @@ uint16_t getStackAvailable(void * address, uint16_t size) i++; } return i*4; +#if defined(CLI) + cliStackPaint(); +#endif } template diff --git a/radio/src/telemetry/frsky.cpp b/radio/src/telemetry/frsky.cpp index 9dffd4b23..daa122f72 100644 --- a/radio/src/telemetry/frsky.cpp +++ b/radio/src/telemetry/frsky.cpp @@ -47,7 +47,7 @@ uint8_t link_counter = 0; #define FRSKY_RX_PACKET_SIZE 19 uint8_t frskyRxBuffer[FRSKY_RX_PACKET_SIZE]; // Receive buffer. 9 bytes (full packet), worst case 18 bytes with byte-stuffing (+1) -#if !defined(CPUARM) +#if !defined(CPUARM) && !defined(PCBFLAMENCO) uint8_t frskyTxBuffer[FRSKY_TX_PACKET_SIZE]; #endif @@ -59,7 +59,7 @@ uint8_t frskyTxBufferCount = 0; uint8_t telemetryState = TELEMETRY_INIT; #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) Fifo<512> telemetryFifo; // TODO should be in the driver #endif @@ -323,7 +323,7 @@ void telemetryWakeup() } #endif -#if !defined(CPUARM) +#if !defined(CPUARM) && !defined(PCBFLAMENCO) if (IS_FRSKY_D_PROTOCOL()) { // Attempt to transmit any waiting Fr-Sky alarm set packets every 50ms (subject to packet buffer availability) static uint8_t frskyTxDelay = 5; diff --git a/radio/src/telemetry/frsky.h b/radio/src/telemetry/frsky.h index ebb9b14f7..07c9a5c2b 100644 --- a/radio/src/telemetry/frsky.h +++ b/radio/src/telemetry/frsky.h @@ -491,7 +491,7 @@ enum FrSkyDataState { #endif }; -#if defined(CPUARM) +#if defined(CPUARM) || defined(PCBFLAMENCO) #define frskySendAlarms() #else #define SEND_RSSI_ALARMS 6 diff --git a/radio/src/thirdparty/CoOS/OsConfig.h b/radio/src/thirdparty/CoOS/OsConfig.h index 7c1876d37..ffc41ca1a 100644 --- a/radio/src/thirdparty/CoOS/OsConfig.h +++ b/radio/src/thirdparty/CoOS/OsConfig.h @@ -69,9 +69,9 @@ Idle task stack size(word). /*!< System frequency (Hz). */ -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBFLAMENCO) || (defined(PCBTARANIS) && defined(REV9E)) #define CFG_CPU_FREQ (168000000) -#elif defined(PCBTARANIS) +#elif defined(PCBTARANIS) || defined(PCBHORUS) #define CFG_CPU_FREQ (120000000) #else #define CFG_CPU_FREQ (36000000) // TODO check if really correct for sky9x? diff --git a/radio/src/thirdparty/GCS_MAVLink/message_definitions_v1.0/matrixpilot.xml b/radio/src/thirdparty/GCS_MAVLink/message_definitions_v1.0/matrixpilot.xml index 29d368d2a..240c0ed01 100644 --- a/radio/src/thirdparty/GCS_MAVLink/message_definitions_v1.0/matrixpilot.xml +++ b/radio/src/thirdparty/GCS_MAVLink/message_definitions_v1.0/matrixpilot.xml @@ -178,7 +178,7 @@ Serial UDB Extra Current Waypoint Goal X Serial UDB Extra Current Waypoint Goal Y Serial UDB Extra Current Waypoint Goal Z - Serial UDB Extra Stack Memory Free + Serial UDB Extra Stack Memory Free Backwards compatible version of SERIAL_UDB_EXTRA F4: format diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h index eeb90532b..0a0e6f884 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h @@ -30,9 +30,9 @@ #ifndef __USB_AUDIO_CORE_H_ #define __USB_AUDIO_CORE_H_ +#include "../../../../../../../targets/Flamenco/usbd_desc.h" #include "usbd_ioreq.h" #include "usbd_req.h" -#include "usbd_desc.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h index 1b0c3f048..f3f1af32f 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h @@ -30,8 +30,8 @@ #define __USBD_CDC_IF_TEMPLATE_H /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" -#include "usbd_conf.h" +#include "../../../../../../../targets/Flamenco/usb_conf.h" +#include "../../../../../../../targets/Flamenco/usbd_conf.h" #include "usbd_cdc_core.h" /* Exported types ------------------------------------------------------------*/ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/src/usbd_cdc_core.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/src/usbd_cdc_core.c index 3856f778d..4cce796e7 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/src/usbd_cdc_core.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/src/usbd_cdc_core.c @@ -64,7 +64,8 @@ */ /* Includes ------------------------------------------------------------------*/ -#include "usbd_cdc_core.h" +#include "../inc/usbd_cdc_core.h" + #include "usbd_desc.h" #include "usbd_req.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h index bf6aae491..740d3b22e 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h @@ -30,8 +30,8 @@ #define __DFU_MAL_H /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" - +#include "../../../../../../../targets/Flamenco/usb_conf.h" +#include "../../../../../../../targets/Flamenco/usbd_conf.h" #include "usbd_conf.h" #include "usbd_dfu_core.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h index eaae60359..777649e0a 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h @@ -30,8 +30,7 @@ #define __MEM_IF_MAL_H /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" - +#include "../../../../../../../targets/Flamenco/usb_conf.h" #include "usbd_dfu_mal.h" /* Exported types ------------------------------------------------------------*/ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c index 5f2b3b360..56ef1e662 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c @@ -63,7 +63,8 @@ /* Includes ------------------------------------------------------------------*/ #include "usbd_dfu_core.h" -#include "usbd_desc.h" + +#include "../../../../../../../targets/Flamenco/usbd_desc.h" #include "usbd_req.h" #include "usb_bsp.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c index 7cdc52349..99eb14a05 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c @@ -48,7 +48,8 @@ /* Includes ------------------------------------------------------------------*/ #include "usbd_hid_core.h" -#include "../../../../../usbd_desc.h" + +#include "../../../../../../../targets/Flamenco/usbd_desc.h" #include "usbd_req.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h index c8f3a2adf..ef440fbc4 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h @@ -31,7 +31,7 @@ #define _USBD_MSC_DATA_H_ /* Includes ------------------------------------------------------------------*/ -#include "usbd_conf.h" +#include "../../../../../../../targets/Flamenco/usbd_conf.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h index b1a20f947..ab10fa7a1 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h @@ -30,7 +30,7 @@ #define __USBD_CONF__H__ /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" +#include "../../../../../../targets/Flamenco/usb_conf.h" /** @defgroup USB_CONF_Exported_Defines * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h index c01964749..030e35c38 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h @@ -30,9 +30,9 @@ #define __USBD_CORE_H /* Includes ------------------------------------------------------------------*/ +#include "../../../../../../targets/Flamenco/usbd_conf.h" #include "../../../STM32_USB_OTG_Driver/inc/usb_dcd.h" #include "usbd_def.h" -#include "usbd_conf.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h index f88416df3..467f5f822 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h @@ -31,9 +31,9 @@ #define __USB_REQUEST_H_ /* Includes ------------------------------------------------------------------*/ +#include "../../../../../../targets/Flamenco/usbd_conf.h" #include "usbd_def.h" #include "usbd_core.h" -#include "usbd_conf.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c index 2659732e6..8a5f8390d 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c @@ -27,8 +27,9 @@ /* Includes ------------------------------------------------------------------*/ #include "usbd_req.h" + +#include "../../../../../../targets/Flamenco/usbd_desc.h" #include "usbd_ioreq.h" -#include "usbd_desc.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h index 2e415e126..5de0d5143 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h @@ -30,7 +30,7 @@ #define __USB_CONF__H__ /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" +#include "../../../../../targets/Flamenco/usb_conf.h" /** @addtogroup USB_OTG_DRIVER * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h index 8e0d264f4..ee651a992 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h @@ -30,7 +30,7 @@ #define __USB_DEF_H__ /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" +#include "../../../../../targets/Flamenco/usb_conf.h" /** @addtogroup USB_OTG_DRIVER * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h index 323e87056..cc490c0ed 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h @@ -30,7 +30,7 @@ #define __USB_OTG_REGS_H__ /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" +#include "../../../../../targets/Flamenco/usb_conf.h" /** @addtogroup USB_OTG_DRIVER diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c index 56e224418..5ba595dfd 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c @@ -28,7 +28,8 @@ /* Includes ------------------------------------------------------------------*/ #include "usb_core.h" #include "usb_hcd.h" -#include "usb_conf.h" + +#include "../../../../../targets/Flamenco/usb_conf.h" #include "usb_bsp.h" diff --git a/radio/src/translations.cpp b/radio/src/translations.cpp index 524d87a55..b02c249ad 100644 --- a/radio/src/translations.cpp +++ b/radio/src/translations.cpp @@ -581,7 +581,7 @@ const pm_char STR_BLCOLOR[] PROGMEM = TR_BLCOLOR; const pm_char STR_CONFIRMDELETE[] PROGMEM = TR_CONFIRMDELETE; #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) const pm_char STR_BYTES[] PROGMEM = TR_BYTES; const pm_char STR_ANTENNAPROBLEM[] PROGMEM = TR_ANTENNAPROBLEM; const pm_char STR_MODULE[] PROGMEM = TR_MODULE; diff --git a/radio/src/translations.h b/radio/src/translations.h index c533a504d..14b28d745 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -800,7 +800,7 @@ extern const pm_char STR_BLCOLOR[]; extern const pm_char STR_CONFIRMDELETE[]; #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) extern const pm_char STR_BYTES[]; extern const pm_char STR_ANTENNAPROBLEM[]; extern const pm_char STR_MODULE[]; diff --git a/radio/src/translations/en.h.txt b/radio/src/translations/en.h.txt index 4936d8d44..a7084a450 100644 --- a/radio/src/translations/en.h.txt +++ b/radio/src/translations/en.h.txt @@ -129,7 +129,9 @@ #define LEN_RETA123 "\001" -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBFLAMENCO) + #define TR_RETA123 "RETA123LR" +#elif defined(PCBTARANIS) && defined(REV9E) #define TR_RETA123 "RETA1234LRLR" #elif defined(PCBTARANIS) || defined(REVX) #define TR_RETA123 "RETA123LR" @@ -443,8 +445,16 @@ #define LEN_VSWASHTYPE "\004" #define TR_VSWASHTYPE "---\0""120\0""120X""140\0""90\0" +#if defined(PCBHORUS) +#define LEN_VKEYS "\006" +#define TR_VKEYS "Menu\0 ""Exit\0 ""Enter\0""Up\0 ""Down\0 ""Right\0""Left" +#elif defined(PCBTARANIS) #define LEN_VKEYS "\005" -#define TR_VKEYS TR("Menu\0""Exit\0""Down\0""Up\0 ""Right""Left\0", "Menu\0""Exit\0""Enter""Page\0""Plus\0""Minus") +#define TR_VKEYS "Menu\0""Exit\0""Enter""Page\0""Plus\0""Minus" +#else +#define LEN_VKEYS "\005" +#define TR_VKEYS "Menu\0""Exit\0""Down\0""Up\0 ""Right""Left\0" +#endif #define LEN_VRENCODERS "\003" #define TR_VRENCODERS "REa""REb" @@ -455,7 +465,10 @@ #define TR_STICKS_VSRCRAW TR("Rud\0""Ele\0""Thr\0""Ail\0", "\307Rud""\307Ele""\307Thr""\307Ail") #define TR_TRIMS_VSRCRAW TR("TrmR""TrmE""TrmT""TrmA", "\313Rud""\313Ele""\313Thr""\313Ail") -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBFLAMENCO) + #define TR_POTS_VSRCRAW "SD\0 ""LS\0 ""RS\0 " + #define TR_SW_VSRCRAW "SA\0 ""SB\0 ""SC\0 ""SE\0 ""SF\0 " +#elif defined(PCBTARANIS) && defined(REV9E) #define TR_POTS_VSRCRAW "\310F1\0""\310F2\0""\310F3\0""\310F4\0""\311S1\0""\311S2\0""\311LS\0""\311RS\0" #define TR_SW_VSRCRAW "\312SA\0""\312SB\0""\312SC\0""\312SD\0""\312SE\0""\312SF\0""\312SG\0""\312SH\0""\312SI\0""\312SJ\0""\312SK\0""\312SL\0""\312SM\0""\312SN\0""\312SO\0""\312SP\0""\312SQ\0""\312SR\0" #elif defined(PCBTARANIS) @@ -486,13 +499,17 @@ #define TR_ROTENC_SWITCHES #endif -#if !defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + #define TR_PHYS_SWITCHES "SA\300""SA-""SA\301""SB\300""SB\301""SC1""SC2""SC3""SC4""SC5""SC6""SE\300""SE\301""SF\300""SF-""SF\301" +#elif !defined(PCBTARANIS) #define TR_PHYS_SWITCHES "THR""RUD""ELE""AIL""GEA""TRN" #endif #define TR_ON_ONE_SWITCHES "ON\0""One" -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + #define TR_VSWITCHES "---" TR_PHYS_SWITCHES TR_TRIMS_SWITCHES TR_ROTENC_SWITCHES TR_LOGICALSW "ON\0""One" +#elif defined(PCBTARANIS) // only special switches here #define TR_VSWITCHES "---" TR_TRIMS_SWITCHES TR_ON_ONE_SWITCHES #else @@ -546,8 +563,8 @@ // ZERO TERMINATED STRINGS #if defined(COLORLCD) - #define INDENT "\007" - #define LEN_INDENT 1 + #define INDENT " " + #define LEN_INDENT 3 #define INDENT_WIDTH 12 #define BREAKSPACE "\036" #else @@ -557,7 +574,7 @@ #define BREAKSPACE " " #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBFLAMENCO) #define TR_ENTER "[ENTER]" #else #define TR_ENTER "[MENU]" @@ -565,7 +582,9 @@ #define TR_EXIT "[EXIT]" -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + #define TR_POPUPS TR_EXIT "\010" TR_ENTER +#elif defined(PCBTARANIS) #define TR_POPUPS TR_EXIT "\010" "\010" "\010" "\010" TR_ENTER #else #define TR_POPUPS TR_ENTER "\010" TR_EXIT @@ -1031,7 +1050,11 @@ #define TR_ABOUT_MARTIN_1 "Martin Hotar" #define TR_ABOUT_MARTIN_2 "Graphics designer" -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + #define TR_ABOUT_HARDWARE_1 "XXX" + #define TR_ABOUT_HARDWARE_2 "Hardware designer/producer" + #define TR_ABOUT_HARDWARE_3 "" +#elif defined(PCBTARANIS) || defined(PCBHORUS) #define TR_ABOUT_HARDWARE_1 "FrSky" #define TR_ABOUT_HARDWARE_2 "Hardware designer/producer" #define TR_ABOUT_HARDWARE_3 "Firmware contributor" From c92909f7c5791dadf83107848aa907932137cf45 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 8 Oct 2015 18:23:34 +0200 Subject: [PATCH 11/74] A little bit code cleaning... --- .../firmwares/opentx/simulator/CMakeLists.txt | 2 +- radio/src/gui/Horus/lcd.cpp | 102 ++++-------------- radio/src/gui/Horus/lcd.h | 11 +- radio/src/gui/Horus/view_main.cpp | 2 +- radio/src/gui/Horus/widgets.cpp | 4 +- radio/src/pulses/ppm_arm.cpp | 2 +- radio/src/pulses/pulses_arm.h | 2 +- radio/src/targets/Horus/board_horus.h | 4 +- radio/src/targets/Horus/lcd_driver.cpp | 7 +- radio/src/tasks_arm.cpp | 3 +- .../Core/inc/usbd_core.h | 2 +- .../Core/inc/usbd_req.h | 2 +- .../Core/src/usbd_req.c | 2 +- .../STM32_USB_OTG_Driver/inc/usb_defines.h | 2 +- .../STM32_USB_OTG_Driver/inc/usb_regs.h | 2 +- 15 files changed, 42 insertions(+), 107 deletions(-) diff --git a/companion/src/firmwares/opentx/simulator/CMakeLists.txt b/companion/src/firmwares/opentx/simulator/CMakeLists.txt index 062a8f4cd..63531c07d 100644 --- a/companion/src/firmwares/opentx/simulator/CMakeLists.txt +++ b/companion/src/firmwares/opentx/simulator/CMakeLists.txt @@ -21,7 +21,7 @@ add_custom_target(commondeps set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${RADIO_SRC_DIRECTORY}/stamp-opentx.h) add_custom_target(lua_exports - make PCB=TARANIS lua_exports.inc + make PCB=TARANIS lua_exports_taranis.inc WORKING_DIRECTORY ${RADIO_SRC_DIRECTORY} ) diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index c65c059a6..b982f1d84 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -116,80 +116,14 @@ void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags fl #define FONT_MAX_HEIGHT 42 void lcdPutFontPattern(coord_t x, coord_t y, const uint8_t * font, const uint16_t * spec, int index, LcdFlags flags) { - bool blink = false; - bool inv = false; - if (flags & BLINK) { - if (BLINK_ON_PHASE) { - if (flags & INVERS) - inv = true; - else { - blink = true; - } - } - } - else if (flags & INVERS) { - inv = true; - } - - coord_t w = *((uint16_t *)font); - coord_t width = getFontPatternWidth(spec, index); - coord_t height = *(((uint16_t *)font)+1); - - assert(height <= FONT_MAX_HEIGHT); - - for (int i=0; i> 8; const pm_uchar * font = fontsTable[fontindex]; const uint16_t * fontspecs = fontspecsTable[fontindex]; @@ -216,9 +150,9 @@ int getFontHeight(LcdFlags flags) return heightTable[FONTSIZE(flags) >> 8]; } -int getTextWidth(const pm_char *s, int len, LcdFlags flags) +int getTextWidth(const pm_char * s, int len, LcdFlags flags) { - const uint16_t *specs = fontspecsTable[FONTSIZE(flags) >> 8]; + const uint16_t * specs = fontspecsTable[FONTSIZE(flags) >> 8]; int result = 0; for (int i=0; (len==0 || i> 8; + const pm_uchar * font = fontsTable[fontindex]; + const uint16_t * fontspecs = fontspecsTable[fontindex]; - if ((flags&INVERS) && ((~flags & BLINK) || BLINK_ON_PHASE)) { - if ((COLOR_IDX(flags) == TEXT_COLOR_INDEX)) { - flags += TEXT_INVERTED_COLOR - TEXT_COLOR; - if (FONTSIZE(flags) == TINSIZE) - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); - else if (FONTSIZE(flags) == SMLSIZE) - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); - else - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); - } - flags &= ~(INVERS|BLINK); + if ((flags&INVERS) && ((~flags & BLINK) || BLINK_ON_PHASE) && (COLOR_IDX(flags) == TEXT_COLOR_INDEX)) { + flags += TEXT_INVERTED_COLOR - TEXT_COLOR; + if (FONTSIZE(flags) == TINSIZE) + lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); + else if (FONTSIZE(flags) == SMLSIZE) + lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + else + lcdDrawFilledRect(x-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } char str[256]; @@ -272,7 +206,7 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags break; } else if (c >= 0x20) { - lcd_putcAtt(x, y, c, flags); + lcdPutFontPattern(x, y, font, fontspecs, getMappedChar(c), flags); x = lcdNextPos; } else if (c == 0x1F) { // X-coord prefix diff --git a/radio/src/gui/Horus/lcd.h b/radio/src/gui/Horus/lcd.h index 9fe05c00b..71b833bb9 100644 --- a/radio/src/gui/Horus/lcd.h +++ b/radio/src/gui/Horus/lcd.h @@ -238,7 +238,7 @@ void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, #if !defined(SIMU) inline void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) { - lcdFillRectangle(x, y, w, h, lcdColorTable[COLOR_IDX(att)]); + lcdDrawFilledRectDMA(x, y, w, h, lcdColorTable[COLOR_IDX(att)]); } #else void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att); @@ -266,9 +266,10 @@ void lcdDrawFilledRectWithAttributes(coord_t x, scoord_t y, coord_t w, coord_t h void drawBlackOverlay(); void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); -inline void lcd_square(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { lcd_rect(x, y, w, w, SOLID, att); } - -void lcdDrawTelemetryTopBar(); +inline void lcdDrawSquare(coord_t x, coord_t y, coord_t w, LcdFlags att=0) +{ + lcdDrawRect(x, y, w, w, att); +} #define V_BAR(xx, yy, ll) \ lcd_vline(xx-1,yy-ll,ll); \ @@ -283,10 +284,8 @@ void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags fl #define lcdSetRefVolt(...) void lcdSetContrast(); -void lcdInit(); #define lcdOff(...) -void lcdRefresh(); void lcdSendByte(uint8_t data); void lcdSend(const uint8_t * data, uint32_t size); void lcdSendString(const char * s); diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index 6d75f4aae..411b5b777 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -138,7 +138,7 @@ void displayTrims(uint8_t phase) lcd_vline(xm, ym-1, 3); } } - lcd_square(xm-3, ym-3, 7, att); + lcdDrawSquare(xm-3, ym-3, 7, att); } } diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index de9846e35..bda3da69e 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -53,10 +53,10 @@ void drawStick(coord_t centrex, int16_t xval, int16_t yval) #define BOX_CENTERY (170 - FH - BOX_WIDTH/2) #define MARKER_WIDTH 5 - lcd_square(centrex-BOX_WIDTH/2, BOX_CENTERY-BOX_WIDTH/2, BOX_WIDTH, TEXT_COLOR); + lcdDrawSquare(centrex-BOX_WIDTH/2, BOX_CENTERY-BOX_WIDTH/2, BOX_WIDTH, TEXT_COLOR); lcd_vlineStip(centrex, BOX_CENTERY-1, 3, SOLID, TEXT_COLOR); lcd_hlineStip(centrex-1, BOX_CENTERY, 3, SOLID, TEXT_COLOR); - lcd_square(centrex + (xval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, BOX_CENTERY - (yval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, MARKER_WIDTH, ROUND|TEXT_COLOR); + lcdDrawSquare(centrex + (xval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, BOX_CENTERY - (yval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, MARKER_WIDTH, ROUND|TEXT_COLOR); #undef BOX_CENTERY #undef MARKER_WIDTH diff --git a/radio/src/pulses/ppm_arm.cpp b/radio/src/pulses/ppm_arm.cpp index edd21c5fb..44dc5cb04 100644 --- a/radio/src/pulses/ppm_arm.cpp +++ b/radio/src/pulses/ppm_arm.cpp @@ -61,7 +61,7 @@ void setupPulsesPPM(unsigned int port) // Don't enable interru PpmPulsesData * ppmPulsesData = (port == TRAINER_MODULE ? &trainerPulsesData.ppm : &modulePulsesData[port].ppm); uint16_t * ptr = ppmPulsesData->pulses; -#if defined(PCBSKY9X) +#if defined(PPM_PIN_HW_SERIAL) ppmPulsesData->index = 0; #else ppmPulsesData->ptr = ptr; diff --git a/radio/src/pulses/pulses_arm.h b/radio/src/pulses/pulses_arm.h index 9c9927a1a..118b45db2 100644 --- a/radio/src/pulses/pulses_arm.h +++ b/radio/src/pulses/pulses_arm.h @@ -47,7 +47,7 @@ extern uint8_t s_current_protocol[NUM_MODULES]; extern uint8_t s_pulses_paused; extern uint16_t failsafeCounter[NUM_MODULES]; -#if defined(PCBSKY9X) +#if defined(PPM_PIN_HW_SERIAL) PACK(struct PpmPulsesData { uint16_t pulses[20]; uint32_t index; diff --git a/radio/src/targets/Horus/board_horus.h b/radio/src/targets/Horus/board_horus.h index 45f1438d2..caf84cdb4 100644 --- a/radio/src/targets/Horus/board_horus.h +++ b/radio/src/targets/Horus/board_horus.h @@ -250,7 +250,9 @@ void ledRed(void); void ledBlue(void); // LCD driver -void lcdDrawFullRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); +void lcdInit(void); +void lcdRefresh(void); +void lcdDrawFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); // Backlight driver #define setBacklight(xx) diff --git a/radio/src/targets/Horus/lcd_driver.cpp b/radio/src/targets/Horus/lcd_driver.cpp index 536eeaf5d..5af5c9d9e 100644 --- a/radio/src/targets/Horus/lcd_driver.cpp +++ b/radio/src/targets/Horus/lcd_driver.cpp @@ -18,7 +18,7 @@ #define LCD_BACKGROUND_LAYER 0x0000 #define LCD_FOREGROUND_LAYER 0x0001 -#define LCD_BKLIGHT_PWM_FREQ 300 +#define LCD_BKLIGHT_PWM_FREQ 300 #define LCD_DEFAULT_FRAME_BUFFER SDRAM_BANK_ADDR #define FRAME_BUFFER_OFFSET ((uint32_t)0x100000) @@ -439,7 +439,7 @@ void lcdInit(void) LCD_SetTransparency(255); } -void lcdDrawFullRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) +void lcdDrawFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { uint32_t addr = CurrentFrameBuffer + 2*(LCD_PIXEL_WIDTH*y + x); uint16_t red = (0xF800 & color) >> 11; @@ -465,7 +465,7 @@ void lcdDrawFullRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t co DMA2D_StartTransfer(); /* Wait for CTC Flag activation */ - while(DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); + while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); } void lcdRefresh() @@ -476,5 +476,4 @@ void lcdRefresh() else LCD_SetLayer(LCD_BACKGROUND_LAYER); LCD_SetTransparency(0); - // memcpy((void *)(SDRAM_BANK_ADDR+FRAME_BUFFER_OFFSET), (void *)SDRAM_BANK_ADDR, 480*272*2); } diff --git a/radio/src/tasks_arm.cpp b/radio/src/tasks_arm.cpp index 26442ae0b..523153b33 100644 --- a/radio/src/tasks_arm.cpp +++ b/radio/src/tasks_arm.cpp @@ -197,7 +197,8 @@ void menusTask(void * pdata) BACKLIGHT_OFF(); -#if defined(PCBTARANIS) +#if defined(COLORLCD) +#elif defined(PCBTARANIS) displaySleepBitmap(); #else lcd_clear(); diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h index 030e35c38..a49985ba2 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_core.h @@ -30,7 +30,7 @@ #define __USBD_CORE_H /* Includes ------------------------------------------------------------------*/ -#include "../../../../../../targets/Flamenco/usbd_conf.h" +#include "usbd_conf.h" #include "../../../STM32_USB_OTG_Driver/inc/usb_dcd.h" #include "usbd_def.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h index 467f5f822..87b0f5444 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_req.h @@ -31,7 +31,7 @@ #define __USB_REQUEST_H_ /* Includes ------------------------------------------------------------------*/ -#include "../../../../../../targets/Flamenco/usbd_conf.h" +#include "usbd_conf.h" #include "usbd_def.h" #include "usbd_core.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c index 8a5f8390d..cd7a409d9 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/src/usbd_req.c @@ -28,7 +28,7 @@ /* Includes ------------------------------------------------------------------*/ #include "usbd_req.h" -#include "../../../../../../targets/Flamenco/usbd_desc.h" +#include "usbd_desc.h" #include "usbd_ioreq.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h index ee651a992..8e0d264f4 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_defines.h @@ -30,7 +30,7 @@ #define __USB_DEF_H__ /* Includes ------------------------------------------------------------------*/ -#include "../../../../../targets/Flamenco/usb_conf.h" +#include "usb_conf.h" /** @addtogroup USB_OTG_DRIVER * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h index cc490c0ed..323e87056 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_regs.h @@ -30,7 +30,7 @@ #define __USB_OTG_REGS_H__ /* Includes ------------------------------------------------------------------*/ -#include "../../../../../targets/Flamenco/usb_conf.h" +#include "usb_conf.h" /** @addtogroup USB_OTG_DRIVER From e25115b1045ca6e311cfecdc43ac14a64bb831d6 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 8 Oct 2015 18:51:05 +0200 Subject: [PATCH 12/74] Compilation fixes --- radio/src/gui/9X/lcd.cpp | 2 +- radio/src/gui/9X/lcd.h | 2 +- radio/src/gui/Taranis/lcd.cpp | 2 +- radio/src/gui/Taranis/lcd.h | 2 +- radio/src/targets/simu/simpgmspace.cpp | 2 +- radio/src/tests/eeprom.cpp | 10 +++++----- .../Class/audio/inc/usbd_audio_core.h | 2 +- .../Class/cdc/inc/usbd_cdc_if_template.h | 4 ++-- .../Class/dfu/inc/usbd_dfu_mal.h | 3 +-- .../Class/dfu/inc/usbd_mem_if_template.h | 2 +- .../Class/dfu/src/usbd_dfu_core.c | 2 +- .../Class/hid/src/usbd_hid_core.c | 2 +- .../Class/msc/inc/usbd_msc_data.h | 2 +- .../Core/inc/usbd_conf_template.h | 2 +- .../STM32_USB_OTG_Driver/inc/usb_conf_template.h | 2 +- .../Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c | 2 +- 16 files changed, 21 insertions(+), 22 deletions(-) diff --git a/radio/src/gui/9X/lcd.cpp b/radio/src/gui/9X/lcd.cpp index 1c6751f1a..ee9b09f60 100644 --- a/radio/src/gui/9X/lcd.cpp +++ b/radio/src/gui/9X/lcd.cpp @@ -40,7 +40,7 @@ display_t displayBuf[DISPLAY_BUF_SIZE]; void lcd_clear() { - memset(displayBuf, 0, DISPLAY_BUFER_SIZE); + memset(displayBuf, 0, DISPLAY_BUFFER_SIZE); } coord_t lcdLastPos; diff --git a/radio/src/gui/9X/lcd.h b/radio/src/gui/9X/lcd.h index 1789429ab..a5b87b5e3 100644 --- a/radio/src/gui/9X/lcd.h +++ b/radio/src/gui/9X/lcd.h @@ -144,7 +144,7 @@ extern display_t displayBuf[DISPLAY_BUF_SIZE]; extern coord_t lcdLastPos; extern coord_t lcdNextPos; -#define DISPLAY_BUFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) +#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) #define DISPLAY_END (displayBuf + DISPLAY_BUF_SIZE) #define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) diff --git a/radio/src/gui/Taranis/lcd.cpp b/radio/src/gui/Taranis/lcd.cpp index 93ccce20d..18f17e8cf 100644 --- a/radio/src/gui/Taranis/lcd.cpp +++ b/radio/src/gui/Taranis/lcd.cpp @@ -52,7 +52,7 @@ inline bool lcdIsPointOutside(coord_t x, coord_t y) void lcd_clear() { - memset(displayBuf, 0, DISPLAY_BUFER_SIZE); + memset(displayBuf, 0, DISPLAY_BUFFER_SIZE); } coord_t lcdLastPos; diff --git a/radio/src/gui/Taranis/lcd.h b/radio/src/gui/Taranis/lcd.h index a5342efb8..a0813c0ec 100644 --- a/radio/src/gui/Taranis/lcd.h +++ b/radio/src/gui/Taranis/lcd.h @@ -138,7 +138,7 @@ extern coord_t lcdLastPos; extern coord_t lcdNextPos; -#define DISPLAY_BUFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) +#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) #define DISPLAY_END (displayBuf + DISPLAY_BUF_SIZE) #define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index f41d475c2..27002ae39 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -348,7 +348,7 @@ void *main_thread(void *) try { #endif -#if defined(COLORLCD) +#if defined(PCBHORUS) lcdColorsInit(); #endif diff --git a/radio/src/tests/eeprom.cpp b/radio/src/tests/eeprom.cpp index 0a032ae25..bf0a5536f 100644 --- a/radio/src/tests/eeprom.cpp +++ b/radio/src/tests/eeprom.cpp @@ -37,7 +37,7 @@ #include "gtests.h" #if !defined(PCBSKY9X) -TEST(EEPROM, 100_random_writes) +TEST(Eeprom, 100_random_writes) { eepromFile = NULL; // in memory RlcFile f; @@ -60,7 +60,7 @@ TEST(EEPROM, 100_random_writes) } } -TEST(EEPROM, test2) +TEST(Eeprom, test2) { eepromFile = NULL; // in memory RlcFile f; @@ -83,7 +83,7 @@ TEST(EEPROM, test2) EXPECT_EQ(sz, 300); } -TEST(EEPROM, eeCheckImmediately) +TEST(Eeprom, eeCheckImmediately) { eepromFile = NULL; // in memory // RlcFile f; @@ -108,7 +108,7 @@ TEST(EEPROM, eeCheckImmediately) EXPECT_EQ(sz, 300); } -TEST(EEPROM, copy) +TEST(Eeprom, copy) { eepromFile = NULL; // in memory @@ -133,7 +133,7 @@ TEST(EEPROM, copy) EXPECT_EQ(sz, 300); } -TEST(EEPROM, rm) +TEST(Eeprom, rm) { eepromFile = NULL; // in memory diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h index 0a0e6f884..914f70218 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/audio/inc/usbd_audio_core.h @@ -30,7 +30,7 @@ #ifndef __USB_AUDIO_CORE_H_ #define __USB_AUDIO_CORE_H_ -#include "../../../../../../../targets/Flamenco/usbd_desc.h" +#include "usbd_desc.h" #include "usbd_ioreq.h" #include "usbd_req.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h index f3f1af32f..1b0c3f048 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc/inc/usbd_cdc_if_template.h @@ -30,8 +30,8 @@ #define __USBD_CDC_IF_TEMPLATE_H /* Includes ------------------------------------------------------------------*/ -#include "../../../../../../../targets/Flamenco/usb_conf.h" -#include "../../../../../../../targets/Flamenco/usbd_conf.h" +#include "usb_conf.h" +#include "usbd_conf.h" #include "usbd_cdc_core.h" /* Exported types ------------------------------------------------------------*/ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h index 740d3b22e..2711dfc0e 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_dfu_mal.h @@ -30,8 +30,7 @@ #define __DFU_MAL_H /* Includes ------------------------------------------------------------------*/ -#include "../../../../../../../targets/Flamenco/usb_conf.h" -#include "../../../../../../../targets/Flamenco/usbd_conf.h" +#include "usb_conf.h" #include "usbd_conf.h" #include "usbd_dfu_core.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h index 777649e0a..d4542f515 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/inc/usbd_mem_if_template.h @@ -30,7 +30,7 @@ #define __MEM_IF_MAL_H /* Includes ------------------------------------------------------------------*/ -#include "../../../../../../../targets/Flamenco/usb_conf.h" +#include "usb_conf.h" #include "usbd_dfu_mal.h" /* Exported types ------------------------------------------------------------*/ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c index 56ef1e662..0cfa47897 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/dfu/src/usbd_dfu_core.c @@ -64,7 +64,7 @@ /* Includes ------------------------------------------------------------------*/ #include "usbd_dfu_core.h" -#include "../../../../../../../targets/Flamenco/usbd_desc.h" +#include "usbd_desc.h" #include "usbd_req.h" #include "usb_bsp.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c index 99eb14a05..3dcfd918c 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/hid/src/usbd_hid_core.c @@ -49,7 +49,7 @@ /* Includes ------------------------------------------------------------------*/ #include "usbd_hid_core.h" -#include "../../../../../../../targets/Flamenco/usbd_desc.h" +#include "usbd_desc.h" #include "usbd_req.h" diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h index ef440fbc4..c8f3a2adf 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/msc/inc/usbd_msc_data.h @@ -31,7 +31,7 @@ #define _USBD_MSC_DATA_H_ /* Includes ------------------------------------------------------------------*/ -#include "../../../../../../../targets/Flamenco/usbd_conf.h" +#include "usbd_conf.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h index ab10fa7a1..b1a20f947 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_conf_template.h @@ -30,7 +30,7 @@ #define __USBD_CONF__H__ /* Includes ------------------------------------------------------------------*/ -#include "../../../../../../targets/Flamenco/usb_conf.h" +#include "usb_conf.h" /** @defgroup USB_CONF_Exported_Defines * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h index 5de0d5143..2e415e126 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_conf_template.h @@ -30,7 +30,7 @@ #define __USB_CONF__H__ /* Includes ------------------------------------------------------------------*/ -#include "../../../../../targets/Flamenco/usb_conf.h" +#include "usb_conf.h" /** @addtogroup USB_OTG_DRIVER * @{ diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c index 5ba595dfd..115b1ad5d 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/src/usb_hcd.c @@ -29,7 +29,7 @@ #include "usb_core.h" #include "usb_hcd.h" -#include "../../../../../targets/Flamenco/usb_conf.h" +#include "usb_conf.h" #include "usb_bsp.h" From ce2f7b61e9a4dc1def7e67e516428416796f8ad1 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 9 Oct 2015 07:29:33 +0200 Subject: [PATCH 13/74] New Horus masks added --- radio/src/bitmaps/Horus/mask_ball.png | Bin 254 -> 0 bytes radio/src/bitmaps/Horus/mask_current_bg.png | Bin 0 -> 697 bytes radio/src/bitmaps/Horus/mask_current_dot.png | Bin 0 -> 293 bytes radio/src/bitmaps/Horus/mask_dot.png | Bin 0 -> 270 bytes radio/src/bitmaps/Horus/mask_top_polygon.png | Bin 0 -> 526 bytes 5 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 radio/src/bitmaps/Horus/mask_ball.png create mode 100644 radio/src/bitmaps/Horus/mask_current_bg.png create mode 100644 radio/src/bitmaps/Horus/mask_current_dot.png create mode 100644 radio/src/bitmaps/Horus/mask_dot.png create mode 100644 radio/src/bitmaps/Horus/mask_top_polygon.png diff --git a/radio/src/bitmaps/Horus/mask_ball.png b/radio/src/bitmaps/Horus/mask_ball.png deleted file mode 100644 index b6a4a2c8376aeb66c783ad9d23f7ddc803425462..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@KN#5=*4F5rJ!QSPQfg+p*9+AaB zRo6k7(Me-=1yGQ^#M9T6{XQ4F5S!&YwVF#np>9tX#}JM4b0-<{u_$u5{+HOvy_C28 z!!8rKrbE+qXB;wnY+^lo&Aun)dnO#%ucTC9{M;p*H;t{F-PqYBAlvnvB!A)M>i!@v zh96#FVdQ&MBb@0JUCS#sB~S diff --git a/radio/src/bitmaps/Horus/mask_current_bg.png b/radio/src/bitmaps/Horus/mask_current_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..553602dcfc48bee3042d369cc78f1b13c743dec4 GIT binary patch literal 697 zcmeAS@N?(olHy`uVBq!ia0vp^8bEBp!3HGzHFkXiQfx`y?k)`fL2$v|<&%LToCO|{ z#Xud`L734=V|E2lkiEpy*OmP~7bmL#LkMs4X$A%+H%}MGkc@k8Z|={&?I3dCplyl;NA8R^i%E=k~6_dluHKyqO)P7aJDBo%Mel=zISRu1iL^{4%YDFUf^kYBM z)8A3N4L_A0-0nAg{`|Sd(rJA2{FZ;I+IwQMx)7(7kRm^iYn_XMbegDn7p!L_~_tcxq9Or!4byVqQj+v0rL=T0ff0#v_9#qP$du{jY z!NG%!XVZ*@HpJ+)H>`X8s8Vj(&RyobhaETs?&-9B-D%~2rmidh)Ip*!Pb z%7h>{rhlKa-S(XLy!mhI+_`hz3)|b<7iMpa=!wzWF7_^NvP*BpQ`7tV%POO*-FM$j zGnzTUYiWT@d(cXeQ+IL??q1iO8kxO5b;SzN{VI8Rc^O-yc-o!QHsAcz!`+yBTSiuP z;_0VK6Q2L7ngMkG}YLhzjrV0$-CNXJWLu(XPix& zGjE=eT)(BY_2KWeIk%4$$^gB4GNtJ4t!lFe_v3pNIGiN+oP8AhNW8%OK8MoRRhhs< O#^CAd=d#Wzp$PzY=s)TJ literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_current_dot.png b/radio/src/bitmaps/Horus/mask_current_dot.png new file mode 100644 index 0000000000000000000000000000000000000000..6f76965a93d563832f44c81f759f475026b442ce GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2@KN#5=*KpF^sI`6IrQk(@Ik;On& z*Fl)kNn>^eP>{XE)7O>#J{KpeC}+U3^IAZmO`a}}AsXlBPTI@cV!+cXuWsZWvn-K= z?Sxm(WHsJ550rGfcW_`w%QXwR=M={tls&go&1LP^rw{g~dv&HAn z2Vq7hjoB4ILCF%=h?3y^w370~qEv?R@^Zb*yzJuS#DY}4{G#;P?`))iin={r978lF z&Yfh)$D+vL`d?xv_fp>W54%j{nhs6dopH$Qv5EEUHT#~F@0oC5zmigc@pG4K-ZZvy zc4KFkfNa-ulKh32tNVkz7=CzZs#d+vHrc?UR3#eL@j}q>*pHnLb0146FIZL2sl7_- z1QB~WvxLhv1es(v<*eB@{73J?J%FuUu||5O?cns zi4fi0Pm74h;c)2nu{)_l Date: Fri, 9 Oct 2015 07:30:38 +0200 Subject: [PATCH 14/74] Makefile fixes --- radio/src/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/radio/src/Makefile b/radio/src/Makefile index aff9aa9ec..cca3b777e 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -1186,7 +1186,7 @@ endif ifeq ($(PCB), HORUS) # HORUS radio ARCH = ARM - LCDSIZE = 320 + LCDSIZE = 480 GUIDIRECTORY = Horus FLAVOUR = HORUS LDSCRIPT = targets/Horus/stm32f4_flash.ld @@ -1851,6 +1851,7 @@ ifeq ($(PCB), $(filter $(PCB), HORUS)) MIDSIZE = 16 DBLSIZE = 20 XXLSIZE = 38 + MASKFORMAT = 8bits else FONTNAME = Roboto TINSIZE = 5 @@ -1859,13 +1860,14 @@ else MIDSIZE = 11 DBLSIZE = 14 XXLSIZE = 38 + MASKFORMAT = 4bits endif .PHONY: fonts fonts: $(FONTS) fonts/font_%size.lbm: fonts/font_%size.png - $(IMG2LBM) $^ $@ $(LCDSIZE) 8bits + $(IMG2LBM) $^ $@ $(LCDSIZE) $(MASKFORMAT) fonts/font_tinsize.png: $(FONT2PNG) $(FONTNAME) $(TINSIZE) fonts/font_tinsize @@ -1907,7 +1909,7 @@ bitmaps/Flamenco/%.lbm: bitmaps/Flamenco/%.png $(IMG2LBM) $^ $@ $(LCDSIZE) 4/4/4/4 bitmaps/Horus/mask_%.lbm: bitmaps/Horus/mask_%.png - $(IMG2LBM) $^ $@ $(LCDSIZE) 8bits + $(IMG2LBM) $^ $@ $(LCDSIZE) $(MASKFORMAT) bitmaps/Horus/%.lbm: bitmaps/Horus/%.png $(IMG2LBM) $^ $@ $(LCDSIZE) 5/6/5 From e0fa546b10e45775f107d7112f64554696b3c45c Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 9 Oct 2015 19:36:57 +0200 Subject: [PATCH 15/74] Quick and dirty - I will rewrite this commit later --- radio/src/bitmaps/Horus/mask_index_shadow.png | Bin 0 -> 743 bytes radio/src/gui/Horus/menus.h | 4 +- radio/src/gui/Horus/navigation.cpp | 7 ++- radio/src/gui/Horus/widgets.cpp | 41 +++++++++++-- radio/src/main_arm.cpp | 54 ++++++++++-------- 5 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_index_shadow.png diff --git a/radio/src/bitmaps/Horus/mask_index_shadow.png b/radio/src/bitmaps/Horus/mask_index_shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..ba2a278a2e11d33f96b060cc12c2b0317705adca GIT binary patch literal 743 zcmeAS@N?(olHy`uVBq!ia0vp^sz7YP!3HF&t{J)kDVAa<&kznEsNqQI0P;BtJR*yM z>aT+^qm#z$3ZS55iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}CDV{El zAsLNtZ*KH{?I3gDuK_lw=a`d7T=5A_{C_;>8K)Y9YYy2X{{ zsHm{e#MAQ=o1y>x%g2wJUwODMII8dT^68fr+6RV)9_2qh(_G>{7d8kdP?{+_tv*ZIHCpQrcr^;y{4tNUK&?=CAZzj^Oo-jGTXo;{kMpTE~!uGj7B*W34;I9deeugl*WrLi>b`o8+j_um`OK6~Qx zpT&0yUwKZ>yng-q*R9VhdDxhn6n+%-D{#E{TD5+|_R9D`5g#?-O*+3{FJU-jpu)kV zeBtO(*5AK>Gk9pe{QmpRn>PZk7pH4zs_uLL`1il}lY_iAZrJeQ{Op-?!X~N6$jDq- zAA5c2?YBa%g5BNSZp-gxbg8x^Sj~M@d2jjb`Sa!#RaAIPQu$i7*7Wr0(?9m*|M?-^ ze{D)oOl+*7RPTkT#}<6Rh`n>~o{V1hx$mlykbo388E&U?OR&mMu}hJ`YdmuV(STzXmZqQq*; z?YF%kne#X8min) {val--;} else {val=max;} -void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t rowcount, uint16_t scrollbar_X, uint8_t flags) +bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t rowcount, uint16_t scrollbar_X, uint8_t flags) { vertpos_t l_posVert = m_posVert; horzpos_t l_posHorz = m_posHorz; @@ -246,7 +246,7 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t if (cc != curr) { chainMenu((MenuFuncP)pgm_read_adr(&menuTab[cc])); - return; + return false; } } @@ -315,7 +315,6 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_REPT(KEY_LEFT): DEC(l_posHorz, 0, maxcol); break; - break; case EVT_ROTARY_RIGHT: if (s_editMode != 0) break; @@ -445,6 +444,8 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t m_posVert = l_posVert; m_posHorz = l_posHorz; + + return true; } void check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount, uint16_t scrollbar_X) diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index bda3da69e..70a3566a6 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -170,6 +170,7 @@ const uint8_t LBM_LUA_SCRIPTS_ICON[] = { const uint8_t LBM_TELEMETRY_ICON[] = { #include "../../bitmaps/Horus/mask_telemetry.lbm" }; + const uint8_t * const LBM_MODEL_ICONS[] = { LBM_MODEL_SETUP_ICON, CASE_HELI(LBM_HELI_ICON) @@ -187,8 +188,28 @@ const uint8_t * const LBM_MODEL_ICONS[] = { LBM_TELEMETRY_ICON }; -const uint8_t LBM_BALL[] = { -#include "../../bitmaps/Horus/mask_ball.lbm" +const uint8_t LBM_TOP_POLYGON[] = { +#include "../../bitmaps/Horus/mask_top_polygon.lbm" +}; + +const uint8_t LBM_DOT[] = { +#include "../../bitmaps/Horus/mask_dot.lbm" +}; + +const uint8_t LBM_CURRENT_BG[] = { +#include "../../bitmaps/Horus/mask_current_bg.lbm" +}; + +const uint8_t LBM_CURRENT_SHADOW[] = { +#include "../../bitmaps/Horus/mask_index_shadow.lbm" +}; + +const uint8_t LBM_CURRENT_DOT[] = { +#include "../../bitmaps/Horus/mask_current_dot.lbm" +}; + +const uint8_t LBM_MENU_MODEL[] = { +#include "../../bitmaps/Horus/mask_menu_model.lbm" }; #define MENU_ICONS_SPACING 34 @@ -197,17 +218,29 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun { // clear the screen lcdDrawFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); + lcdDrawFilledRect(0, MENU_HEADER_HEIGHT, LCD_W, MENU_TITLE_TOP-MENU_HEADER_HEIGHT, TEXT_BGCOLOR); lcdDrawFilledRect(0, MENU_TITLE_TOP, LCD_W, MENU_TITLE_HEIGHT, TITLE_BGCOLOR); lcdDrawFilledRect(0, MENU_BODY_TOP, LCD_W, MENU_BODY_HEIGHT, TEXT_BGCOLOR); lcdDrawFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); - lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-11, 0, 36, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); + lcdDrawBitmapPattern(0, 0, LBM_TOP_POLYGON, TITLE_BGCOLOR); + + lcdDrawBitmapPattern(5, 5, LBM_MENU_MODEL, MENU_TITLE_COLOR); + + if (m_posVert < 0) { + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-11, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-11, 0, LBM_CURRENT_SHADOW, TEXT_COLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); + } + else { + lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-11, 0, 36, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_DOT, MENU_TITLE_COLOR); + } for (int i=0; i Date: Fri, 9 Oct 2015 20:59:23 +0200 Subject: [PATCH 16/74] index masks --- radio/src/bitmaps/Horus/mask_current_bg.png | Bin 697 -> 329 bytes radio/src/bitmaps/Horus/mask_index_shadow.png | Bin 743 -> 715 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/radio/src/bitmaps/Horus/mask_current_bg.png b/radio/src/bitmaps/Horus/mask_current_bg.png index 553602dcfc48bee3042d369cc78f1b13c743dec4..bc2bfd0153928e8ccc058650133b2074c37e76ce 100644 GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0vp^DnM+^!3HG%UtYBvNU;<@dFT$-<` zx`aFRKbwuZfVaXl2c5EYQyJfCj$o{mmN%IMl#X_Ku7(8A5T-G@yGywq1vUl?U delta 686 zcmX@fw3F4iGr-TCmrII^fq{Y7)59eQNNWJG1qU0D?AO@!4M?#idAqwX{0G4WdzVk1 zXed$7UgGKN%6^}VlU0Brgtz%L0|S$rr;B4q#=W;U_h;XB5IOL1eM$OhvtxZ1gBJ5h zs@zG+Iq|@cHJvl%HT1Q}ljnzp7u9@c5VihbNEXmzt)0xWeM2(0F81jjUJ3 zREv#wQVJf*WlSj9_4~s++f0FmTT>qY_z`iJcQP;k&U()oKWoliy(-GkmN>yjP4Gaw zFJsiSqfeiz&h=}*nBlTB=H40}Hpc^BznU_9tdLnMA|2l>wW5&#`mvws>F+4shM!6g zZuc8LfBsx!=`_B1e#^g9?L9GBU5Ha_s@Ic`w{PG6-dHd1@5k_=cD~zU$Nl%!IhvLV zm+Zc~wW0fH)8D^;m!}$RxxcJ)iqK($X?*u?-HOWoRAMD-CGKQn@!fXDl*9#@vnuAf zo6W8*=)V8vUC{dL@_XvdWsY+`>^iD+GsjHGXrhP0(m%{1P7f;O*1fj-_2A$^#kQ>v# z&)IH!PJG_{w{`B^x$cGS?d=P*H%9ct=xrB!7dP3Zx8kYk{rzQ?(bev|@1_~eoZz*z zK&CxtrO2r}xd(Tz>rRc#UZ1*Rh3I~jyu7@Otx-Jf&S{%(e(K?F%q_YtBP%=c^i!qs zkCTlmq^G3@t_>4(GLYc;P%FRm%6s{X%Q^*`YV6M6yBGK5UF|g#$USNKoL+R_POkmn$@O1Ta JS?83{1ORu+KpOx6 diff --git a/radio/src/bitmaps/Horus/mask_index_shadow.png b/radio/src/bitmaps/Horus/mask_index_shadow.png index ba2a278a2e11d33f96b060cc12c2b0317705adca..a2f7f684bbf8477e789116f9f498697bb25b2f32 100644 GIT binary patch delta 651 zcmaFPdYVm=@{k`%$)o}WO^N|ZO#WH5G z&Pr=sMl@56amzIoPp4VSp4{BEI&8IvWi9`Uj~_o?S6F%_JYiZYPrI|9zyHhczg0Yq zN+*5(T)b`Dw)$m#+1qbttko3j-l@moz~Q*~;(^aLEdrCRBUXmoxO;bLgwCX8FRys+ zi+}2GYh$xu%AxPIWo2a%v9V`+W1^yzIGAkCJ98HN<3@84V6***LAZCcSzABna# zYt|?%{d3jQZdRprSz==2^5yEk{@NAFOkSpGZf+hE6C-fkaH?19+qb!Ve0&)uQWocO z{%0I|GL>PeNb|vlxb?@MK2=RDFE8&oo_y-5(d^l?L0H1^~`@9`rMS;4l4$V zOjO}Kefsp8_3Jlp+t&8}xAEhD+yZLdX*P17zkO@FCUw2^qYeM@!hL_{rM;Bp;y8ZS UYoRMMFnuw2y85}Sb4q9e08pkhPyhe` delta 679 zcmX@j`kYmHKE>0;F(jk$?ahte zuN`C#e0+abRrTajzNJ?kG_~^l@_w;fSpSN*{Gq<%2mg-UmRfpzUAMTh92FH7ns|DC zVl(u=fBE=P^D7Vc1xNLrUOxTOLi@nb(4+jPXPPUVFJ86EiK8)yOY`ZUKy!0*pXI?Z z*Oy*@eerGC^L{HkyEGfQ%X#(NZ{E74#B=!P;zt(GUcT(qIX%NiZAxnA9P?M3bxxUd z8=YO`vsvRZ(ahfySNuBv_xbbmzP>&SdwX@?%lzGC<>fc;-OJnZ@#$05>gwwM?Ltlu zEcn)jb=$K?*Yor9_nOP~x_$k6`<@d=i@^MK`CFqjmd0J*SHJoGd*j(>PkjEf_)g&~ z&&iqBuV4SV^?4-^8*`JwkD`7Bju&66)^FHe86PO(qb9sb=lAO+42KLb(&4*n+RTy!_6+dqy&R_B9OWKS#vIrq?Wm3rbb@NUNR+PZDeF*VsUZputKOUpw#lo;`a6jvE%rOkSp$C~@g!$%_)JEw|tP z`o7ZhzT4j8qOJ#X%ogi-PYK$wu6xQR!)I&bfAU^f+P*U?5}5WFJYD@<);T3K0RZZT BO#1); From df698e9ff29694efde446d2dc05c1ea8e4a1f876 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 9 Oct 2015 23:31:38 +0200 Subject: [PATCH 17/74] Cosmetics --- .../gui/Horus/menu_model_logical_switches.cpp | 2 +- radio/src/gui/Horus/widgets.cpp | 22 +++++-------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/radio/src/gui/Horus/menu_model_logical_switches.cpp b/radio/src/gui/Horus/menu_model_logical_switches.cpp index 7d20c6eef..5c2afc723 100644 --- a/radio/src/gui/Horus/menu_model_logical_switches.cpp +++ b/radio/src/gui/Horus/menu_model_logical_switches.cpp @@ -105,7 +105,7 @@ void menuModelLogicalSwitches(evt_t event) int sub = m_posVert; horzpos_t horz = m_posHorz; - if (horz>=0) { + if (sub>= 0 && horz>=0) { displayColumnHeader(STR_CSW_HEADERS, horz); } diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 70a3566a6..388e7e10d 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -82,13 +82,6 @@ void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) } } -void displayScreenIndex(uint8_t index, uint8_t count) -{ - // char str[10]; - // sprintf(str, "%d/%d", index+1, count); - // lcd_putsAtt(LCD_W-8-getTextWidth(str), MENU_FOOTER_TOP, str, m_posVert < 0 ? INVERS : TEXT_COLOR); -} - void displayScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { lcdDrawVerticalLine(x, y, h, LINE_COLOR); @@ -225,12 +218,12 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun lcdDrawBitmapPattern(0, 0, LBM_TOP_POLYGON, TITLE_BGCOLOR); - lcdDrawBitmapPattern(5, 5, LBM_MENU_MODEL, MENU_TITLE_COLOR); + lcdDrawBitmapPattern(5, 7, LBM_MENU_MODEL, MENU_TITLE_COLOR); if (m_posVert < 0) { - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-11, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-11, 0, LBM_CURRENT_SHADOW, TEXT_COLOR); - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_SHADOW, TEXT_COLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-1, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); } else { lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-11, 0, 36, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); @@ -238,18 +231,13 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun } for (int i=0; i 0) { - displayScreenIndex(pageIndex, pageCount); - } } select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, evt_t event) From 8f5b844e23bd1039693794406c77c65870458021 Mon Sep 17 00:00:00 2001 From: martin Date: Sat, 10 Oct 2015 00:18:22 +0200 Subject: [PATCH 18/74] index cosmetics --- radio/src/bitmaps/Horus/mask_current_bg.png | Bin 329 -> 341 bytes radio/src/bitmaps/Horus/mask_index_shadow.png | Bin 715 -> 689 bytes radio/src/gui/Horus/widgets.cpp | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/bitmaps/Horus/mask_current_bg.png b/radio/src/bitmaps/Horus/mask_current_bg.png index bc2bfd0153928e8ccc058650133b2074c37e76ce..75244f36b0d930935ec253340cf20bc127d1e5bb 100644 GIT binary patch delta 274 zcmX@fbd^c5Gr-TCmrII^fq{Y7)59eQNUH&{F$Wuv{H<2iHc`>H{*tGQV@O2n+nXCX zTO0&hAKol|>zFJpa9TingQBtaMh@nK9z43wV+$5m9GB)l_u%h_n{PfGY?D62^XszIU=RdDEj+{2L zeE)CF*~@;OJd;|mEmu0oYs2^Y=P!>)Y+Sdx-1d$EgT!$`!(+l)Ee(kh@7C;NXKqN1 ryih88YLiQ#NXBik&o=jCyTy-c&D^!{%B9M delta 262 zcmcc0bdpK2Gr-TCmrII^fq{Y7)59eQNUH#`F$Wuv{C|1X?um-N^~XG2977^n-`?EF z+iW1vn)rN&sttp!Xz-3S9sWZydAe)~Go~xWc6>Ud8l-Vgf2Z|)yOT@vHC2~zr~YTN zQ5W!5nC76<7{RoWC5iKhP=_iW0X_Clxwrk|qo%38{r#^jP3HKPb+0R9E|~Z(*XKR_ z;i6AU`HPd!D*v54b6Q}sy|z0`*tt>E*IG=z`>BU)?hR9u`>bLz3Y0L85V49 o3*ss|cfxYmdKI;Vst0Ne3qxBvhE diff --git a/radio/src/bitmaps/Horus/mask_index_shadow.png b/radio/src/bitmaps/Horus/mask_index_shadow.png index a2f7f684bbf8477e789116f9f498697bb25b2f32..4370002ba3527de2b60703f6662a4ab49ac7bb0b 100644 GIT binary patch delta 625 zcmX@jx{+0}Gr-TCmrII^fq{Y7)59eQNNWMH83!AX+<2XP_e4eCdOc4U$B>N1x3|uF zJ$7Jd_?WLYx5ZP$YpPJzMzMpFFF4O%qknla|HZ`>LRFsv_I$b4G%tsh+cn2TcVh;&6@`^Sukl zX3YQT_VBa)=j4U^pD)-}9B|um#ao#xWqeuI>|5tH-#X`*OGx;ibOz^xmoGbCzs_E_ zZe2x9jgCp>?UyAhLbU2_(rx7GFTX9D7_?~0q0f7qKCg|OwsH6F)923d?R&4>Qqf#B z&wXdivkxC8sCaTY*53dBZO+r8lXvgRMrF@Bt5ztpc*?JDRt=LU3op`G6t;Tdl&WSn z#{(Zf8g7l^P4-#X*mdW_vuA9IEP)~yv$kezjdBSL$*5t_RE>K5)nvz%Nd`O@qr~Ov zpX|P5^7PT8N6b|P1r9RDSsXuvKi*TXwRC-%*jb6@Df9myznXRQ=1s}(-@j+9?R-2V zYwNF%d;fY%`!;)PdQNINn$#H7HGR6c+hW1Jas4M#ny$@qTkN>|?y{hjH9rGfg@3la zHtsPz^Srq2uwwJUhLD#UQk|(Pqn$7m=@{k`%$)o}WO^N|ZO#WH5G z&Pr=sMl@56amzIoPp4VSp4{BEI&8IvWi9`Uj~_o?S6F%_JYiZYPrI|9zyHhczg0Yq zN+*5(T)b`Dw)$m#+1qbttko3j-l@moz~Q*~;(^aLEdrCRBUXmoxO;bLgwCX8FRys+ zi+}2GYh$xu%AxPIWo2a%v9V`+W1^yzIGAkCJ98HN<3@84V6***LAZCcSzABna# zYt|?%{d3jQZdRprSz==2^5yEk{@NAFOkSpGZf+hE6C-fkaH?19+qb!Ve0&)uQWocO z{%0I|GL>PeNb|vlxb?@MK2=RDFE8&oo_y-5(d^l?L0H1^~`@9`rMS;4l4$V zOjO}Kefsp8_3Jlp+t&8}xAEhD+yZLdX*P17zkO@FCUw2^qYeM@!hL_{rM;Bp;y8ZS UYoRMMFnuw2y85}Sb4q9e0LMi%8UO$Q diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 388e7e10d..f1e58f7ee 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -223,7 +223,7 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun if (m_posVert < 0) { lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_SHADOW, TEXT_COLOR); - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-1, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); } else { lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-11, 0, 36, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); @@ -231,7 +231,7 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun } for (int i=0; i Date: Sat, 10 Oct 2015 08:13:12 +0200 Subject: [PATCH 19/74] Code simplification --- radio/src/gui/Horus/menus.h | 33 ++++++++-------------------- radio/src/gui/Horus/navigation.cpp | 8 +++---- radio/src/gui/Taranis/menus.h | 3 --- radio/src/gui/Taranis/navigation.cpp | 4 ---- 4 files changed, 13 insertions(+), 35 deletions(-) diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h index 7ab3fc062..64ae48ff5 100644 --- a/radio/src/gui/Horus/menus.h +++ b/radio/src/gui/Horus/menus.h @@ -217,46 +217,31 @@ bool isInputSourceAvailable(int source); #define CHECK_FLAG_NO_SCREEN_INDEX 1 bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t maxrow, uint16_t scrollbar_X, uint8_t flags=0); -void check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t maxrow, uint16_t scrollbar_X); -void check_submenu_simple(check_event_t event, uint8_t maxrow, uint16_t scrollbar_X); -void title(const pm_char * s); -#define TITLE(str) title(str) +bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t maxrow, uint16_t scrollbar_X); +bool check_submenu_simple(check_event_t event, uint8_t maxrow, uint16_t scrollbar_X); #define MENU_TAB(...) const uint8_t mstate_tab[] = __VA_ARGS__ -#define MENU_CHECK(tab, menu, lines_count, scrollbar_X) \ - check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X) - -#define MENU_CHECK_FLAGS(tab, menu, flags, lines_count, scrollbar_X) \ - check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X, flags) - void drawMenuTemplate(const char *title, evt_t event, int pageIndex=0, int pageCount=0); #define MENU(title, tab, menu, lines_count, scrollbar_X, ...) \ + MENU_TAB(__VA_ARGS__); \ if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ - MENU_TAB(__VA_ARGS__); \ - if (!MENU_CHECK(tab, menu, lines_count, scrollbar_X)) return; \ + if (!check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X)) return; \ drawMenuTemplate(title, event, menu, DIM(tab)); \ -#define MENU_FLAGS(title, tab, menu, flags, lines_count, scrollbar_X, ...) \ - MENU_TAB(__VA_ARGS__); \ - MENU_CHECK_FLAGS(tab, menu, flags, lines_count, scrollbar_X); \ - TITLE(title) - -#define SIMPLE_MENU_NOTITLE(tab, menu, lines_count, scrollbar_X) \ - check_simple(event, menu, tab, DIM(tab), lines_count, scrollbar_X); - #define SIMPLE_MENU(title, tab, menu, lines_count, scrollbar_X) \ + if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ + if (!check_simple(event, menu, tab, DIM(tab), lines_count, scrollbar_X)) return; \ drawMenuTemplate(title, event, menu, DIM(tab)); \ - SIMPLE_MENU_NOTITLE(tab, menu, lines_count, scrollbar_X) #define SUBMENU(title, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ - drawMenuTemplate(title, event); \ - check(event, 0, NULL, 0, mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X) + if (!check(event, 0, NULL, 0, mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X)) return; \ + drawMenuTemplate(title, event); #define SIMPLE_SUBMENU_NOTITLE(lines_count, scrollbar_X) \ - check_submenu_simple(event, lines_count, scrollbar_X); + if (!check_submenu_simple(event, lines_count, scrollbar_X)) return #define SIMPLE_SUBMENU(title, lines_count, scrollbar_X) \ SIMPLE_SUBMENU_NOTITLE(lines_count, scrollbar_X); \ diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp index d001cae82..843737df2 100644 --- a/radio/src/gui/Horus/navigation.cpp +++ b/radio/src/gui/Horus/navigation.cpp @@ -448,14 +448,14 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t return true; } -void check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount, uint16_t scrollbar_X) +bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount, uint16_t scrollbar_X) { - check(event, curr, menuTab, menuTabSize, 0, 0, rowcount, scrollbar_X); + return check(event, curr, menuTab, menuTabSize, 0, 0, rowcount, scrollbar_X); } -void check_submenu_simple(check_event_t event, uint8_t rowcount, uint16_t scrollbar_X) +bool check_submenu_simple(check_event_t event, uint8_t rowcount, uint16_t scrollbar_X) { - check_simple(event, 0, 0, 0, rowcount, scrollbar_X); + return check_simple(event, 0, 0, 0, rowcount, scrollbar_X); } void repeatLastCursorMove(evt_t event) diff --git a/radio/src/gui/Taranis/menus.h b/radio/src/gui/Taranis/menus.h index c47e2d052..3ef146d74 100644 --- a/radio/src/gui/Taranis/menus.h +++ b/radio/src/gui/Taranis/menus.h @@ -249,9 +249,6 @@ void title(const pm_char * s); MENU_TAB(__VA_ARGS__); \ MENU_CHECK_FLAGS(title, tab, menu, flags, lines_count) -#define SIMPLE_MENU_NOTITLE(tab, menu, lines_count) \ - check_simple(NULL, event, menu, tab, DIM(tab), lines_count); - #define SIMPLE_MENU(title, tab, menu, lines_count) \ check_simple(title, event, menu, tab, DIM(tab), lines_count) diff --git a/radio/src/gui/Taranis/navigation.cpp b/radio/src/gui/Taranis/navigation.cpp index 3905fc144..6930f1dbc 100644 --- a/radio/src/gui/Taranis/navigation.cpp +++ b/radio/src/gui/Taranis/navigation.cpp @@ -600,10 +600,6 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP displayScrollbar(scrollbar_X, MENU_HEADER_HEIGHT, LCD_H-MENU_HEADER_HEIGHT, s_pgOfs, linesCount, NUM_BODY_LINES); } - if (name) { - title(name); - } - m_posVert = l_posVert; m_posHorz = l_posHorz; } From 8dc402d11917b67fe950dfd377c1787bb9a56378 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 10 Oct 2015 08:40:34 +0200 Subject: [PATCH 20/74] [Horus] Bitmaps definitions moved to a new file --- radio/src/Makefile | 14 +- ...dex_shadow.png => mask_current_shadow.png} | Bin radio/src/gui/Horus/bitmaps.cpp | 136 ++++++++++++++++++ radio/src/gui/Horus/gui.h | 11 ++ radio/src/gui/Horus/widgets.cpp | 95 +----------- 5 files changed, 151 insertions(+), 105 deletions(-) rename radio/src/bitmaps/Horus/{mask_index_shadow.png => mask_current_shadow.png} (100%) create mode 100644 radio/src/gui/Horus/bitmaps.cpp diff --git a/radio/src/Makefile b/radio/src/Makefile index cca3b777e..ebeb7224a 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -1225,7 +1225,7 @@ ifeq ($(PCB), HORUS) PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp CPPSRC += tasks_arm.cpp audio_arm.cpp sbus.cpp telemetry/telemetry.cpp CPPSRC += targets/Horus/pulses_driver.cpp targets/Horus/keys_driver.cpp targets/Horus/adc_driver.cpp targets/Horus/trainer_driver.cpp targets/Horus/audio_driver.cpp targets/Horus/serial2_driver.cpp targets/Horus/telemetry_driver.cpp - CPPSRC += gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp + CPPSRC += gui/$(GUIDIRECTORY)/bitmaps.cpp gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp SRC += targets/Horus/system_stm32f4xx.c SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/misc.c SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c @@ -1298,18 +1298,6 @@ ifeq ($(PCB), HORUS) CPPSRC += haptic.cpp CPPSRC += targets/Horus/haptic_driver.cpp EXTRABOARDSRC += - - ifeq ($(TW8823_TRACES), YES) - CPPDEFS += -DTW8823_TRACES - endif - - ifeq ($(TW8823_BITMAPS), YES) - CPPDEFS += -DTW8823_BITMAPS - endif - - ifeq ($(TW8823_SERIAL), YES) - CPPDEFS += -DTW8823_SERIAL - endif endif CC = $(TRGT)gcc diff --git a/radio/src/bitmaps/Horus/mask_index_shadow.png b/radio/src/bitmaps/Horus/mask_current_shadow.png similarity index 100% rename from radio/src/bitmaps/Horus/mask_index_shadow.png rename to radio/src/bitmaps/Horus/mask_current_shadow.png diff --git a/radio/src/gui/Horus/bitmaps.cpp b/radio/src/gui/Horus/bitmaps.cpp new file mode 100644 index 000000000..f01425ea8 --- /dev/null +++ b/radio/src/gui/Horus/bitmaps.cpp @@ -0,0 +1,136 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../../opentx.h" + +/* + * Header bitmaps + */ + +const uint8_t LBM_TOP_POLYGON[] = { +#include "../../bitmaps/Horus/mask_top_polygon.lbm" +}; + +const uint8_t LBM_DOT[] = { +#include "../../bitmaps/Horus/mask_dot.lbm" +}; + +const uint8_t LBM_CURRENT_BG[] = { +#include "../../bitmaps/Horus/mask_current_bg.lbm" +}; + +const uint8_t LBM_CURRENT_SHADOW[] = { +#include "../../bitmaps/Horus/mask_current_shadow.lbm" +}; + +const uint8_t LBM_CURRENT_DOT[] = { +#include "../../bitmaps/Horus/mask_current_dot.lbm" +}; + +/* + * Model menu bitmaps + */ + +const uint8_t LBM_MENU_MODEL[] = { +#include "../../bitmaps/Horus/mask_menu_model.lbm" +}; + +const uint8_t LBM_MODEL_SETUP_ICON[] = { +#include "../../bitmaps/Horus/mask_model_setup.lbm" +}; + +const uint8_t LBM_HELI_ICON[] = { +#include "../../bitmaps/Horus/mask_heli.lbm" +}; + +const uint8_t LBM_FLIGHT_MODES_ICON[] = { +#include "../../bitmaps/Horus/mask_flight_modes.lbm" +}; + +const uint8_t LBM_INPUTS_ICON[] = { +#include "../../bitmaps/Horus/mask_inputs.lbm" +}; + +const uint8_t LBM_MIXER_ICON[] = { +#include "../../bitmaps/Horus/mask_mixer.lbm" +}; + +const uint8_t LBM_OUTPUTS_ICON[] = { +#include "../../bitmaps/Horus/mask_outputs.lbm" +}; + +const uint8_t LBM_CURVES_ICON[] = { +#include "../../bitmaps/Horus/mask_curves.lbm" +}; + +const uint8_t LBM_GVARS_ICON[] = { +#include "../../bitmaps/Horus/mask_gvars.lbm" +}; + +const uint8_t LBM_LOGICAL_SWITCHES_ICON[] = { +#include "../../bitmaps/Horus/mask_logical_switches.lbm" +}; + +const uint8_t LBM_SPECIAL_FUNCTIONS_ICON[] = { +#include "../../bitmaps/Horus/mask_special_functions.lbm" +}; + +const uint8_t LBM_LUA_SCRIPTS_ICON[] = { +#include "../../bitmaps/Horus/mask_lua_scripts.lbm" +}; + +const uint8_t LBM_TELEMETRY_ICON[] = { +#include "../../bitmaps/Horus/mask_telemetry.lbm" +}; + +const uint8_t * const LBM_MODEL_ICONS[] = { + LBM_MENU_MODEL, + LBM_MODEL_SETUP_ICON, + CASE_HELI(LBM_HELI_ICON) + CASE_FLIGHT_MODES(LBM_FLIGHT_MODES_ICON) + LBM_INPUTS_ICON, + LBM_MIXER_ICON, + LBM_OUTPUTS_ICON, + CASE_CURVES(LBM_CURVES_ICON) + CASE_GVARS(LBM_GVARS_ICON) + LBM_LOGICAL_SWITCHES_ICON, + LBM_SPECIAL_FUNCTIONS_ICON, +#if defined(LUA_MODEL_SCRIPTS) + LBM_LUA_SCRIPTS_ICON, +#endif + LBM_TELEMETRY_ICON +}; + diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index 3e94f521a..6d72bad81 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -68,3 +68,14 @@ void drawFooter(); void displayProgressBar(const char *label); void updateProgressBar(int num, int den); +// Header bitmaps +extern const uint8_t LBM_TOP_POLYGON[]; +extern const uint8_t LBM_DOT[]; +extern const uint8_t LBM_CURRENT_BG[]; +extern const uint8_t LBM_CURRENT_SHADOW[]; +extern const uint8_t LBM_CURRENT_DOT[]; + +// Menu icons +extern const uint8_t * const LBM_RADIO_ICONS[]; +extern const uint8_t * const LBM_MODEL_ICONS[]; + diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index f1e58f7ee..587699a48 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -116,95 +116,6 @@ void title(const pm_char * s) lcd_putsAtt(MENU_TITLE_LEFT, MENU_TITLE_TOP+2, s, MENU_TITLE_COLOR); } -const uint8_t LBM_MODEL_SETUP_ICON[] = { -#include "../../bitmaps/Horus/mask_model_setup.lbm" -}; - -const uint8_t LBM_HELI_ICON[] = { -#include "../../bitmaps/Horus/mask_heli.lbm" -}; - -const uint8_t LBM_FLIGHT_MODES_ICON[] = { -#include "../../bitmaps/Horus/mask_flight_modes.lbm" -}; - -const uint8_t LBM_INPUTS_ICON[] = { -#include "../../bitmaps/Horus/mask_inputs.lbm" -}; - -const uint8_t LBM_MIXER_ICON[] = { -#include "../../bitmaps/Horus/mask_mixer.lbm" -}; - -const uint8_t LBM_OUTPUTS_ICON[] = { -#include "../../bitmaps/Horus/mask_outputs.lbm" -}; - -const uint8_t LBM_CURVES_ICON[] = { -#include "../../bitmaps/Horus/mask_curves.lbm" -}; - -const uint8_t LBM_GVARS_ICON[] = { -#include "../../bitmaps/Horus/mask_gvars.lbm" -}; - -const uint8_t LBM_LOGICAL_SWITCHES_ICON[] = { -#include "../../bitmaps/Horus/mask_logical_switches.lbm" -}; - -const uint8_t LBM_SPECIAL_FUNCTIONS_ICON[] = { -#include "../../bitmaps/Horus/mask_special_functions.lbm" -}; - -const uint8_t LBM_LUA_SCRIPTS_ICON[] = { -#include "../../bitmaps/Horus/mask_lua_scripts.lbm" -}; - -const uint8_t LBM_TELEMETRY_ICON[] = { -#include "../../bitmaps/Horus/mask_telemetry.lbm" -}; - -const uint8_t * const LBM_MODEL_ICONS[] = { - LBM_MODEL_SETUP_ICON, - CASE_HELI(LBM_HELI_ICON) - CASE_FLIGHT_MODES(LBM_FLIGHT_MODES_ICON) - LBM_INPUTS_ICON, - LBM_MIXER_ICON, - LBM_OUTPUTS_ICON, - CASE_CURVES(LBM_CURVES_ICON) - CASE_GVARS(LBM_GVARS_ICON) - LBM_LOGICAL_SWITCHES_ICON, - LBM_SPECIAL_FUNCTIONS_ICON, -#if defined(LUA_MODEL_SCRIPTS) - LBM_LUA_SCRIPTS_ICON, -#endif - LBM_TELEMETRY_ICON -}; - -const uint8_t LBM_TOP_POLYGON[] = { -#include "../../bitmaps/Horus/mask_top_polygon.lbm" -}; - -const uint8_t LBM_DOT[] = { -#include "../../bitmaps/Horus/mask_dot.lbm" -}; - -const uint8_t LBM_CURRENT_BG[] = { -#include "../../bitmaps/Horus/mask_current_bg.lbm" -}; - -const uint8_t LBM_CURRENT_SHADOW[] = { -#include "../../bitmaps/Horus/mask_index_shadow.lbm" -}; - -const uint8_t LBM_CURRENT_DOT[] = { -#include "../../bitmaps/Horus/mask_current_dot.lbm" -}; - -const uint8_t LBM_MENU_MODEL[] = { -#include "../../bitmaps/Horus/mask_menu_model.lbm" -}; - #define MENU_ICONS_SPACING 34 void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCount) @@ -218,8 +129,6 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun lcdDrawBitmapPattern(0, 0, LBM_TOP_POLYGON, TITLE_BGCOLOR); - lcdDrawBitmapPattern(5, 7, LBM_MENU_MODEL, MENU_TITLE_COLOR); - if (m_posVert < 0) { lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_SHADOW, TEXT_COLOR); @@ -230,8 +139,10 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_DOT, MENU_TITLE_COLOR); } + lcdDrawBitmapPattern(5, 7, LBM_MODEL_ICONS[0], MENU_TITLE_COLOR); + for (int i=0; i Date: Sat, 10 Oct 2015 09:18:42 +0200 Subject: [PATCH 21/74] [Horus] Radio icons added --- radio/src/bitmaps/Horus/mask_analogs.png | Bin 0 -> 800 bytes radio/src/bitmaps/Horus/mask_calibration.png | Bin 0 -> 575 bytes .../src/bitmaps/Horus/mask_global_functions.png | Bin 0 -> 1049 bytes radio/src/bitmaps/Horus/mask_hardware.png | Bin 0 -> 686 bytes radio/src/bitmaps/Horus/mask_menu_radio.png | Bin 0 -> 1194 bytes radio/src/bitmaps/Horus/mask_radio_setup.png | Bin 0 -> 1326 bytes radio/src/bitmaps/Horus/mask_sd_browser.png | Bin 0 -> 1091 bytes radio/src/bitmaps/Horus/mask_switches.png | Bin 0 -> 536 bytes radio/src/bitmaps/Horus/mask_trainer.png | Bin 0 -> 1032 bytes radio/src/bitmaps/Horus/mask_version.png | Bin 0 -> 544 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_analogs.png create mode 100644 radio/src/bitmaps/Horus/mask_calibration.png create mode 100644 radio/src/bitmaps/Horus/mask_global_functions.png create mode 100644 radio/src/bitmaps/Horus/mask_hardware.png create mode 100644 radio/src/bitmaps/Horus/mask_menu_radio.png create mode 100644 radio/src/bitmaps/Horus/mask_radio_setup.png create mode 100644 radio/src/bitmaps/Horus/mask_sd_browser.png create mode 100644 radio/src/bitmaps/Horus/mask_switches.png create mode 100644 radio/src/bitmaps/Horus/mask_trainer.png create mode 100644 radio/src/bitmaps/Horus/mask_version.png diff --git a/radio/src/bitmaps/Horus/mask_analogs.png b/radio/src/bitmaps/Horus/mask_analogs.png new file mode 100644 index 0000000000000000000000000000000000000000..96fc0c8a20b4e9a62be69f815748e0ef6516d99a GIT binary patch literal 800 zcmV+*1K<3KP)rfQO|EbR=C^n^P5k4~oEMz(f# zb_jrjgM$=8)Pq%3B>>*t-7#ttLNFeW698S;B}q1jOOm9>btFkr!?}^E`ta}&i;Ige z3wOTFk>FDUd!@~o*y1IgAilV^tJh-k4MNwLhSO~#wXllH*wM77& zot@?F?QPwj=XqRTUk{?yb^YCpy^Xjj*l`@nvdp2Op-|z?&(9M8kB*L70yk}^)6-LU zo`==d)lku`t*rq7j*pL{`8rva1pvrqv!QJ(URWzz5$;-=2_Vo0yv$K=B zuJii(nk0FDf6sEc%)Y)p0${0B`cU;>0+Zy)$q6SWCW3xVrBak6i2#^LBv>pKL+5#0 zv)c1Klu9L-rU}ckFgiMl>FH^#tgK*UWF)ktU!K+N+NTG|KfB(3*ERd?xdGU((eCap zUSD4$1(wTYJU>5UU|<08c)Y%k^ZER@UvK~Zas}YW$472&Z!?q0L=GGt9_Gfz2CLO- e0G9r{f2ZFC#|1?{IGv#Y0000J%=P)r%Dh-G#IfGF|k5bU3!!5_)D%>HWaGeVv+|z{=E}eo4{M)Rp zuuFp3jTY&RL<9DG{4=BR_zeJP1^Y)U*}Ln)+wDeDN|I7i+qNX7BqAcl7{zf+j4@)2 z(Pp!8bBGz!T7z>AB64~-=g?Zuxy4hk5CUNsf{5UG9?~>LS(d2l8cHcBrBK&3%Cbb7 zrtmxuM1(L5A%s{C9)SZLREk3;pT>XCI_XW>&}r>})%a{5TAE7&MEIsaO4qn)ODvn-ph zN(V3in{&?jG+1kGtJkHFT)|2yTkZF%ZRau#AxRR7qUdEEDvBaXl4QgfbuROAzuzx% zyxndee(U?*`aGY{B&DRo;V=fbZTlne^?IFMV|*IMuM}S{7w4ZdJ|{!H)SiWS(don( zqrUH{X&O2nkIv3b9-mp$G?wuV9LF)8PNxyrC4Vkvz;PT;E52Vem&e_1w-|h{c(n$y ze#>0-kXY?lTrvm*L4Y6#UN;D=I{K_O?ES@f`{sIudG$c=4E}HKP9H>ibq2RO*Tet- N002ovPDHLkV1kLx`3(R7 literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_global_functions.png b/radio/src/bitmaps/Horus/mask_global_functions.png new file mode 100644 index 0000000000000000000000000000000000000000..3b3efefb57dbcce2d55613102b71aca3f5ab8a1f GIT binary patch literal 1049 zcmV+!1m^pRP)E&T)Pw)0oYRSU3x1A!Xq&Nm6zb%7U0EYe`a8vXSza#grrqG7GAw@t z&(APT^T!*z-EPRTZ12E-EE0*ZyStl`BoV-mM{aH|4-O7!XTnFS2L=Yv-`@{W6yfoB zeiW@~nux_>`19uv?(XiOD2jc=<>loBz?GF1CNs;jcz%9vyKWc;3knJdfJ;kDU;4y> zGcz*@fDaE3Nr5*uHVA+>H#fFEfk1!&*wE1MbKseo8RqBbQxF6Kpe)NQE-vQm?CdAs z`}=!}qDYs^#b`A8XpHi#wg`Y%S69?^ou+AinW2e^2?Ah!eZ8|( zkB^U2)3mrWL{VfxK>_FI=lS;b#*U5-0^soQa6&GG!(ob|NL5uCiA0hEx3;!YRaMHe zOaN3>m8z<;udk1WVbJUK((CmyCnqN<5v!}K34p<1&^fs_H#Z4@x~}uPHaR&-0IaR8 zbq1{KI{kh>0dRD5)OO>OlaquW_W1b7l#~=Iio&O-r{utqNQ9cEQPVV5R8-iWVRm*l zOG-+3cXwwCUQ<&;0RNKRw+hGy!>g*Q;sosJ>9O^lnwla2`g}fTI!00aM0E_7K;G@Qd3i%>6j!>PftHZ@S$%2%*e>#?d@&C?0nn<%gf8oQoXdaL`jl3 zH#ZlbJ32a|+wCR*N|MB#ot=cB*Voq+1c7e1o3U6dIq=!p85Kn#0H&m*aCLQ+kw}Dj zd3o_@1A#zNE{u(h5dfQ-nw;ynv$Hb}q9_W>%F5!;ZEbCiNLX50N&sA4U3Cup{{GJP z_V!QRMNv_aqoQ(oc}W2Dcs!r{lakA_EH*Ya#zE85(|LV;?I_xjkr4u5OH0f5?&H_j zSNeQDN|MCw?QO^9vbeaI02m5|zTXJwy3Vz=HOHVA7Z(J;w6ruDhGE|b2!a3ruq^A# zBH(hlP*+#yxWFtfE&>4R>gu4Xs(oLbpP!F#IE+vz1i#<^qb@TVjbeIw8UWDT+?=?t z{?Ob1IW$e9Wm#VyMLgoJt}e*3jG>{Scy0ZO6&4mE7z{!bzrGHH-}-kF@k{m>1qFt{ Twl+i;00000NkvXXu0mjfp9$*6 literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_hardware.png b/radio/src/bitmaps/Horus/mask_hardware.png new file mode 100644 index 0000000000000000000000000000000000000000..f51a1aee1288e069044aaa27ad48617304c48da2 GIT binary patch literal 686 zcmV;f0#W^mP)QE3}TdyYyN+6BcpDNHR;y4|6_O+c24?T-YTtteIn;BYwbcez~dHq}4>R@ZfM9Ova|uj)7s>AKFJ1wr5u^X5_% zh5zk!Wc*|>7@*l~;(R`%*=)kHtUDW7mbq_15d53VFXCh}NsGmT+n-J+DwRri5&Je* zm{dn15duKzbefjSC3j|;X5dtZrp#Wi2h%jc7{hM2!)P?(XYqJExTCPCJ|2%mL^K|c zsoU+|%=pU=Nu<6^PMqkaTlsZ@Mx z{1Sl_3I#M84a8zGB$G+hYBjzT0D$LtsMqV5OeWZFx6cXWH}pTA{Bqfv;Wh*T;C01!oypQTbMh@yySH2V2l=G#*J4K#1v U{-V2`#{d8T07*qoM6N<$f^cjzx&QzG literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_menu_radio.png b/radio/src/bitmaps/Horus/mask_menu_radio.png new file mode 100644 index 0000000000000000000000000000000000000000..6d93fbcccd4f6691ea4281d71e62e75e03364563 GIT binary patch literal 1194 zcmV;b1XcTqP)F*h4cm#8w;_}LTyA;Y*rAgB%r~@ z#v%b_31|~7v{1pI5-r?9j24nVVi01A%^GoDaml_LvPrxb?(+?tX7`-$w=?I=nb{g5 zB7D&v9v%>hM36`%kVqr|0D>Su5Cn90ccZ1H1z$F*V(E06BuOGs6bV4ZA&MeNl0@lr zx|%H2l=E2AH28cztgNgclgR)8EEWsgZZ{kb2b!9i007U=&v<-%#L3ACG))5lSglqp zEG%GpdKwms4NcDudAa~+(^<@E3} z#z)2`zaN=Q z28=Oyz1~VW|3~kv&oi$t-iA1omu~GFh3I>CSL?VXo z3fcu0VzC$jAdAIfn5UbYn`AbdX>f3md_G^)Hsi`S~c6WEv?d>f!G&B&$apZJ54Ym6F z`w0LA0s-O*m%YQ`C<)Ka%?$hsBbb_+!ut9;K0ZEv?v=>|MNu$5KCWM_ zcXxNVxw(NTibZ+_Mk0}b4ldlizgjAl0s!zlkD;LhE-(^_#LvT3&5n+az!-z7syID8Eh>LDn+?%u6irP{=;`Uf<>e(z zrs}esAP9PuF|y9iPJDiTVq#(f0FcdQF*Y`a#l=Mc!06~Gwzs#jw6p{O*xTEK$Kx^7 zDlh~=fT;j}e0(&V{POaG#>Ph73qcTyKJxYT6*il#C<3pquZCI$h9C%)(Mp`}_NJc6LUGhlj**9331SR8A)%(z^>; zmJPui$5Cr*D-8?`P;G6k{;V)+Yir5l@laP+m;Njq4i{<3vaEM0`duwal3ta5wP-Y2 z0{QBQMx#Yqg(@XUQuQK{&*w=Hgx`h=f} z7Y>MvY}_a+Zbgws&(+IUEi{(=_;ezSuW22HD=;W>HZQ?RGmY77GE0wY9Ysiz{Aw;$SccP1D5l z{QNvtR#vdJwS{0Xh`6{o#KryNVs&*@*exzDA`l4NEYxRbXQ;2QM^RA`mY0_S0CRJ5 z0DzvJ9%z~dUDwgk(IJ3!T}NMEA4*G0QBzX`zuzBQsKenf>+9=97|mugTUuI}l9ECI zTwh-ow&UaD1i*Xu?(yNnhlcP!di03De*KCX>Zrh7U0npg)YMcqHa0RoK3+I0EiEbg#c!y)u`yPa;gn|JTtB>FMEZ<->;$ z1iG60(cmYt7BznExh_Cm0y_6)007Tdi4i1=`n;Q|C z)6-L9s53J&34qU^Kj+7fAGx}^Djd69E&`yUC>Ih*Q4|88%jJqFvr|)3Oh`x|0Fhb( z_?-IDYOXBH1VB|)F9cRql>jKqa#VeCaxwvkf+FtNVzB@KPEJn#lQx)`m?*epsMY6$ z@_0NrJv}vSDl9An0DS)Z`9jC%x^r6q2!%qJot?$|_wPls(RJNOuG-pK1F?#Wi@CYE zd1G=N9UY15`+M!~?i%lSX=#Za9UUwzETq|NCICKp@!fCnhEgA04$x3=ap5dc$CQX(#)oUcDj!R#sNtx=GB;%n$%gCKKo9=Pw4fRmGx;`#gc?*zca#6)IiXVYf0 z(Q3640AIX#VHg(o_Vz9%9%5x`Mn*=|Jbw4?T}*yVvHm;l?(QNK3c+kPBOxIHNl8h# kbLWm2?5`^^1OHp{2kZ&tw|FTU$p8QV07*qoM6N<$f?`2npa1{> literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_sd_browser.png b/radio/src/bitmaps/Horus/mask_sd_browser.png new file mode 100644 index 0000000000000000000000000000000000000000..75b6a9296d65f5448d59ca22247ff3dc52e721de GIT binary patch literal 1091 zcmV-J1ibr+P)1JFrC zK~zY`<(ExJYFiY?{~lAb@`h*%7g;qGItPIcp4IUkC&-CWKmv`a41q;sE=YRI^?7i38Yaba& z;t!aBKV%u7HO68wxLhu*tgIjsi9i&^4<3)l6WT;k1WB~Ew!&_=qqMX%HJSggcsx#v z#XfF1_rRc zz7DtB4VTOH^)=qy+ynsh_V&VRwZiA~VR3O0CX)#>Gc%Z+oP@z(fYrnc+6aKl%gemExgh}N=H`+lRVoz$FcOJSuh$a*Pfkvx zDJIF?-CY7;LqkK#sZJQNOeTB(pno@={`26BRA8-Ei}m$&WMup;3j_iXMG+c}21`pz zh{a+s8jUzOIQVt2AP5pzsZ^r5xmkKvUtcd>o6TlyZEeBfa6q9@z+f<-yu2KRg@v%& z?IC+cFIywk|)6>%l z&#tepSyWU+g+jq#F!(iaXJ;n?u(Y(4;c)m?fj*X2a)jU5xQ>|9hYPF`fb7{5Oq=ESI@&W+J$;nBo$;`}zQmKR} ziofq5U@#Z}0Q>vx0?}yn z$9ep|#>dCUDc8p9^%4MUYHE0XelE3(qDYU&^C7S6>gpH@g}xSCKLV5F;NTzuP%f9V zrKN>kU0p0IDu{uG!b*Vfi(wOT2c%L#xwosRwe{ajsLm1_07=jG*b zb93`^a8*@RTHx<38jUg@k4yI#7Z z-;dSRRdjcE!|(S40Q7o&VpLyVUQ(mc{L-=Z-HVEfcz1Uv9hc|k=IC%Zcz=H{O||U% zUzpR=Qw$FenXm2Ih`2-eXZDD66h=m0I zAjw>F38&E{V!Xh415@lWJ3n^!o7q*EnUR92NXz)!Y};nTFqnu~2$8505gUfVwrzVj z_Q9rUGJvdymfQ2~p^0@LX$u+wFFo&1NI(vs5Z^rBY!b z1Owo5xy|)TrNYwYQ$-To>sM5MYGxT$Hih1 z?RGoX+Ly#7gb2*#^Z7qqPN&l;Fn2f{UI%;q`;l!n8;nMytMPC+jI}mY)%*RPrIeA% zmQr$|P+(2dVwY2B1CdfyQxm;*m;8}dv7wk6w|Lc?<*md2+A@_3n zswN_=Rx9*+J#;#qM8Si>z-I*N__x2e{*HBBhvPWt_xo6{*NN`9R;!`g?Lyb}U>$#q as=omCTPLGKtb#@W0000cc zK~zY`<(JJ%a$6L}m&bFO2x($g5Pr-`L<$NDO2~qWoM_afNwWx?)F3EvA_fvc;y(~U zMU+I;ARQm*&t+qth@-oah4250ZJ`R%n?>+EMs0D%60 zG4zLwEj0*$Ha0dW7z|P<6rz`x7iw#3qmGUanwXfNnwpx*$@qswqfzws_JUy;bar;a z;c&p^a$#s_2nvM)4Gj(0-Q7i{Rs>Ealc=w+hhDG8@$s=B`0?=(Hk%DZgg_whbKud@ zQ7Dy4+~42-a*@quLv3v>5{X3R;ERh35D`{aSHE49NF<DzH7)(t~;qC3MbjC87jMC|}=x#`-(?S7=kp;FiHL#{5v*2g>2`9v-H=M9I668i3w?ckh0$n4OG^u~*(}!A z*F}TXYISLFE|)`hcQ-hWgU91RCX*@5@jQ=UFbJ(y3%Oj5^Ye26!1D64Xs|+|C=JfT z-EKFks;a=UEKDX7%w{v>ayf_y0|Nty#bSl{%gamA;J&`T!aBk2#Ih{8TrRr0x+1^d zPg<>(o}QjasZ^3iqalezLJJEEbaHY+L`2Qa&D7M?B&rkac6<3tB5!`bA1up)h@jKy zFg`vGr_+hCv9UtGYBU-Q4i2Kdz5Uy4&1N%ro-cpYJkMi#dKyH8+1Xj#-rfo}wYazl zsZ=WZh;25T;J+>akBp3f<2bCWtO$UaOa>;CNi>j%(Ae0Bo12?o-%f{zhae&x92^J& zQ>heGD%HP&=Dn`24#{M)JlJZr!eX(Y)bZ=}`rm@)-Rt$121gn%*CQYliYR0X9`sr~&SAo<_Do&EyVSk`yw2eMEA0000RhWWLNgj2Z1n|yx*J2WZp<5i3jKj50dQN2qDnzcG2l{uv)DGg(MOQ z6bc2Hrio}Y`eNj(TCElVeCgF_G&~$7?;B?_8BC{B)a!MarU^-s0*koU>!DOCA(>2K zHk$zePT)u+0wDx8n+;;ISfHss9*>Y^*~{?t;tGXAffv%XH6K>hzQK;;z_Kjl^LY%1 z!`olLPki# Date: Sun, 11 Oct 2015 11:30:58 +0200 Subject: [PATCH 22/74] [Horus] Radio icons now displayed --- radio/src/gui/Horus/bitmaps.cpp | 42 +++++++++++++++++++++++++++++++++ radio/src/gui/Horus/widgets.cpp | 6 +++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/radio/src/gui/Horus/bitmaps.cpp b/radio/src/gui/Horus/bitmaps.cpp index f01425ea8..358d292ac 100644 --- a/radio/src/gui/Horus/bitmaps.cpp +++ b/radio/src/gui/Horus/bitmaps.cpp @@ -60,6 +60,48 @@ const uint8_t LBM_CURRENT_DOT[] = { #include "../../bitmaps/Horus/mask_current_dot.lbm" }; +/* + * Radio menu bitmaps + */ + +const uint8_t LBM_MENU_RADIO[] = { +#include "../../bitmaps/Horus/mask_menu_radio.lbm" +}; + +const uint8_t LBM_RADIO_SETUP_ICON[] = { +#include "../../bitmaps/Horus/mask_radio_setup.lbm" +}; + +const uint8_t LBM_SD_BROWSER_ICON[] = { +#include "../../bitmaps/Horus/mask_sd_browser.lbm" +}; + +const uint8_t LBM_GLOBAL_FUNCTIONS_ICON[] = { +#include "../../bitmaps/Horus/mask_global_functions.lbm" +}; + +const uint8_t LBM_TRAINER_ICON[] = { +#include "../../bitmaps/Horus/mask_trainer.lbm" +}; + +const uint8_t LBM_CALIBRATION_ICON[] = { +#include "../../bitmaps/Horus/mask_calibration.lbm" +}; + +const uint8_t LBM_VERSION_ICON[] = { +#include "../../bitmaps/Horus/mask_version.lbm" +}; + +const uint8_t * const LBM_RADIO_ICONS[] = { + LBM_MENU_RADIO, + LBM_RADIO_SETUP_ICON, + LBM_SD_BROWSER_ICON, + LBM_GLOBAL_FUNCTIONS_ICON, + LBM_TRAINER_ICON, + LBM_CALIBRATION_ICON, + LBM_VERSION_ICON +}; + /* * Model menu bitmaps */ diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 587699a48..b0db92533 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -139,10 +139,12 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_DOT, MENU_TITLE_COLOR); } - lcdDrawBitmapPattern(5, 7, LBM_MODEL_ICONS[0], MENU_TITLE_COLOR); + const uint8_t * const * icons = (g_menuPos[0] == 0 ? LBM_MODEL_ICONS : LBM_RADIO_ICONS); + + lcdDrawBitmapPattern(5, 7, icons[0], MENU_TITLE_COLOR); for (int i=0; i Date: Sun, 11 Oct 2015 11:53:40 +0200 Subject: [PATCH 23/74] [Horus] Now a fake main view --- radio/src/bitmaps/Horus/mainview_flat.png | Bin 0 -> 116656 bytes radio/src/gui/Horus/gui.h | 4 ++-- radio/src/gui/Horus/view_main.cpp | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mainview_flat.png diff --git a/radio/src/bitmaps/Horus/mainview_flat.png b/radio/src/bitmaps/Horus/mainview_flat.png new file mode 100644 index 0000000000000000000000000000000000000000..44f8ca652cbf1dc5c3504579a9911bddeaf60679 GIT binary patch literal 116656 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

H#~s5b)4ax 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 literal 0 HcmV?d00001 diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index 6d72bad81..e7ceabd94 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -40,10 +40,10 @@ #define MENU_HEADER_HEIGHT 45 #define MENU_TITLE_TOP 48 #define MENU_TITLE_HEIGHT 21 -#define MENU_BODY_TOP 70 +#define MENU_BODY_TOP 69 // MENU_TITLE_TOP + MENU_TITLE_HEIGHT #define MENU_CONTENT_TOP 72 #define MENU_BODY_HEIGHT 182 -#define MENU_FOOTER_TOP 251 +#define MENU_FOOTER_TOP 251 // MENU_BODY_TOP + MENU_BODY_HEIGHT #define MENU_FOOTER_HEIGHT 21 #define MENU_TITLE_LEFT 6 diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index 411b5b777..faf19e648 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -282,6 +282,10 @@ void onMainViewMenu(const char *result) void displayTelemetryScreen(int index, unsigned int evt); +const uint16_t LBM_MAINVIEW_FLAT[] = { +#include "../../bitmaps/Horus/mainview_flat.lbm" +}; + void menuMainView(evt_t event) { // clear the screen @@ -335,6 +339,8 @@ void menuMainView(evt_t event) break; } + lcdDrawBitmap(0, 0, LBM_MAINVIEW_FLAT); + #if 0 displayMainViewIndex(); @@ -350,7 +356,7 @@ void menuMainView(evt_t event) } #endif -#if defined(GVARS) +#if 0 if (s_gvar_timer > 0) { s_gvar_timer--; displayMessageBox(); From b3979d9fff5e917d82db21b4180198961d2f9bf7 Mon Sep 17 00:00:00 2001 From: martin Date: Sun, 11 Oct 2015 22:50:32 +0200 Subject: [PATCH 24/74] reduced index size and icon spacing to fit all 12 icons --- radio/src/bitmaps/Horus/mask_current_bg.png | Bin 341 -> 335 bytes radio/src/bitmaps/Horus/mask_current_shadow.png | Bin 689 -> 559 bytes radio/src/gui/Horus/widgets.cpp | 13 ++++++++----- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/radio/src/bitmaps/Horus/mask_current_bg.png b/radio/src/bitmaps/Horus/mask_current_bg.png index 75244f36b0d930935ec253340cf20bc127d1e5bb..715792023f36befe2d2569b5985c8c6cf002d278 100644 GIT binary patch delta 268 zcmcc0be>7EGr-TCmrII^fq{Y7)59eQNGk!cF$Wuv{B-u#pNWdT^=CX?977^n-`?EF z+iW1v`mp<>(7gjwxw)2o;CkbHl#7R1h&Shep1$jpXRaz%vjXYE8JuBNs5KxS*6=rQBBn) jQv$M&Zqf-q{*Y68)#a4Nt#iGA9%S%z^>bP0l+XkKsYq%p delta 274 zcmX@lbd^c5Gr-TCmrII^fq{Y7)59eQNUH&{F$Wuv{H<2iHc`>H{*tGQV@O2n+nXCX zTO0&hAKol|>zFJpa9TingQBtaMh@nK9z43wV+$5m9GB)l_u%h_n{PfGY?D62^XszIU=RdDEj+{2L zeE)CF*~@;OJd;|mEmu0oYs2^Y=P!>)Y+Sdx-1d$EgT!$`!(+l)Ee(kh@7C;NXKqN1 ryih88YLiQ#NXBik&o=jCyTy-c&D^!{%Hc`>1o^g$*i(^Pc>)V_5 zt6v#N9RC=f6jiXIUf{^xvySWsUDH3Z{@~iZ(|Q-Hy6hZQbw`oH6|Mr8RMLa^@0@Qw`r;NdNX{~R$PC*Q~G?` z=I8Z=UeixM^}q79>S)^Lldin&hhwH}6?^?v>ROb>QiT?UgNF=i7PbgvY>k@Xw|u96 zxZd<)b9)&+|N09RHg0Rfln*YM!qN1gwQCW_-Z=gLg&b?cRzFv~nn^N_^qd(9~b4g~GMc>3_n&+My@A*C~0Wy+_;oVLH1y4>bHe`w&c92kcT Mp00i_>zopr0A@AZ&j0`b delta 626 zcmZ3_vXND>Gr-TCmrII^fq{Y7)59eQNNWMH83!AX+<2XP_e4dXdL}(j7srr{#<#c5 zdp&kwX!w|~Hn+u7#A~Wh)<&^|lP@^WU!#9{GXKTJ6+%^?0``2l)-*4NmD@GPM0aLI z!PX6@X5Hnx()!a+v0iO|%M%mjPd4WnKiNFL{_L9Zu?Z@iP8>D&kAJWAde7b}z@fP9 zGtrm{paL``=2k^Rvd8Ka>ZMjD`k9H*6ds7Hs3num`h0bpL7Q2gO@KmU%$>? zw{BfUO^uF8iY$R57qhlzY>jdW49Tcr&{U0j{ncd0lt~6W7o)`M z<(}-mWb*XUqeskD1qBW=$5|Xdgg@R>ueEf2nb=v0<|*_4AHSM)^yW>;@87>?tnGX} zBWvrgk9+@mOZzr^YkE#a~~Q<|>La$D@U`|h%!l{G&DT!nwO zy*BPKJoCJ`?XY6=!G@P5UbB|5I07x&ay!@0-`_)X`sv%P`;sTCB%00s_~FBWq>Tz) zO5eVJzj^z%GSA`O3D*{Eh~wB0wf4j&p7WmrydtAlf8q+r+nbg8?fGnAGGg#_^>bP0 Hl+XkKL^VB% diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index b0db92533..3051de66d 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -116,7 +116,7 @@ void title(const pm_char * s) lcd_putsAtt(MENU_TITLE_LEFT, MENU_TITLE_TOP+2, s, MENU_TITLE_COLOR); } -#define MENU_ICONS_SPACING 34 +#define MENU_ICONS_SPACING 31 void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCount) { @@ -130,12 +130,10 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun lcdDrawBitmapPattern(0, 0, LBM_TOP_POLYGON, TITLE_BGCOLOR); if (m_posVert < 0) { - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-12, 0, LBM_CURRENT_SHADOW, TEXT_COLOR); - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-10, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); } else { - lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-11, 0, 36, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); + lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-9, 0, 32, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_DOT, MENU_TITLE_COLOR); } @@ -147,6 +145,11 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun lcdDrawBitmapPattern(50+i*MENU_ICONS_SPACING, 7, icons[i+1], MENU_TITLE_COLOR); } + if (m_posVert < 0) { + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-10, 0, LBM_CURRENT_SHADOW, TEXT_COLOR); + lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); + } + if (name) { // header title(name); From b458b79bed5dadc578a178ededd5ff5fd4705e9a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 13 Oct 2015 23:04:35 +0200 Subject: [PATCH 25/74] [Horus] make clean also cleans the font bitmap files --- radio/src/Makefile | 1 + radio/util/font2png.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/radio/src/Makefile b/radio/src/Makefile index ebeb7224a..71eef86cd 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -2087,6 +2087,7 @@ clean_list : $(REMOVE) bitmaps/*/*.lbm $(REMOVE) bitmaps/*/*/*.lbm $(REMOVE) fonts/*.lbm + $(REMOVE) fonts/font_* $(REMOVE) fonts/*/*.lbm $(REMOVE) lua_exports* lua_fields.txt $(MAKE) -C targets/taranis/bootloader clean diff --git a/radio/util/font2png.py b/radio/util/font2png.py index 86a6ce8bb..73acb86e6 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -11,7 +11,7 @@ except: pass for f in glob.glob("fonts/*.ttf"): - id = QtGui.QFontDatabase.addApplicationFont(f) + QtGui.QFontDatabase.addApplicationFont(f) chars = u""" !"#$%&'()*+,-./0123456789:;<=>?°ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz~ ≥→←↑↓ △Ⓘ""" @@ -40,14 +40,12 @@ def getFontWidth(fontsize, metrics): def createFontBitmap(filename, fontname, fontsize, foreground, background, coordsfile=True): coords = [ ] - # font = QtGui.QFont(QtGui.QFontDatabase.applicationFontFamilies(id)[0]) font = QtGui.QFont(fontname) font.setPixelSize(fontsize) font.setHintingPreference(QtGui.QFont.PreferNoHinting) metrics = QtGui.QFontMetrics(font) width = getFontWidth(fontsize, metrics) top, bottom = getFontTopBottom(fontsize, metrics) - print top, bottom image = QtGui.QImage(width, fontsize+1, QtGui.QImage.Format_RGB32) image.fill(background) painter = QtGui.QPainter() From 76b5143dd40026a886e0c3cd3e4fd759fe173b8e Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 15 Oct 2015 22:21:01 +0200 Subject: [PATCH 26/74] some more Horus graprics --- .../bitmaps/Horus/mask_topmenu_altitude_ico.png | Bin 0 -> 412 bytes .../Horus/mask_topmenu_masterpart_ico.png | Bin 0 -> 785 bytes radio/src/bitmaps/Horus/mask_topmenu_opentx.png | Bin 0 -> 1110 bytes .../bitmaps/Horus/mask_topmenu_slavepart_ico.png | Bin 0 -> 516 bytes radio/src/bitmaps/Horus/mask_topmenu_usb_ico.png | Bin 0 -> 494 bytes .../bitmaps/Horus/mask_topmenu_volume_ico.png | Bin 0 -> 524 bytes radio/src/bitmaps/Horus/mask_trim_bg.png | Bin 0 -> 158 bytes radio/src/bitmaps/Horus/mask_trim_frame_bg.png | Bin 0 -> 440 bytes radio/src/bitmaps/Horus/mask_trim_shadow.png | Bin 0 -> 210 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_topmenu_altitude_ico.png create mode 100644 radio/src/bitmaps/Horus/mask_topmenu_masterpart_ico.png create mode 100644 radio/src/bitmaps/Horus/mask_topmenu_opentx.png create mode 100644 radio/src/bitmaps/Horus/mask_topmenu_slavepart_ico.png create mode 100644 radio/src/bitmaps/Horus/mask_topmenu_usb_ico.png create mode 100644 radio/src/bitmaps/Horus/mask_topmenu_volume_ico.png create mode 100644 radio/src/bitmaps/Horus/mask_trim_bg.png create mode 100644 radio/src/bitmaps/Horus/mask_trim_frame_bg.png create mode 100644 radio/src/bitmaps/Horus/mask_trim_shadow.png diff --git a/radio/src/bitmaps/Horus/mask_topmenu_altitude_ico.png b/radio/src/bitmaps/Horus/mask_topmenu_altitude_ico.png new file mode 100644 index 0000000000000000000000000000000000000000..b51eaf71aea30dbef6a80a4127f278dcc6fee88c GIT binary patch literal 412 zcmV;N0b~A&P)WL5Ph=w8`6ILDoOZy082`-VDPCZ zFq_T(CzoZ3X_|C89Sp-@x7*#j-88622@q`Eyr<;Qi>!=9&Rd%g7J9F<#M@aX_`{2)tJxcXk}T_?RI5297+%b z@(EEC$#%OH@qM30qrvHP`tvo^ literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_topmenu_masterpart_ico.png b/radio/src/bitmaps/Horus/mask_topmenu_masterpart_ico.png new file mode 100644 index 0000000000000000000000000000000000000000..1da37a42edb03adfdf6bdc553af7dd6a1b7c7da9 GIT binary patch literal 785 zcmV+s1Md8ZP)P>c{RbR*OYcN+ndQRTyKC&Sa3zWRRVk!=v6_1TK2zI1USoi+mV~ z&~~286(s z2;UVxZ>*HdKB5cA9ovSKvZIGwZ``qMe`qWg1KG(s0pX2LV=;f|^K=??-R6rrK~w3p zFBCub_GpA?`+c@*BPDygyLoBOy;G=h1iqV@;nC4iI4DWFuFH=fK5#IRpb>%^ zU<6>)vN+J+&ri$CJUKZD1l~X!RVtM@Jv~jW_05A{2&Pgg7K=sJ>-BKZwY4=`mPMr$ z#icE7v;b&~;r#qOZQBlx=JWY~fsvBR3(oa^mPoKtsrbTfpxJD;d2hA@=W;n7A0Gz- zn`pIKy%mf`BHY~E42)etXJ=amqt?3ZDqoI`(RJN$(L$j>c*(pD zKI!XYtyT*aBuR{pjE=8W?uKhsI_S0f>5s6ipWNB$;dLyAdB zDaJ-kvyjNbN@0tLrG!G3SYRR46tY0$H#6CYg`dW7y$j9rI^WFu^}c=6ch_C#xvuMe zuG4*=^PB^cJUBRDeSJMsQc{?ak-?Uh7M`D<(;vOPy)>K6G@H#VC@7%A;o$c6w&3&f z@&xbCjg1YGWMg9^&1N%)hlho}zP_Gjvzgf4-KAcyrzA;iYHDI-WhDVHDk_TC*ViP; z=H_Mspv7XL)oNu_R1_siqRZtXNlKDLoleK*=4O$5dwV2FlgUH?jEIP!+wCSvrlzJ6 z08v#{MF1Qb84-78v)KrMot>Q;^Ocnql4O5>KLK!Xa8Q`bvdm!NH;;~vW>;62##}l& zIsyRH)z!ft{_DfTLwpaX(}{(J1+1>F0szX&$^`fR{tiV^M0z|PO|8S>z|71H&d$yR z7A8p&0Kn(-3DAG7*XuPK3=Itd08&y?(9_d{>gsC2Ns?h z4EzFXYikLBLqkKNRqgHV1i#rlxpzcNfswKWY2N#|Ov9#|ePp;o)C1cRHQ? z@#6;pu)4aMd3kvRz?hgAVf4?L2Q$~{bhKD3BG=Z|0?fT$FVoZ0zl;G&xw*MGJUm2e zYb)GtH%OwZs|&}+$4E*_0sxpyCa9{4*w|PEi;9X0l$V#|?d?sZe{=-}1yEHL03gdU z#>U2=swz}fg^nbH?)X1M7y#Vf-s0rskKnVFeHH)4L&_Rt?3GazB}JuFMqzizWz>1pU=mVk`kdc7z~`7n`2m5 zn7~?ES_0}qVos7bH#dxli3vKQ)YMcyJv{}~NPqQ+|4SH+M$FF62H>(RV}5=f27@7h z3z<0p6c-mmQ4|f9oScm8?ChZWkeQQ2TU#3*A0IWi%jJUIZvSmjNM=4dI;zp<=jUtI zi;If^H6bzI-`}S!%R<}G&_J)(%fiA!fhmf@tE;Qfnm;`~F)b}k=!`}qA08e^ew`nR zq6n_2sEF_H@1ZldTCGB(*Xy~nv!mgcmX+2-RpOvB|o8zjq@&Et;07*qoM6N<$f@nk$N&o-= literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_topmenu_slavepart_ico.png b/radio/src/bitmaps/Horus/mask_topmenu_slavepart_ico.png new file mode 100644 index 0000000000000000000000000000000000000000..db6eb756cc1ad50cd0bf9bb198f6a9932c4de1d4 GIT binary patch literal 516 zcmV+f0{i`mP)yhld;p6+g5XOi zSZsES3 zAokexdR-6d`#vqp3WWmTWHO=ho4T%x^Z6V)-|zR;2WdW^OXro*-EJqHS4K0LjC5WZ z&E<0PdDZD%E|*Lulc6Rzkw|c}*}S$gLI`#`9cHuH(5nJ~y+Izxv*R=OJk2+EEeU^Mx&tw-g~iFghR*UF%84e zg4Sxa-_h-MOWU@!fBe!14`b?|3!Fc@eD z0YC^5J$k)fpQE1VVYOPl1rib{B4A^w#V4W}i{ru_r%||pZFY8NcW1#5a5|m1Uaz@aF4=0e zn8{=~8jXB=6uiM?GC{3YLpq&CBocvTS=es3FijJsQt5RgN#1U^r&DLM8ME0ece|aJ zyI!vx4u`zo@4g#jFc>fzjZ#%r_WOO0uq=zIREn$B>Jzs}l8)o>a=DNsMLZs-<2V3- za5#)+vx(#J2*WT?sZ^k88Uld;gb=^t8BEi}bUKA$7*K-2phpNH5DJA5i^b4rG$5tK z`FuvZ-G;8~C>D#p|4kw~!D>peZuwr%EeInL*EUn^|erj+u_ zW0G$l@$H|cXR|Rilwq(Cn?3uLdt@&Vt#;?lHzaJ zVQrOPCFIq{8izVBSC_qltnxA%D;i6jBQvMlJjj>%*K zAp|y?4FZ7x;_)~XMM0%ff#2_kjYN|3`5d)c4VGm=(=;R!2}Gk&?Du;JAuydzF&>Z6 zXf#l*Rv}3eV45a_!62K>CLfOnzr!#LhQnbFheML2M3VRW9d5T9|Khn%vS+k8pU=>B z9fn~b5{aN#EFu<*IWGBlE^fCQ3WWmt{XUY(Bo2oI(&;pW5DwAxT$rW_RaMbywIItf z06Y?B85VsUyGWiLCWXzT&-4Ux7&aA^8@vI9hb`mi^T%i?RNC}eC%{O z-|mnk2ZI4+S>|%NBuRoKd%YgLUN5)X?OSQRUen|8u-ol^Wv~yqT#n^(`K_4AWLPSd zUb3$}s;Y|P@%VPO)9Hk4Hv5vf{%)AfW)9IbO-J@3jz%M-QmG$**5(r@f;r3C^TmDu O00002?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4XtZ+z_W{I6*4d!Imaj)+IdMFsEonW)z4*}Q$iB}EBh<^ literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_trim_frame_bg.png b/radio/src/bitmaps/Horus/mask_trim_frame_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..bf0f329f00bd001819ffb7011a648c98bf42f428 GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0vp^+(5jDgAGVd-){~i8CZ&)JVQ8upoSx*1IXtr@Q5r1 zs=p4xj7}P}D}aKMC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NHH)lntHl8 zhD5Z!y}2>#P=ZL?!}k*H0Sld4-ImW-xFR^)@#USP8-wrM)_xh9V$}WiQnrBR7rv&2 zUPlu?@Xe7)+Q)Zo=@QMGGUvGee=D%~^O~`qtGU7duwwo!-?(X~_iW3x+og9{!9u7> zCBAmP?&(GED`VURFPl8I>UvQv7d`Fh^ouVVrgyVi_p+H6o>^m7c&2Rcxk`yk9@}m| zXTN^*Z$iVGbE`w=glbht_c!kLYoEV4^4XQ-i$y2bJr*z(RP&tF4K$1OfRLv|+r*uR z3R+Y7vM^;a zGTj{Ce%I-G2comSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt=wZE{-7< zy>HKK=72B1~6O_F7|2%i~Zf|0?(fhp%SM6Bt5qfsQ x_GXzU@h6xVCMj4tD>2QqbiO02^oi5J*hXfzxVe8yqc_lK22WQ%mvv4FO#l|~LDB#K literal 0 HcmV?d00001 From 719ecc847305d597537da2b1d0a0f81e1eaf695a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 16 Oct 2015 17:26:21 +0200 Subject: [PATCH 27/74] [Horus] A lot of GUI fixes --- radio/src/fonts/extra_17px.png | Bin 0 -> 4792 bytes radio/src/gui/9X/gui.h | 2 +- radio/src/gui/9X/popups.cpp | 2 +- radio/src/gui/9X/view_text.cpp | 2 +- radio/src/gui/9X/widgets.cpp | 2 +- radio/src/gui/Horus/gui.h | 10 +- radio/src/gui/Horus/helpers.cpp | 142 ++++++++++++++---- radio/src/gui/Horus/lcd.cpp | 89 ++++++----- radio/src/gui/Horus/lcd.h | 14 +- radio/src/gui/Horus/menu_general_calib.cpp | 12 +- .../src/gui/Horus/menu_general_sdmanager.cpp | 4 +- radio/src/gui/Horus/menu_general_setup.cpp | 38 ++--- radio/src/gui/Horus/menu_general_trainer.cpp | 9 +- radio/src/gui/Horus/menu_model.cpp | 13 +- radio/src/gui/Horus/menu_model_curves.cpp | 36 ++--- .../gui/Horus/menu_model_custom_functions.cpp | 10 +- .../src/gui/Horus/menu_model_flightmodes.cpp | 21 ++- radio/src/gui/Horus/menu_model_gvars.cpp | 6 +- radio/src/gui/Horus/menu_model_heli.cpp | 6 +- .../src/gui/Horus/menu_model_inputs_mixes.cpp | 129 ++++++++-------- radio/src/gui/Horus/menu_model_limits.cpp | 20 +-- .../gui/Horus/menu_model_logical_switches.cpp | 14 +- radio/src/gui/Horus/menu_model_select.cpp | 8 +- radio/src/gui/Horus/menu_model_setup.cpp | 2 +- radio/src/gui/Horus/menu_model_telemetry.cpp | 28 ++-- radio/src/gui/Horus/menus.h | 16 +- radio/src/gui/Horus/navigation.cpp | 24 ++- radio/src/gui/Horus/popups.cpp | 9 +- radio/src/gui/Horus/view_main.cpp | 3 +- radio/src/gui/Horus/view_text.cpp | 4 +- radio/src/gui/Horus/widgets.cpp | 39 +++-- radio/src/gui/Taranis/gui.h | 2 +- radio/src/gui/Taranis/helpers.cpp | 35 ++++- radio/src/gui/Taranis/navigation.cpp | 2 +- radio/src/gui/Taranis/popups.cpp | 2 +- radio/src/gui/Taranis/view_text.cpp | 2 +- radio/src/gui/Taranis/widgets.cpp | 2 +- radio/src/myeeprom.h | 2 +- radio/src/opentx.h | 25 +-- radio/src/stamp.cpp | 14 +- radio/src/switches.cpp | 6 +- radio/src/translations/en.h.txt | 6 +- radio/util/font2png.py | 18 ++- 43 files changed, 498 insertions(+), 332 deletions(-) create mode 100644 radio/src/fonts/extra_17px.png diff --git a/radio/src/fonts/extra_17px.png b/radio/src/fonts/extra_17px.png new file mode 100644 index 0000000000000000000000000000000000000000..dea8be4c7319209d98545f2cfe6e78451aaa4aa3 GIT binary patch literal 4792 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 display_count) { - displayScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); + lcdDrawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); } switch(event) { diff --git a/radio/src/gui/9X/view_text.cpp b/radio/src/gui/9X/view_text.cpp index 4c5af8948..8a58dfa89 100644 --- a/radio/src/gui/9X/view_text.cpp +++ b/radio/src/gui/9X/view_text.cpp @@ -151,7 +151,7 @@ void menuTextView(uint8_t event) lcd_invert_line(0); if (lines_count > LCD_LINES-1) { - displayScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); + lcdDrawScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); } } diff --git a/radio/src/gui/9X/widgets.cpp b/radio/src/gui/9X/widgets.cpp index 5d1b09aa7..d4805cfd7 100644 --- a/radio/src/gui/9X/widgets.cpp +++ b/radio/src/gui/9X/widgets.cpp @@ -72,7 +72,7 @@ void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr) } #if !defined(CPUM64) -void displayScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) +void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { lcd_vlineStip(x, y, h, DOTTED); coord_t yofs = (h * offset) / count; diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index e7ceabd94..b2273456d 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -46,7 +46,7 @@ #define MENU_FOOTER_TOP 251 // MENU_BODY_TOP + MENU_BODY_HEIGHT #define MENU_FOOTER_HEIGHT 21 -#define MENU_TITLE_LEFT 6 +#define MENUS_MARGIN_LEFT 6 #define DEFAULT_SCROLLBAR_X LCD_W-10 #define DEFAULT_SCROLLBAR_Y MENU_CONTENT_TOP @@ -59,9 +59,15 @@ #define INVERT_VERT_MARGIN 1 #define INVERT_LINE_HEIGHT 18 +#define CURVE_SIDE_WIDTH 88 +#define CURVE_CENTER_X (LCD_W-CURVE_SIDE_WIDTH-10) +#define CURVE_CENTER_Y 159 + +#define MENU_TITLE_NEXT_POS (lcdNextPos + 10) + void displaySplash(); void displayScreenIndex(uint8_t index, uint8_t count); -void displayScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); +void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); void drawHeader(int index); void drawFooter(); #define drawStatusLine(...) diff --git a/radio/src/gui/Horus/helpers.cpp b/radio/src/gui/Horus/helpers.cpp index 30eb04045..f63f00db2 100644 --- a/radio/src/gui/Horus/helpers.cpp +++ b/radio/src/gui/Horus/helpers.cpp @@ -38,14 +38,14 @@ uint8_t switchToMix(uint8_t source) { - #warning en double ! - return 0; + div_t qr = div(source-1, 3); + return qr.quot+MIXSRC_FIRST_SWITCH; } -int circularIncDec(int current, int incr, int min, int max, IsValueAvailable isValueAvailable) +int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable) { do { - current += incr; + current += inc; if (current < min) current = max; else if (current > max) @@ -111,6 +111,10 @@ bool isSourceAvailable(int source) return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); } + if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) { + return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); + } + #if !defined(HELI) if (source>=MIXSRC_CYC1 && source<=MIXSRC_CYC3) return false; @@ -144,6 +148,14 @@ bool isSourceAvailable(int source) return true; } +bool isSourceAvailableInGlobalFunctions(int source) +{ + if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + return false; + } + return isSourceAvailable(source); +} + bool isSourceAvailableInCustomSwitches(int source) { bool result = isSourceAvailable(source); @@ -160,23 +172,29 @@ bool isSourceAvailableInCustomSwitches(int source) bool isInputSourceAvailable(int source) { - if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) - return true; + if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { + return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); + } if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) return true; - if (source>=MIXSRC_TrimRud && source=MIXSRC_FIRST_TRIM && source<=MIXSRC_LAST_TRIM) return true; + if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) + return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); + if (source>=MIXSRC_FIRST_CH && source<=MIXSRC_LAST_CH) return true; if (source>=MIXSRC_FIRST_TRAINER && source<=MIXSRC_LAST_TRAINER) return true; - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) - return isTelemetryFieldAvailable(source-MIXSRC_FIRST_TELEM); + if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); + return isTelemetryFieldAvailable(qr.quot) && isTelemetryFieldComparisonAvailable(qr.quot); + } return false; } @@ -190,30 +208,59 @@ enum SwitchContext MixesContext }; +bool isLogicalSwitchAvailable(int index) +{ + LogicalSwitchData * lsw = lswAddress(index); + return (lsw->func != LS_FUNC_NONE); +} + bool isSwitchAvailable(int swtch, SwitchContext context) { - uint32_t index = switchInfo(abs(swtch)).quot; + bool negative = false; if (swtch < 0) { - if (!IS_3POS(index)) - return false; + negative = true; if (swtch == -SWSRC_ON || swtch == -SWSRC_ONE) { return false; } swtch = -swtch; } - if (swtch <= SWSRC_FIRST_SWITCH) { + if (swtch >= SWSRC_SA0 && swtch <= SWSRC_LAST_SWITCH) { + div_t swinfo = switchInfo(swtch); + if (!SWITCH_EXISTS(swinfo.quot)) { + return false; + } + if (!IS_3POS(swinfo.quot)) { + if (negative) { + return false; + } + if (IS_3POS_MIDDLE(swinfo.rem)) { + return false; + } + } return true; } +#if NUM_XPOTS > 0 + if (swtch >= SWSRC_FIRST_MULTIPOS_SWITCH && swtch <= SWSRC_LAST_MULTIPOS_SWITCH) { + int index = (swtch - SWSRC_FIRST_MULTIPOS_SWITCH) / XPOTS_MULTIPOS_COUNT; + if (IS_POT_MULTIPOS(POT1+index)) { + StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[POT1+index]; + return (calib->count >= ((swtch - SWSRC_FIRST_MULTIPOS_SWITCH) % XPOTS_MULTIPOS_COUNT)); + } + else { + return false; + } + } +#endif + if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH && swtch <= SWSRC_LAST_LOGICAL_SWITCH) { if (context == GeneralCustomFunctionsContext) { return false; } else if (context != LogicalSwitchesContext) { - LogicalSwitchData * cs = lswAddress(swtch-SWSRC_FIRST_LOGICAL_SWITCH); - return (cs->func != LS_FUNC_NONE); + return isLogicalSwitchAvailable(swtch - SWSRC_FIRST_LOGICAL_SWITCH); } } @@ -235,6 +282,10 @@ bool isSwitchAvailable(int swtch, SwitchContext context) } } + if (swtch >= SWSRC_FIRST_SENSOR && swtch <= SWSRC_LAST_SENSOR) { + return isTelemetryFieldAvailable(swtch - SWSRC_FIRST_SENSOR); + } + return true; } @@ -274,6 +325,14 @@ bool isSwitchAvailableInTimers(int swtch) return isSwitchAvailable(swtch, TimersContext); } +bool isThrottleSourceAvailable(int source) +{ + if (source >= THROTTLE_SOURCE_FIRST_POT && source < THROTTLE_SOURCE_FIRST_POT+NUM_POTS && !IS_POT_AVAILABLE(POT1+source-THROTTLE_SOURCE_FIRST_POT)) + return false; + else + return true; +} + bool isLogicalSwitchFunctionAvailable(int function) { return function != LS_FUNC_RANGE; @@ -301,15 +360,14 @@ bool isAssignableFunctionAvailable(int function) #if !defined(HAPTIC) case FUNC_HAPTIC: #endif + case FUNC_RESERVE4: #if !defined(DANGEROUS_MODULE_FUNCTIONS) case FUNC_RANGECHECK: case FUNC_BIND: -// case FUNC_MODULE_OFF: #endif #if !defined(LUA) case FUNC_PLAY_SCRIPT: #endif - case FUNC_RESERVE4: case FUNC_RESERVE5: return false; @@ -318,19 +376,39 @@ bool isAssignableFunctionAvailable(int function) } } -bool isModuleAvailable(int module) +bool isSourceAvailableInGlobalResetSpecialFunction(int index) { - if (module == MODULE_TYPE_NONE) + if (index >= FUNC_RESET_PARAM_FIRST_TELEM) return false; - -#if !defined(PXX) - if (module == MODULE_TYPE_XJT) - return false; -#endif - - return true; + else + return isSourceAvailableInResetSpecialFunction(index); } +bool isSourceAvailableInResetSpecialFunction(int index) +{ + if (index >= FUNC_RESET_PARAM_FIRST_TELEM) { + TelemetrySensor & telemetrySensor = g_model.telemetrySensors[index-FUNC_RESET_PARAM_FIRST_TELEM]; + return telemetrySensor.isAvailable(); + } +#if TIMERS < 3 + else if (index == FUNC_RESET_TIMER3) { + return false; + } +#endif +#if TIMERS < 2 + else if (index == FUNC_RESET_TIMER2) { + return false; + } +#endif + else { + return true; + } +} + +bool isModuleAvailable(int module) +{ + return true; +} bool modelHasNotes() { @@ -339,3 +417,15 @@ bool modelHasNotes() strcpy(buf, TEXT_EXT); return isFileAvailable(filename); } + +int getFirstAvailable(int min, int max, bool (*func)(int)) +{ + int retval = 0; + for (int i = min; i <= max; i++) { + if ((*func)(i)) { + retval = i; + break; + } + } + return retval; +} diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index b982f1d84..8358709b1 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -59,11 +59,12 @@ void lcdColorsInit() lcdColorTable[WARNING_COLOR_INDEX] = YELLOW; lcdColorTable[TEXT_DISABLE_COLOR_INDEX] = RGB(0x60, 0x60, 0x60); lcdColorTable[CURVE_AXIS_COLOR_INDEX] = BLUE; + lcdColorTable[CURVE_COLOR_INDEX] = BLACK; + lcdColorTable[CURVE_CURSOR_COLOR_INDEX] = RED; lcdColorTable[TITLE_BGCOLOR_INDEX] = RED; lcdColorTable[HEADER_BGCOLOR_INDEX] = DARKRED; } -coord_t lcdLastPos; coord_t lcdNextPos; #define FONT_CHARS_COUNT 103 @@ -174,14 +175,16 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags const pm_uchar * font = fontsTable[fontindex]; const uint16_t * fontspecs = fontspecsTable[fontindex]; - if ((flags&INVERS) && ((~flags & BLINK) || BLINK_ON_PHASE) && (COLOR_IDX(flags) == TEXT_COLOR_INDEX)) { - flags += TEXT_INVERTED_COLOR - TEXT_COLOR; - if (FONTSIZE(flags) == TINSIZE) - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); - else if (FONTSIZE(flags) == SMLSIZE) - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); - else - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + if ((flags&INVERS) && ((~flags & BLINK) || BLINK_ON_PHASE)) { + if (COLOR_IDX(flags) == TEXT_COLOR_INDEX) { + flags += TEXT_INVERTED_COLOR - TEXT_COLOR; + } + if (FONTSIZE(flags) == TINSIZE) + lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); + else if (FONTSIZE(flags) == SMLSIZE) + lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + else + lcdDrawFilledRect(x-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } char str[256]; @@ -224,7 +227,6 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags } s++; } - lcdLastPos = x; lcdNextPos = x; } @@ -245,7 +247,7 @@ void lcd_puts(coord_t x, coord_t y, const pm_char * s) void lcd_putsLeft(coord_t y, const pm_char * s) { - lcd_puts(MENU_TITLE_LEFT, y, s); + lcd_puts(MENUS_MARGIN_LEFT, y, s); } void lcd_putsCenter(coord_t y, const pm_char * s, LcdFlags attr) @@ -486,7 +488,7 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) lcd_putsiAtt(x, y, STR_VSRCRAW, 0, att); // TODO macro } else if (idx <= MIXSRC_LAST_INPUT) { - char s[32] = "\307"; + char s[32] = "\323"; if (ZEXIST(g_model.inputNames[idx-MIXSRC_FIRST_INPUT])) { zchar2str(s+1, g_model.inputNames[idx-MIXSRC_FIRST_INPUT], LEN_INPUT_NAME); s[1+LEN_INPUT_NAME] = '\0'; @@ -537,8 +539,8 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) putsStrIdx(x, y, STR_CH, idx-MIXSRC_CH1+1, att); #if 0 if (ZEXIST(g_model.limitData[idx-MIXSRC_CH1].name) && (att & STREXPANDED)) { - lcd_putcAtt(lcdLastPos, y, ' ', att); - lcd_putsnAtt(lcdLastPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att); + lcd_putcAtt(lcdNextPos, y, ' ', att); + lcd_putsnAtt(lcdNextPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att); } #endif } @@ -577,36 +579,55 @@ void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) { - if (idx == SWSRC_OFF) + if (idx == SWSRC_NONE) { + return lcd_putsiAtt(x, y, STR_VSWITCHES, 0, att); + } + else if (idx == SWSRC_OFF) { return lcd_putsiAtt(x, y, STR_OFFON, 0, att); + } char s[8]; int pos = 0; if (idx < 0) { s[pos++] = '!'; + idx = -idx; } - int absidx = abs(idx); - -#if defined(FLIGHT_MODES) - if (absidx >= SWSRC_FIRST_FLIGHT_MODE) { - return putsStrIdx(x, y, STR_FP, absidx-SWSRC_FIRST_FLIGHT_MODE, att, idx < 0 ? "!" : ""); - } - else -#endif - if (absidx >= SWSRC_FIRST_SWITCH && absidx <= SWSRC_LAST_SWITCH) { - div_t swinfo = switchInfo(absidx); + if (idx <= SWSRC_LAST_SWITCH) { + div_t swinfo = switchInfo(idx); if (ZEXIST(g_eeGeneral.switchNames[swinfo.quot])) { pos = zchar2str(&s[pos], g_eeGeneral.switchNames[swinfo.quot], LEN_SWITCH_NAME); - s[pos++] = "\300-\301"[swinfo.rem]; - s[pos] = '\0'; - lcd_putsAtt(x, y, s, att); - return; } + else { + s[pos++] = 'S'; + s[pos++] = 'A'+swinfo.quot; + } + s[pos++] = "\300-\301"[swinfo.rem]; + s[pos] = '\0'; + lcd_putsAtt(x, y, s, att); + } + /*else if (idx <= SWSRC_LAST_MULTIPOS_SWITCH) { + div_t swinfo = div(idx - SWSRC_FIRST_MULTIPOS_SWITCH, XPOTS_MULTIPOS_COUNT); + putsStrIdx(x, y, "S", swinfo.quot*10+swinfo.rem+11, att); + }*/ + else if (idx <= SWSRC_LAST_TRIM) { + lcd_putsiAtt(x, y, STR_VSWITCHES, idx-SWSRC_FIRST_TRIM+1, att); + } + else if (idx <= SWSRC_LAST_LOGICAL_SWITCH) { + putsStrIdx(x, y, "L", idx-SWSRC_FIRST_LOGICAL_SWITCH+1, att); + } + else if (idx <= SWSRC_ONE) { + lcd_putsiAtt(x, y, STR_VSWITCHES, idx-SWSRC_ON+1+(2*NUM_STICKS), att); + } + else if (idx <= SWSRC_LAST_FLIGHT_MODE) { + putsStrIdx(x, y, STR_FP, idx-SWSRC_FIRST_FLIGHT_MODE, att); + } + else if (idx == SWSRC_TELEMETRY_STREAMING) { + lcd_putsAtt(x, y, "Tele", att); + } + else { + lcd_putsnAtt(x, y, g_model.telemetrySensors[idx-SWSRC_FIRST_SENSOR].label, TELEM_LABEL_LEN, ZCHAR|att); } - - strAppend(&s[pos], STR_VSWITCHES+1+absidx*STR_VSWITCHES[0], STR_VSWITCHES[0]); - lcd_putsAtt(x, y, s, att); } #if defined(FLIGHT_MODES) @@ -627,12 +648,12 @@ void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att) switch (curve.type) { case CURVE_REF_DIFF: lcd_putsAtt(x, y, "D", att); - GVAR_MENU_ITEM(x+8, y, curve.value, -100, 100, LEFT|att, 0, 0); + GVAR_MENU_ITEM(lcdNextPos, y, curve.value, -100, 100, LEFT|att, 0, 0); break; case CURVE_REF_EXPO: lcd_putsAtt(x, y, "E", att); - GVAR_MENU_ITEM(x+8, y, curve.value, -100, 100, LEFT|att, 0, 0); + GVAR_MENU_ITEM(lcdNextPos, y, curve.value, -100, 100, LEFT|att, 0, 0); break; case CURVE_REF_FUNC: diff --git a/radio/src/gui/Horus/lcd.h b/radio/src/gui/Horus/lcd.h index 71b833bb9..c70cd3708 100644 --- a/radio/src/gui/Horus/lcd.h +++ b/radio/src/gui/Horus/lcd.h @@ -40,7 +40,7 @@ #define LCD_W 480 #define LCD_H 272 -#define BOX_WIDTH 61 +#define BOX_WIDTH 81 #define coord_t int #define scoord_t int #define CENTER @@ -55,10 +55,10 @@ #define LCD_COLS 30 #define BSS 0x00 -#define ERASE 0x00 -#define FORCE 0x00 +// #define ERASE 0x00 +// #define FORCE 0x00 #define FIXEDWIDTH 0x00 -#define FILLWHITE 0x00 +// #define FILLWHITE 0x00 #define BOLD 0x00 /* lcd common flags */ @@ -128,6 +128,8 @@ enum LcdColorIndex WARNING_COLOR_INDEX, TEXT_DISABLE_COLOR_INDEX, CURVE_AXIS_COLOR_INDEX, + CURVE_COLOR_INDEX, + CURVE_CURSOR_COLOR_INDEX, HEADER_BGCOLOR_INDEX, TITLE_BGCOLOR_INDEX, LCD_COLOR_COUNT @@ -153,6 +155,8 @@ void lcdColorsInit(); #define WARNING_COLOR COLOR(WARNING_COLOR_INDEX) #define TEXT_DISABLE_COLOR COLOR(TEXT_DISABLE_COLOR_INDEX) #define CURVE_AXIS_COLOR COLOR(CURVE_AXIS_COLOR_INDEX) +#define CURVE_COLOR COLOR(CURVE_COLOR_INDEX) +#define CURVE_CURSOR_COLOR COLOR(CURVE_CURSOR_COLOR_INDEX) #define TITLE_BGCOLOR COLOR(TITLE_BGCOLOR_INDEX) #define HEADER_BGCOLOR COLOR(HEADER_BGCOLOR_INDEX) @@ -182,6 +186,8 @@ extern uint32_t CurrentFrameBuffer; #define DISPLAY_END (displayBuf + DISPLAY_BUF_SIZE) #define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) +extern coord_t lcdNextPos; + void lcdStartScreen(); void lcd_putc(coord_t x, coord_t y, const unsigned char c); void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags attr=TEXT_COLOR); diff --git a/radio/src/gui/Horus/menu_general_calib.cpp b/radio/src/gui/Horus/menu_general_calib.cpp index 0b491d187..f3d988c3a 100644 --- a/radio/src/gui/Horus/menu_general_calib.cpp +++ b/radio/src/gui/Horus/menu_general_calib.cpp @@ -99,14 +99,14 @@ void menuCommonCalib(evt_t event) case 0: // START CALIBRATION if (!READ_ONLY()) { - lcd_putsCenter(MENU_HEADER_HEIGHT+FH, STR_MENUTOSTART); + lcd_putsCenter(MENU_CONTENT_TOP+FH, STR_MENUTOSTART); } break; case 1: // SET MIDPOINT - lcd_putsCenter(MENU_HEADER_HEIGHT+FH, STR_SETMIDPOINT, INVERS); - lcd_putsCenter(MENU_HEADER_HEIGHT+2*FH, STR_MENUWHENDONE); + lcd_putsCenter(MENU_CONTENT_TOP+FH, STR_SETMIDPOINT, INVERS); + lcd_putsCenter(MENU_CONTENT_TOP+2*FH, STR_MENUWHENDONE); for (int i=0; i 50) { @@ -196,6 +196,8 @@ void menuGeneralCalib(evt_t event) { SIMPLE_MENU(STR_MENUCALIBRATION, menuTabGeneral, e_Calib, 0, DEFAULT_SCROLLBAR_X); + m_posVert = -1; + if (menuEvent) { calibrationState = 0; } diff --git a/radio/src/gui/Horus/menu_general_sdmanager.cpp b/radio/src/gui/Horus/menu_general_sdmanager.cpp index 24890cf56..c9bb5e189 100644 --- a/radio/src/gui/Horus/menu_general_sdmanager.cpp +++ b/radio/src/gui/Horus/menu_general_sdmanager.cpp @@ -348,10 +348,10 @@ void menuGeneralSdManager(evt_t _event) if (!reusableBuffer.sdmanager.lines[i][SD_SCREEN_FILE_LENGTH+1]) { char s[SD_SCREEN_FILE_LENGTH+1+2]; sprintf(s, "[%s]", reusableBuffer.sdmanager.lines[i]); - lcd_putsAtt(MENU_TITLE_LEFT, y, s, attr); + lcd_putsAtt(MENUS_MARGIN_LEFT, y, s, attr); } else { - lcd_putsAtt(MENU_TITLE_LEFT, y, reusableBuffer.sdmanager.lines[i], attr); + lcd_putsAtt(MENUS_MARGIN_LEFT, y, reusableBuffer.sdmanager.lines[i], attr); } } } diff --git a/radio/src/gui/Horus/menu_general_setup.cpp b/radio/src/gui/Horus/menu_general_setup.cpp index 55eb53156..3a3981b43 100644 --- a/radio/src/gui/Horus/menu_general_setup.cpp +++ b/radio/src/gui/Horus/menu_general_setup.cpp @@ -37,6 +37,14 @@ #include "../../opentx.h" #define RADIO_SETUP_2ND_COLUMN 220 +#define YEAR_SEPARATOR_OFFSET 42 +#define MONTH_OFFSET 55 +#define MONTH_SEPARATOR_OFFSET 79 +#define DAY_OFFSET 91 +#define HOUR_SEPARATOR_OFFSET 26 +#define MINUTE_OFFSET 36 +#define MINUTE_SEPARATOR_OFFSET 63 +#define SECOND_OFFSET 75 #define SLIDER_5POS(y, value, label, event, attr) { \ int8_t tmp = value; \ @@ -92,7 +100,6 @@ enum menuGeneralSetupItems { // CASE_MAVLINK(ITEM_MAVLINK_BAUD) ITEM_SETUP_SWITCHES_DELAY, ITEM_SETUP_RX_CHANNEL_ORD, - ITEM_SETUP_STICK_MODE_LABELS, ITEM_SETUP_STICK_MODE, ITEM_SETUP_MAX }; @@ -119,7 +126,7 @@ void menuGeneralSetup(evt_t event) } #endif - MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 2|NAVIGATION_LINE_BY_LINE, 2|NAVIGATION_LINE_BY_LINE, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO_CPUARM(LABEL(VARIO)) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) LABEL(ALARMS), 0, 0, 0, CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, CASE_MAVLINK(0) 0, 0, LABEL(TX_MODE), 0, 1/*to force edit mode*/ }); + MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 2|NAVIGATION_LINE_BY_LINE, 2|NAVIGATION_LINE_BY_LINE, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO_CPUARM(LABEL(VARIO)) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) LABEL(ALARMS), 0, 0, 0, CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, CASE_MAVLINK(0) 0, 0, LABEL(TX_MODE), 1/*to force edit mode*/ }); int sub = m_posVert; @@ -138,8 +145,8 @@ void menuGeneralSetup(evt_t event) flags |= INVERS; lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+28, y, "-", flags); - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+53, y, "-", flags); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+YEAR_SEPARATOR_OFFSET, y, "-", flags); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+MONTH_SEPARATOR_OFFSET, y, "-", flags); for (uint8_t j=0; j<3; j++) { uint8_t rowattr = (m_posHorz==j ? attr : 0); switch (j) { @@ -148,7 +155,7 @@ void menuGeneralSetup(evt_t event) if (rowattr && s_editMode>0) t.tm_year = checkIncDec(event, t.tm_year, 112, 200, 0); break; case 1: - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+36, y, t.tm_mon+1, LEFT|flags|rowattr|LEADING0, 2); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+MONTH_OFFSET, y, t.tm_mon+1, LEFT|flags|rowattr|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_mon = checkIncDec(event, t.tm_mon, 0, 11, 0); break; case 2: @@ -157,7 +164,7 @@ void menuGeneralSetup(evt_t event) int8_t dlim = (((((year%4==0) && (year%100!=0)) || (year%400==0)) && (t.tm_mon==1)) ? 1 : 0); static const pm_uint8_t dmon[] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; dlim += pgm_read_byte(&dmon[t.tm_mon]); - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+61, y, t.tm_mday, LEFT|flags|rowattr|LEADING0, 2); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+DAY_OFFSET, y, t.tm_mday, LEFT|flags|rowattr|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_mday = checkIncDec(event, t.tm_mday, 1, dlim, 0); break; } @@ -177,8 +184,8 @@ void menuGeneralSetup(evt_t event) flags |= INVERS; lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+15, y, ":", flags); - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+35, y, ":", flags); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+HOUR_SEPARATOR_OFFSET, y, ":", flags); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+MINUTE_SEPARATOR_OFFSET, y, ":", flags); for (uint8_t j=0; j<3; j++) { uint8_t rowattr = (m_posHorz==j ? attr : 0); switch (j) { @@ -187,11 +194,11 @@ void menuGeneralSetup(evt_t event) if (rowattr && s_editMode>0) t.tm_hour = checkIncDec(event, t.tm_hour, 0, 23, 0); break; case 1: - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+20, y, t.tm_min, flags|rowattr|LEFT|LEADING0, 2); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+MINUTE_OFFSET, y, t.tm_min, flags|rowattr|LEFT|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_min = checkIncDec(event, t.tm_min, 0, 59, 0); break; case 2: - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+40, y, t.tm_sec, flags|rowattr|LEFT|LEADING0, 2); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+SECOND_OFFSET, y, t.tm_sec, flags|rowattr|LEFT|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_sec = checkIncDec(event, t.tm_sec, 0, 59, 0); break; } @@ -489,20 +496,13 @@ void menuGeneralSetup(evt_t event) break; } - case ITEM_SETUP_STICK_MODE_LABELS: - lcd_putsLeft(y, NO_INDENT(STR_MODE)); - for (int i=0; i<4; i++) { - // lcdDrawBitmap((6+4*i)*12, y, ...); - } - break; - case ITEM_SETUP_STICK_MODE: { char s[2] = " "; s[0] = '1'+g_eeGeneral.stickMode; - lcd_putsAtt(INDENT_WIDTH, y, s, attr); + lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, s, attr); for (uint8_t i=0; i<4; i++) { - putsMixerSource((6+4*i)*12, y, MIXSRC_Rud + pgm_read_byte(modn12x3 + 4*g_eeGeneral.stickMode + i)); + putsMixerSource(RADIO_SETUP_2ND_COLUMN + 40 + 50*i, y, MIXSRC_Rud + pgm_read_byte(modn12x3 + 4*g_eeGeneral.stickMode + i)); } if (attr && s_editMode>0) { CHECK_INCDEC_GENVAR(event, g_eeGeneral.stickMode, 0, 3); diff --git a/radio/src/gui/Horus/menu_general_trainer.cpp b/radio/src/gui/Horus/menu_general_trainer.cpp index 6fdfbc63b..26c3df358 100644 --- a/radio/src/gui/Horus/menu_general_trainer.cpp +++ b/radio/src/gui/Horus/menu_general_trainer.cpp @@ -37,7 +37,7 @@ #include "../../opentx.h" #define TRAINER_COLUMN_WIDTH 60 -#define TRAINER_COLUMN_1 MENU_TITLE_LEFT+80 +#define TRAINER_COLUMN_1 MENUS_MARGIN_LEFT+80 #define TRAINER_COLUMN_2 TRAINER_COLUMN_1+TRAINER_COLUMN_WIDTH #define TRAINER_COLUMN_3 TRAINER_COLUMN_2+TRAINER_COLUMN_WIDTH @@ -58,9 +58,10 @@ void menuGeneralTrainer(evt_t event) uint8_t attr; uint8_t blink = ((s_editMode>0) ? BLINK|INVERS : INVERS); - lcd_putsAtt(TRAINER_COLUMN_1, MENU_HEADER_HEIGHT+1, "Mode", HEADER_COLOR); + /* lcd_putsAtt(TRAINER_COLUMN_1, MENU_HEADER_HEIGHT+1, "Mode", HEADER_COLOR); lcd_putsAtt(TRAINER_COLUMN_2, MENU_HEADER_HEIGHT+1, "Weight", HEADER_COLOR); lcd_putsAtt(TRAINER_COLUMN_3, MENU_HEADER_HEIGHT+1, "Source", HEADER_COLOR); + */ y = MENU_CONTENT_TOP + FH; int sub = m_posVert + 1; @@ -69,7 +70,7 @@ void menuGeneralTrainer(evt_t event) uint8_t chan = channel_order(i); volatile TrainerMix *td = &g_eeGeneral.trainer.mix[chan-1]; - putsMixerSource(MENU_TITLE_LEFT, y, MIXSRC_Rud-1+chan, ((sub==i && CURSOR_ON_LINE()) ? INVERS : 0)); + putsMixerSource(MENUS_MARGIN_LEFT, y, MIXSRC_Rud-1+chan, ((sub==i && CURSOR_ON_LINE()) ? INVERS : 0)); for (int j=0; j<3; j++) { @@ -102,7 +103,7 @@ void menuGeneralTrainer(evt_t event) attr = (sub==6) ? INVERS : 0; if (attr) s_editMode = 0; - lcd_putsAtt(MENU_TITLE_LEFT, MENU_CONTENT_TOP + 6*FH, STR_CAL, attr); + lcd_putsAtt(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 6*FH, STR_CAL, attr); for (int i=0; i<4; i++) { #if defined (PPM_UNIT_PERCENT_PREC1) lcd_outdezAtt(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])*2, LEFT|PREC1); diff --git a/radio/src/gui/Horus/menu_model.cpp b/radio/src/gui/Horus/menu_model.cpp index 24f910706..651074dfc 100644 --- a/radio/src/gui/Horus/menu_model.cpp +++ b/radio/src/gui/Horus/menu_model.cpp @@ -35,11 +35,6 @@ #include "../../opentx.h" -// TODO elsewhere! -#define WCHART 64 -#define X0 (LCD_W-WCHART-8) -#define Y0 118 - enum EnumTabModel { // e_ModelSelect, e_ModelSetup, @@ -80,11 +75,11 @@ extern uint8_t s_curveChan; void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t attr); -#define MIXES_2ND_COLUMN 80 +#define MIXES_2ND_COLUMN 100 uint8_t editDelay(const coord_t x, const coord_t y, const evt_t event, const uint8_t attr, const pm_char *str, uint8_t delay) { - lcd_putsAtt(x+MENU_TITLE_LEFT, y, str, TEXT_COLOR); + lcd_putsAtt(x+MENUS_MARGIN_LEFT, y, str, TEXT_COLOR); lcd_outdezAtt(x+MIXES_2ND_COLUMN, y, (10/DELAY_STEP)*delay, attr|PREC1|LEFT); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, delay, DELAY_MAX); return delay; @@ -121,7 +116,7 @@ static int8_t s_copyTgtOfs; static uint8_t editNameCursorPos = 0; -void editName(coord_t x, coord_t y, char *name, uint8_t size, evt_t event, uint8_t active) +void editName(coord_t x, coord_t y, char * name, uint8_t size, evt_t event, uint8_t active) { uint8_t mode = 0; if (active && s_editMode <= 0) { @@ -184,7 +179,7 @@ void editName(coord_t x, coord_t y, char *name, uint8_t size, evt_t event, uint8 lcd_putsnAtt(x, y, name, size, ZCHAR | mode); coord_t w = (editNameCursorPos == 0 ? 0 : getTextWidth(name, editNameCursorPos, ZCHAR)); char s[] = { idx2char(v), '\0' }; - lcdDrawFilledRect(x+w, y-1, getTextWidth(s, 1)-1, 12, TEXT_INVERTED_BGCOLOR); + lcdDrawFilledRect(x+w, y-INVERT_VERT_MARGIN, getTextWidth(s, 1)-1, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); lcd_putsAtt(x+w, y, s, TEXT_INVERTED_COLOR); } else { diff --git a/radio/src/gui/Horus/menu_model_curves.cpp b/radio/src/gui/Horus/menu_model_curves.cpp index 6a407f670..79f55bd06 100644 --- a/radio/src/gui/Horus/menu_model_curves.cpp +++ b/radio/src/gui/Horus/menu_model_curves.cpp @@ -55,10 +55,10 @@ point_t getPoint(uint8_t i) bool custom = (crv.type == CURVE_TYPE_CUSTOM); uint8_t count = 5+crv.points; if (i < count) { - result.x = X0-1-WCHART+i*WCHART*2/(count-1); - result.y = Y0 - (points[i]) * (WCHART-1) / 100; + result.x = CURVE_CENTER_X-1-CURVE_SIDE_WIDTH+i*CURVE_SIDE_WIDTH*2/(count-1); + result.y = CURVE_CENTER_Y - (points[i]) * (CURVE_SIDE_WIDTH-1) / 100; if (custom && i>0 && i0 ? INVERS|BLINK : INVERS) : 0); lcd_putsLeft(MENU_CONTENT_TOP + 2*FH, "Type"); - lcd_putsiAtt(MENU_TITLE_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 3*FH, STR_CURVE_TYPES, crv.type, attr); + lcd_putsiAtt(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 3*FH, STR_CURVE_TYPES, crv.type, attr); if (attr) { uint8_t newType = checkIncDecModelZero(event, crv.type, CURVE_TYPE_LAST); if (newType != crv.type) { @@ -167,7 +167,7 @@ void menuModelCurveOne(evt_t event) attr = (m_posVert==2 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); lcd_putsLeft(MENU_CONTENT_TOP + 4*FH, STR_COUNT); - lcd_outdezAtt(MENU_TITLE_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 5*FH, 5+crv.points, LEFT|attr, STR_PTS); + lcd_outdezAtt(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 5*FH, 5+crv.points, LEFT|attr, STR_PTS); if (attr) { int count = checkIncDecModel(event, crv.points, -3, 12); // 2pts - 17pts if (checkIncDec_Ret) { @@ -187,7 +187,7 @@ void menuModelCurveOne(evt_t event) } lcd_putsLeft(MENU_CONTENT_TOP + 6*FH, STR_SMOOTH); - lcdDrawCheckBox(70, MENU_CONTENT_TOP + 6*FH, crv.smooth, m_posVert==3 ? INVERS : 0); + lcdDrawCheckBox(lcdNextPos + 10, MENU_CONTENT_TOP + 6*FH, crv.smooth, m_posVert==3 ? INVERS : 0); if (m_posVert==3) crv.smooth = checkIncDecModel(event, crv.smooth, 0, 1); switch(event) { @@ -215,7 +215,7 @@ void menuModelCurveOne(evt_t event) lcd_putsAtt(115, MENU_FOOTER_TOP, "X", HEADER_COLOR); lcd_putsAtt(145, MENU_FOOTER_TOP, "Y", HEADER_COLOR); - coord_t posY = MENU_HEADER_HEIGHT+1; + coord_t posY = MENU_CONTENT_TOP; attr = (s_editMode > 0 ? INVERS|BLINK : INVERS); for (int i=0; i<5+crv.points; i++) { point_t point = getPoint(i); @@ -233,9 +233,9 @@ void menuModelCurveOne(evt_t event) if (i>=pointsOfs && i0 && i<5+crv.points-1) x = points[5+crv.points+i-1]; - lcd_outdezAtt(100, posY, i+1, LEFT); - lcd_outdezAtt(115, posY, x, LEFT|(selectionMode==1?attr:0)); - lcd_outdezAtt(145, posY, points[i], LEFT|(selectionMode==2?attr:0)); + lcd_outdezAtt(110, posY, i+1, LEFT); + lcd_outdezAtt(130, posY, x, LEFT|(selectionMode==1?attr:0)); + lcd_outdezAtt(160, posY, points[i], LEFT|(selectionMode==2?attr:0)); posY += FH; } @@ -256,7 +256,7 @@ void menuModelCurveOne(evt_t event) } if (5+crv.points > NUM_BODY_LINES) { - displayScrollbar(167, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); + lcdDrawScrollbar(167, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); } } @@ -270,14 +270,14 @@ void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t a switch (curve.type) { case CURVE_REF_DIFF: case CURVE_REF_EXPO: - curve.value = GVAR_MENU_ITEM(x+40, y, curve.value, -100, 100, m_posHorz==1 ? LEFT|attr : LEFT, 0, event); + curve.value = GVAR_MENU_ITEM(lcdNextPos+10, y, curve.value, -100, 100, m_posHorz==1 ? LEFT|attr : LEFT, 0, event); break; case CURVE_REF_FUNC: - lcd_putsiAtt(x+40, y, STR_VCURVEFUNC, curve.value, (m_posHorz==1 ? attr : 0)); + lcd_putsiAtt(lcdNextPos+10, y, STR_VCURVEFUNC, curve.value, (m_posHorz==1 ? attr : 0)); if (attr && m_posHorz==1) CHECK_INCDEC_MODELVAR_ZERO(event, curve.value, CURVE_BASE-1); break; case CURVE_REF_CUSTOM: - putsCurve(x+40, y, curve.value, (m_posHorz==1 ? attr : 0)); + putsCurve(lcdNextPos+10, y, curve.value, (m_posHorz==1 ? attr : 0)); if (attr && m_posHorz==1) { if (event==EVT_KEY_LONG(KEY_ENTER) && curve.value!=0) { s_curveChan = (curve.value<0 ? -curve.value-1 : curve.value-1); @@ -311,7 +311,7 @@ void menuModelCurvesAll(evt_t event) uint8_t k = i + s_pgOfs; LcdFlags attr = (sub == k ? INVERS : 0); { - putsStrIdx(MENU_TITLE_LEFT, y, STR_CV, k+1, attr); + putsStrIdx(MENUS_MARGIN_LEFT, y, STR_CV, k+1, attr); editName(50, y, g_model.curveNames[k], sizeof(g_model.curveNames[k]), 0, 0); CurveInfo & crv = g_model.curves[k]; lcd_outdezAtt(100, y, 5+crv.points, LEFT, STR_PTS); diff --git a/radio/src/gui/Horus/menu_model_custom_functions.cpp b/radio/src/gui/Horus/menu_model_custom_functions.cpp index a4b91c558..f39cf865d 100644 --- a/radio/src/gui/Horus/menu_model_custom_functions.cpp +++ b/radio/src/gui/Horus/menu_model_custom_functions.cpp @@ -37,10 +37,10 @@ #define MODEL_CUSTOM_FUNC_1ST_COLUMN 50 #define MODEL_CUSTOM_FUNC_2ND_COLUMN 100 -#define MODEL_CUSTOM_FUNC_2ND_COLUMN_EXT 150 -#define MODEL_CUSTOM_FUNC_3RD_COLUMN 200 -#define MODEL_CUSTOM_FUNC_4TH_COLUMN 286 -#define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF 290 +#define MODEL_CUSTOM_FUNC_2ND_COLUMN_EXT 180 +#define MODEL_CUSTOM_FUNC_3RD_COLUMN 300 +#define MODEL_CUSTOM_FUNC_4TH_COLUMN 440 +#define MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF 450 void onCustomFunctionsFileSelectionMenu(const char *result) { @@ -141,7 +141,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc coord_t y = MENU_CONTENT_TOP + i*FH; unsigned int k = i+s_pgOfs; - putsStrIdx(MENU_TITLE_LEFT, y, functions == g_model.customFn ? STR_SF : STR_GF, k+1, (sub==k && m_posHorz<0) ? INVERS : 0); + putsStrIdx(MENUS_MARGIN_LEFT, y, functions == g_model.customFn ? STR_SF : STR_GF, k+1, (sub==k && m_posHorz<0) ? INVERS : 0); CustomFunctionData *cfn = &functions[k]; unsigned int func = CFN_FUNC(cfn); diff --git a/radio/src/gui/Horus/menu_model_flightmodes.cpp b/radio/src/gui/Horus/menu_model_flightmodes.cpp index 508407830..313c1799d 100644 --- a/radio/src/gui/Horus/menu_model_flightmodes.cpp +++ b/radio/src/gui/Horus/menu_model_flightmodes.cpp @@ -53,6 +53,13 @@ bool isTrimModeAvailable(int mode) return (mode < 0 || (mode%2) == 0 || (mode/2) != m_posVert); } +#define FLIGHT_MODES_NAME_COLUMN 60 +#define FLIGHT_MODES_SWITCH_COLUMN 200 +#define FLIGHT_MODES_TRIMS_COLUMN 210 +#define FLIGHT_MODES_TRIM_WIDTH 25 +#define FLIGHT_MODES_FADEIN_COLUMN 400 +#define FLIGHT_MODES_FADEOUT_COLUMN 450 + void menuModelFlightModesAll(evt_t event) { MENU(STR_MENUFLIGHTPHASES, menuTabModel, e_FlightModesAll, MAX_FLIGHT_MODES+1, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|(ITEM_FLIGHT_MODES_LAST-1), NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, 0}); @@ -101,21 +108,21 @@ void menuModelFlightModesAll(evt_t event) FlightModeData *p = flightModeAddress(k); - putsFlightMode(MENU_TITLE_LEFT, y, k+1, (getFlightMode()==k ? 0/*BOLD*/ : 0) | ((sub==k && m_posHorz<0) ? INVERS : 0)); + putsFlightMode(MENUS_MARGIN_LEFT, y, k+1, (getFlightMode()==k ? 0/*BOLD*/ : 0) | ((sub==k && m_posHorz<0) ? INVERS : 0)); for (int j=0; j0) ? BLINK|INVERS : INVERS) : 0); LcdFlags active = (attr && s_editMode>0) ; switch (j) { case ITEM_FLIGHT_MODES_NAME: - editName(45, y, p->name, sizeof(p->name), event, attr); + editName(FLIGHT_MODES_NAME_COLUMN, y, p->name, sizeof(p->name), event, attr); break; case ITEM_FLIGHT_MODES_SWITCH: if (k == 0) - lcd_puts(145, y, "N/A"); + lcd_puts(FLIGHT_MODES_SWITCH_COLUMN, y, "N/A"); else - putsSwitches(145, y, p->swtch, attr); + putsSwitches(FLIGHT_MODES_SWITCH_COLUMN, y, p->swtch, attr); if (active) CHECK_INCDEC_MODELSWITCH(event, p->swtch, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); break; @@ -125,7 +132,7 @@ void menuModelFlightModesAll(evt_t event) case ITEM_FLIGHT_MODES_TRIM_AIL: { uint8_t t = j-ITEM_FLIGHT_MODES_TRIM_RUD; - putsTrimMode(155+j*15, y, k, t, attr); + putsTrimMode(FLIGHT_MODES_TRIMS_COLUMN+j*FLIGHT_MODES_TRIM_WIDTH, y, k, t, attr); if (active) { trim_t & v = p->trim[t]; v.mode = checkIncDec(event, v.mode==TRIM_MODE_NONE ? -1 : v.mode, -1, k==0 ? 0 : 2*MAX_FLIGHT_MODES-1, EE_MODEL, isTrimModeAvailable); @@ -134,12 +141,12 @@ void menuModelFlightModesAll(evt_t event) } case ITEM_FLIGHT_MODES_FADE_IN: - lcd_outdezAtt(272, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); + lcd_outdezAtt(FLIGHT_MODES_FADEIN_COLUMN, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); if (active) p->fadeIn = checkIncDec(event, p->fadeIn, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); break; case ITEM_FLIGHT_MODES_FADE_OUT: - lcd_outdezAtt(300, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); + lcd_outdezAtt(FLIGHT_MODES_FADEOUT_COLUMN, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); if (active) p->fadeOut = checkIncDec(event, p->fadeOut, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); break; diff --git a/radio/src/gui/Horus/menu_model_gvars.cpp b/radio/src/gui/Horus/menu_model_gvars.cpp index bb3d82a0e..651e26555 100644 --- a/radio/src/gui/Horus/menu_model_gvars.cpp +++ b/radio/src/gui/Horus/menu_model_gvars.cpp @@ -66,8 +66,8 @@ void menuModelGVars(evt_t event) for (int l=0; l0) ? BLINK|INVERS : INVERS) : 0); @@ -75,7 +75,7 @@ void menuModelGVars(evt_t event) switch(j) { case 0: - editName(MENU_TITLE_LEFT+30, y, g_model.gvars[i].name, LEN_GVAR_NAME, event, attr); + editName(MENUS_MARGIN_LEFT+30, y, g_model.gvars[i].name, LEN_GVAR_NAME, event, attr); break; default: diff --git a/radio/src/gui/Horus/menu_model_heli.cpp b/radio/src/gui/Horus/menu_model_heli.cpp index b5e6c864a..28f861f69 100644 --- a/radio/src/gui/Horus/menu_model_heli.cpp +++ b/radio/src/gui/Horus/menu_model_heli.cpp @@ -79,7 +79,7 @@ void menuModelHeli(evt_t event) break; case ITEM_HELI_ELE_WEIGHT: - lcd_puts(MENU_TITLE_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcd_puts(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.elevatorWeight, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.elevatorWeight, -100, 100); break; @@ -91,7 +91,7 @@ void menuModelHeli(evt_t event) break; case ITEM_HELI_AIL_WEIGHT: - lcd_puts(MENU_TITLE_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcd_puts(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.aileronWeight, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.aileronWeight, -100, 100); break; @@ -103,7 +103,7 @@ void menuModelHeli(evt_t event) break; case ITEM_HELI_COL_WEIGHT: - lcd_puts(MENU_TITLE_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcd_puts(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.collectiveWeight, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.collectiveWeight, -100, 100); break; diff --git a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp index a460f88e5..cf4e9ebfe 100644 --- a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp @@ -35,7 +35,7 @@ #include "../../opentx.h" -#define EXPO_ONE_2ND_COLUMN 80 +#define EXPO_ONE_2ND_COLUMN 110 #if defined(FLIGHT_MODES) void displayFlightModes(coord_t x, coord_t y, FlightModesType value, uint8_t attr) @@ -47,7 +47,7 @@ void displayFlightModes(coord_t x, coord_t y, FlightModesType value, uint8_t att char s[] = " "; s[0] = '0' + i; lcd_putsAtt(x, y, s, flags); - x += 9; + x += 12; } } @@ -81,25 +81,38 @@ int expoFn(int x) return anas[ed->chn]; } +int divRound(int num, int den) +{ + if (num < 0) + return (num - den/2) / den; + else + return (num + den/2) / den; +} + coord_t getYCoord(FnFuncP fn, coord_t x) { - return limit(0, Y0 - (fn(x * (RESX/WCHART)) * WCHART / RESX), LCD_H-1); + return limit(0, CURVE_CENTER_Y - divRound(fn(divRound(x * RESX, CURVE_SIDE_WIDTH)) * CURVE_SIDE_WIDTH, RESX), LCD_H-1); } void DrawFunction(FnFuncP fn, int offset) { - lcd_vlineStip(X0-offset, Y0-WCHART, WCHART*2, 0xee, CURVE_AXIS_COLOR); - lcd_hlineStip(X0-WCHART-offset, Y0, WCHART*2, 0xee, CURVE_AXIS_COLOR); + lcd_vlineStip(CURVE_CENTER_X-offset, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2, 0xee, CURVE_AXIS_COLOR); + lcd_hlineStip(CURVE_CENTER_X-CURVE_SIDE_WIDTH-offset, CURVE_CENTER_Y, CURVE_SIDE_WIDTH*2, 0xee, CURVE_AXIS_COLOR); - coord_t ymin=0, ymax=0, info=0; - for (int xv=-WCHART; xv<=WCHART; xv++) { + coord_t prev_yv = (coord_t)-1; + + for (int xv=-CURVE_SIDE_WIDTH; xv<=CURVE_SIDE_WIDTH; xv++) { coord_t yv = getYCoord(fn, xv); - - if (ymin == 0 || yv < ymin) - ymin = yv; - if (ymax == 0 || yv > ymax) - ymax = yv; - info += yv*xv; + if (prev_yv != (coord_t)-1) { + if (abs((int8_t)yv-prev_yv) <= 1) { + lcdDrawPoint(CURVE_CENTER_X+xv-offset-1, prev_yv, CURVE_COLOR); + } + else { + uint8_t tmp = (prev_yv < yv ? 0 : 1); + lcdDrawVerticalLine(CURVE_CENTER_X+xv-offset-1, yv+tmp, prev_yv-yv, CURVE_COLOR); + } + } + prev_yv = yv; } } @@ -331,7 +344,7 @@ void menuModelExpoOne(evt_t event) SUBMENU(STR_MENUINPUTS, EXPO_FIELD_MAX, EXPO_ONE_2ND_COLUMN+85, { 0, 0, 0, (ed->srcRaw >= MIXSRC_FIRST_TELEM ? (uint8_t)0 : (uint8_t)HIDDEN_ROW), 0, 0, CASE_CURVES(CURVE_ROWS) CASE_FLIGHT_MODES((MAX_FLIGHT_MODES-1) | NAVIGATION_LINE_BY_LINE) 0 /*, ...*/}); - putsMixerSource(60, MENU_FOOTER_TOP, MIXSRC_FIRST_INPUT+ed->chn, HEADER_COLOR); + putsMixerSource(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, MIXSRC_FIRST_INPUT+ed->chn, HEADER_COLOR); int8_t sub = m_posVert; @@ -346,18 +359,18 @@ void menuModelExpoOne(evt_t event) if (ed->scale > 0) x = (x * 1024) / convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale); } else { - lcd_outdezAtt(X0+WCHART, MENU_HEADER_HEIGHT+6*FH, calcRESXto1000(x), PREC1); + lcd_outdezAtt(CURVE_CENTER_X+CURVE_SIDE_WIDTH, CURVE_CENTER_Y+CURVE_SIDE_WIDTH-FH+6, calcRESXto1000(x), PREC1); } x = limit(-1024, x, 1024); int y = limit(-1024, expoFn(x), 1024); - lcd_outdezAtt(X0-WCHART, MENU_HEADER_HEIGHT+1, calcRESXto1000(y), LEFT|PREC1); + lcd_outdezAtt(CURVE_CENTER_X-CURVE_SIDE_WIDTH, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, calcRESXto1000(y), LEFT|PREC1); - x = x / (RESX/WCHART); + x = divRound(x*CURVE_SIDE_WIDTH, RESX); y = getYCoord(expoFn, x); - lcd_vlineStip(X0+x, y-3, 7, SOLID, TEXT_INVERTED_BGCOLOR); - lcd_hlineStip(X0+x-3, y, 7, SOLID, TEXT_INVERTED_BGCOLOR); + lcd_vlineStip(CURVE_CENTER_X+x, y-3, 7, SOLID, CURVE_CURSOR_COLOR); + lcd_hlineStip(CURVE_CENTER_X+x-3, y, 7, SOLID, CURVE_CURSOR_COLOR); } for (unsigned int k=0; kdestCh+1, HEADER_COLOR); + putsChn(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, md2->destCh+1, HEADER_COLOR); // The separation line between 2 columns lcd_vlineStip(MENU_COLUMN2_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, SOLID, HEADER_COLOR); @@ -612,28 +626,29 @@ static uint8_t s_copySrcCh; #define _STR_MAX(x) PSTR("/" #x) #define STR_MAX(x) _STR_MAX(x) -#define EXPO_LINE_WEIGHT_POS 89 -#define EXPO_LINE_SRC_POS 102 -#define EXPO_LINE_FM_POS 138 -#define EXPO_LINE_CURVE_POS 138 -#define EXPO_LINE_SWITCH_POS 182 -#define EXPO_LINE_SIDE_POS 215 -#define EXPO_LINE_SELECT_POS 61 -#define EXPO_LINE_SELECT_WIDTH 244 -#define EXPO_LINE_NAME_POS 238 +#define EXPO_LINE_WEIGHT_POS 92 +#define EXPO_LINE_SRC_POS 115 +#define EXPO_LINE_CURVE_POS 162 +#define EXPO_LINE_SWITCH_POS 210 +#define EXPO_LINE_SIDE_POS 250 +#define EXPO_LINE_FM_POS 270 +#define EXPO_LINE_NAME_POS 380 +#define EXPO_LINE_SELECT_POS 50 +#define EXPO_LINE_SELECT_WIDTH (LCD_W-EXPO_LINE_SELECT_POS-5) -#define MIX_LINE_WEIGHT_POS 69 -#define MIX_LINE_SRC_POS 83 -#define MIX_LINE_FM_POS 142 -#define MIX_LINE_CURVE_POS 142 -#define MIX_LINE_SWITCH_POS 190 -#define MIX_LINE_DELAY_POS 225 -#define MIX_LINE_SELECT_POS 40 -#define MIX_LINE_SELECT_WIDTH 265 +#define MIX_LINE_WEIGHT_POS 92 +#define MIX_LINE_SRC_POS 115 +#define MIX_LINE_CURVE_POS 162 +#define MIX_LINE_SWITCH_POS 210 +#define MIX_LINE_DELAY_POS 255 +#define MIX_LINE_FM_POS 270 +#define MIX_LINE_NAME_POS 380 +#define MIX_LINE_SELECT_POS 50 +#define MIX_LINE_SELECT_WIDTH (LCD_W-EXPO_LINE_SELECT_POS-5) void lineSurround(bool expo, coord_t y, LcdFlags flags=CURVE_AXIS_COLOR) { - lcd_rect(expo ? EXPO_LINE_SELECT_POS : MIX_LINE_SELECT_POS, y-INVERT_VERT_MARGIN, expo ? EXPO_LINE_SELECT_WIDTH : MIX_LINE_SELECT_WIDTH, INVERT_LINE_HEIGHT, WARNING_COLOR|(s_copyMode == COPY_MODE ? SOLID : DOTTED), flags); + lcd_rect(expo ? EXPO_LINE_SELECT_POS : MIX_LINE_SELECT_POS, y-INVERT_VERT_MARGIN, expo ? EXPO_LINE_SELECT_WIDTH : MIX_LINE_SELECT_WIDTH, INVERT_LINE_HEIGHT, (s_copyMode == COPY_MODE ? SOLID : DOTTED), flags); } void onExpoMixMenu(const char *result) @@ -684,10 +699,8 @@ void displayMixLine(coord_t y, MixData *md) { if (md->name[0]) lcd_putsnAtt(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); - if (!md->flightModes || ((md->curve.value || md->swtch) && ((get_tmr10ms() / 200) & 1))) - displayMixInfos(y, md); - else - displayFlightModes(MIX_LINE_FM_POS, y, md->flightModes, 0); + displayMixInfos(y, md); + displayFlightModes(MIX_LINE_FM_POS, y, md->flightModes, 0); } void displayExpoInfos(coord_t y, ExpoData *ed) @@ -705,10 +718,8 @@ void displayExpoLine(coord_t y, ExpoData *ed) { putsMixerSource(EXPO_LINE_SRC_POS, y, ed->srcRaw); - if (!ed->flightModes || ((ed->curve.value || ed->swtch) && ((get_tmr10ms() / 200) & 1))) - displayExpoInfos(y, ed); - else - displayFlightModes(EXPO_LINE_FM_POS, y, ed->flightModes, 0); + displayExpoInfos(y, ed); + displayFlightModes(EXPO_LINE_FM_POS, y, ed->flightModes, 0); if (ed->name[0]) { lcd_putsnAtt(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); @@ -724,6 +735,9 @@ void menuModelExpoMix(uint8_t expo, evt_t event) uint8_t chn = (expo ? expoAddress(s_currIdx)->chn+1 : mixAddress(s_currIdx)->destCh+1); + int linesCount = getLinesCount(expo); + SIMPLE_MENU(expo ? STR_MENUINPUTS : STR_MIXER, menuTabModel, expo ? e_InputsAll : e_MixAll, linesCount, DEFAULT_SCROLLBAR_X); + switch (event) { case EVT_ENTRY: @@ -847,12 +861,9 @@ void menuModelExpoMix(uint8_t expo, evt_t event) break; } - int linesCount = getLinesCount(expo); - SIMPLE_MENU(expo ? STR_MENUINPUTS : STR_MIXER, menuTabModel, expo ? e_InputsAll : e_MixAll, linesCount, DEFAULT_SCROLLBAR_X); - char str[6]; sprintf(str, "%d/%d", getExpoMixCount(expo), expo ? MAX_EXPOS : MAX_MIXERS); - lcd_putsAtt(60, MENU_FOOTER_TOP, str, HEADER_COLOR); + lcd_putsAtt(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, str, HEADER_COLOR); sub = m_posVert; s_currCh = 0; @@ -865,10 +876,10 @@ void menuModelExpoMix(uint8_t expo, evt_t event) if (expo ? (ichn+1 == ch && EXPO_VALID(ed)) : (isrcRaw && md->destCh+1 == ch)) { if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { if (expo) { - putsMixerSource(MENU_TITLE_LEFT, y, ch); + putsMixerSource(MENUS_MARGIN_LEFT, y, ch); } else { - putsChn(MENU_TITLE_LEFT, y, ch, 0); // show CHx + putsChn(MENUS_MARGIN_LEFT, y, ch, 0); // show CHx } } uint8_t mixCnt = 0; @@ -889,7 +900,7 @@ void menuModelExpoMix(uint8_t expo, evt_t event) if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { LcdFlags attr = ((s_copyMode || sub != cur) ? 0 : INVERS); if (expo) { - ed->weight = GVAR_MENU_ITEM(EXPO_LINE_WEIGHT_POS, y, ed->weight, MIN_EXPO_WEIGHT, 100, attr | (isExpoActive(i) ? BOLD : 0), 0, 0); + GVAR_MENU_ITEM(EXPO_LINE_WEIGHT_POS, y, ed->weight, MIN_EXPO_WEIGHT, 100, attr | (isExpoActive(i) ? BOLD : 0), 0, 0); displayExpoLine(y, ed); } else { @@ -919,7 +930,7 @@ void menuModelExpoMix(uint8_t expo, evt_t event) } if (cur == sub) { /* invert the raw when it's the current one */ - lineSurround(expo, y, WARNING_COLOR); + lineSurround(expo, y, ALARM_COLOR); } } } @@ -941,10 +952,10 @@ void menuModelExpoMix(uint8_t expo, evt_t event) } if (cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES) { if (expo) { - putsMixerSource(MENU_TITLE_LEFT, y, ch, attr); + putsMixerSource(MENUS_MARGIN_LEFT, y, ch, attr); } else { - putsChn(MENU_TITLE_LEFT, y, ch, attr); // show CHx + putsChn(MENUS_MARGIN_LEFT, y, ch, attr); // show CHx if (attr) { displayHeaderChannelName(ch); } diff --git a/radio/src/gui/Horus/menu_model_limits.cpp b/radio/src/gui/Horus/menu_model_limits.cpp index 2d9c11bee..0afe8631f 100644 --- a/radio/src/gui/Horus/menu_model_limits.cpp +++ b/radio/src/gui/Horus/menu_model_limits.cpp @@ -65,15 +65,15 @@ enum LimitsItems { }; #if defined(PPM_CENTER_ADJUSTABLE) - #define LIMITS_NAME_POS 44 - #define LIMITS_OFFSET_POS 136 - #define LIMITS_MIN_POS 175 - #define LIMITS_DIRECTION_POS 177 - #define LIMITS_MAX_POS 214 - #define LIMITS_REVERT_POS 218 - #define LIMITS_CURVE_POS 232 - #define LIMITS_PPM_CENTER_POS 292 - #define LIMITS_SYMETRICAL_POS 298 + #define LIMITS_NAME_POS 52 + #define LIMITS_OFFSET_POS 160 + #define LIMITS_MIN_POS 220 + #define LIMITS_DIRECTION_POS 240 + #define LIMITS_MAX_POS 300 + #define LIMITS_REVERT_POS 320 + #define LIMITS_CURVE_POS 360 + #define LIMITS_PPM_CENTER_POS 450 + #define LIMITS_SYMETRICAL_POS 460 #else #define LIMITS_NAME_POS 44 #define LIMITS_OFFSET_POS 136 @@ -177,7 +177,7 @@ void menuModelLimits(evt_t event) int limit = (g_model.extendedLimits ? LIMIT_EXT_MAX : 1000); - putsChn(MENU_TITLE_LEFT, y, k+1, (sub==k && m_posHorz < 0) ? INVERS : 0); + putsChn(MENUS_MARGIN_LEFT, y, k+1, (sub==k && m_posHorz < 0) ? INVERS : 0); if (sub==k && m_posHorz < 0 && event==EVT_KEY_LONG(KEY_ENTER) && !READ_ONLY()) { killEvents(event); MENU_ADD_ITEM(STR_RESET); diff --git a/radio/src/gui/Horus/menu_model_logical_switches.cpp b/radio/src/gui/Horus/menu_model_logical_switches.cpp index 5c2afc723..5f1def420 100644 --- a/radio/src/gui/Horus/menu_model_logical_switches.cpp +++ b/radio/src/gui/Horus/menu_model_logical_switches.cpp @@ -47,12 +47,12 @@ enum LogicalSwitchFields { LS_FIELD_LAST = LS_FIELD_COUNT-1 }; -#define CSW_1ST_COLUMN 40 -#define CSW_2ND_COLUMN 85 -#define CSW_3RD_COLUMN 130 -#define CSW_4TH_COLUMN 185 -#define CSW_5TH_COLUMN 225 -#define CSW_6TH_COLUMN 270 +#define CSW_1ST_COLUMN 42 +#define CSW_2ND_COLUMN 90 +#define CSW_3RD_COLUMN 140 +#define CSW_4TH_COLUMN 200 +#define CSW_5TH_COLUMN 245 +#define CSW_6TH_COLUMN 300 #define INCDEC_DECLARE_VARS(f) uint8_t incdecFlag = (f); IsValueAvailable isValueAvailable = NULL #define INCDEC_SET_FLAG(f) incdecFlag = (f) @@ -131,7 +131,7 @@ void menuModelLogicalSwitches(evt_t event) // CSW name unsigned int sw = SWSRC_SW1+k; - putsSwitches(MENU_TITLE_LEFT, y, sw, (getSwitch(sw) ? 0/*BOLD*/ : 0) | ((sub==k && CURSOR_ON_LINE()) ? INVERS : 0)); + putsSwitches(MENUS_MARGIN_LEFT, y, sw, (getSwitch(sw) ? 0/*BOLD*/ : 0) | ((sub==k && CURSOR_ON_LINE()) ? INVERS : 0)); // CSW func lcd_putsiAtt(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp index 82d604c65..fa3968546 100644 --- a/radio/src/gui/Horus/menu_model_select.cpp +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -248,18 +248,18 @@ void menuModelSelect(evt_t event) LcdFlags flags = 0; if (sub==k && s_copyMode == 0) { flags |= TEXT_INVERTED_COLOR; - lcdDrawFilledRect(MENU_TITLE_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawFilledRect(MENUS_MARGIN_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } char str[20]; strAppendDigits(str, k+1); - lcd_putsAtt(MENU_TITLE_LEFT, y, str, flags); + lcd_putsAtt(MENUS_MARGIN_LEFT, y, str, flags); if (s_copyMode == MOVE_MODE || (s_copyMode == COPY_MODE && s_copySrcRow >= 0)) { if (k == sub) { if (s_copyMode == COPY_MODE) { k = s_copySrcRow; - // lcd_rect(MENU_TITLE_LEFT-5, y-3, 170, 16, SOLID, YELLOW); + // lcd_rect(MENUS_MARGIN_LEFT-5, y-3, 170, 16, SOLID, YELLOW); } else { k = sub + s_copyTgtOfs; @@ -285,7 +285,7 @@ void menuModelSelect(evt_t event) } if (s_copyMode && (vertpos_t)sub==i+s_pgOfs) { - lcd_rect(MENU_TITLE_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, s_copyMode == COPY_MODE ? SOLID : DOTTED, WARNING_COLOR); + lcd_rect(MENUS_MARGIN_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, s_copyMode == COPY_MODE ? SOLID : DOTTED, WARNING_COLOR); } } diff --git a/radio/src/gui/Horus/menu_model_setup.cpp b/radio/src/gui/Horus/menu_model_setup.cpp index a322b6e38..f04ca9935 100644 --- a/radio/src/gui/Horus/menu_model_setup.cpp +++ b/radio/src/gui/Horus/menu_model_setup.cpp @@ -108,7 +108,7 @@ void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, evt_t event) if (attr && m_posHorz < 0) { lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 80+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } - putsStrIdx(MENU_TITLE_LEFT, y, STR_TIMER, timerIdx+1); + putsStrIdx(MENUS_MARGIN_LEFT, y, STR_TIMER, timerIdx+1); putsTimerMode(MODEL_SETUP_2ND_COLUMN, y, timer->mode, (m_posHorz<=0 ? attr : 0)); putsTimer(MODEL_SETUP_2ND_COLUMN+50, y, timer->start, (m_posHorz!=0 ? attr : 0)); if (attr && s_editMode>0) { diff --git a/radio/src/gui/Horus/menu_model_telemetry.cpp b/radio/src/gui/Horus/menu_model_telemetry.cpp index f05412ca5..0ea07919f 100644 --- a/radio/src/gui/Horus/menu_model_telemetry.cpp +++ b/radio/src/gui/Horus/menu_model_telemetry.cpp @@ -214,7 +214,7 @@ void menuModelSensor(evt_t event) SUBMENU(STR_MENUSENSOR, SENSOR_FIELD_MAX, DEFAULT_SCROLLBAR_X, { 0, 0, sensor->type == TELEM_TYPE_CALCULATED ? (uint8_t)0 : (uint8_t)1, SENSOR_UNIT_ROWS, SENSOR_PREC_ROWS, SENSOR_PARAM1_ROWS, SENSOR_PARAM2_ROWS, SENSOR_PARAM3_ROWS, SENSOR_PARAM4_ROWS, 0 }); - lcd_outdezAtt(MENU_TITLE_LEFT+getTextWidth(TR_MENUSENSOR)+5, MENU_FOOTER_TOP, s_currIdx+1, HEADER_COLOR|LEFT); + lcd_outdezAtt(MENUS_MARGIN_LEFT+getTextWidth(TR_MENUSENSOR)+5, MENU_FOOTER_TOP, s_currIdx+1, HEADER_COLOR|LEFT); putsTelemetryChannelValue(SENSOR_2ND_COLUMN, MENU_FOOTER_TOP, s_currIdx, getValue(MIXSRC_FIRST_TELEM+3*s_currIdx), HEADER_COLOR|LEFT); int sub = m_posVert; @@ -371,7 +371,7 @@ void menuModelSensor(evt_t event) case SENSOR_FIELD_PARAM4: { - putsStrIdx(MENU_TITLE_LEFT, y, NO_INDENT(STR_SOURCE), k-SENSOR_FIELD_PARAM1+1); + putsStrIdx(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_SOURCE), k-SENSOR_FIELD_PARAM1+1); int8_t & source = sensor->calc.sources[k-SENSOR_FIELD_PARAM1]; if (attr) { source = checkIncDec(event, source, -MAX_SENSORS, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); @@ -461,7 +461,7 @@ void menuModelTelemetry(evt_t event) if (k>=ITEM_TELEMETRY_SENSOR1 && k0) { switch (m_posHorz) { case 0: @@ -590,7 +590,7 @@ void menuModelTelemetry(evt_t event) case ITEM_TELEMETRY_SCREEN_LABEL4: { uint8_t screenIndex = TELEMETRY_CURRENT_EDIT_SCREEN(k); - putsStrIdx(MENU_TITLE_LEFT, y, STR_SCREEN, screenIndex+1); + putsStrIdx(MENUS_MARGIN_LEFT, y, STR_SCREEN, screenIndex+1); TelemetryScreenType oldScreenType = TELEMETRY_SCREEN_TYPE(screenIndex); TelemetryScreenType newScreenType = (TelemetryScreenType)selectMenuItem(TELEM_SCRTYPE_COL, y, PSTR(""), STR_VTELEMSCREENTYPE, oldScreenType, 0, TELEMETRY_SCREEN_TYPE_MAX, (m_posHorz==0 ? attr : 0), event); if (newScreenType != oldScreenType) { @@ -665,17 +665,17 @@ void menuModelTelemetry(evt_t event) if (IS_BARS_SCREEN(screenIndex)) { FrSkyBarData & bar = g_model.frsky.screens[screenIndex].bars[lineIndex]; source_t barSource = bar.source; - putsMixerSource(MENU_TITLE_LEFT+INDENT_WIDTH, y, barSource, (m_posHorz==0 ? attr : 0)); + putsMixerSource(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, barSource, (m_posHorz==0 ? attr : 0)); int barMax = getMaximumValue(barSource); int barMin = -barMax; if (barSource) { if (barSource <= MIXSRC_LAST_CH) { - putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+100, y, barSource, calc100toRESX(bar.barMin), (m_posHorz==1 ? attr : 0) | LEFT); - putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+200, y, barSource, calc100toRESX(bar.barMax), (m_posHorz==2 ? attr : 0) | LEFT); + putsChannelValue(MENUS_MARGIN_LEFT+INDENT_WIDTH+100, y, barSource, calc100toRESX(bar.barMin), (m_posHorz==1 ? attr : 0) | LEFT); + putsChannelValue(MENUS_MARGIN_LEFT+INDENT_WIDTH+200, y, barSource, calc100toRESX(bar.barMax), (m_posHorz==2 ? attr : 0) | LEFT); } else { - putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+100, y, barSource, bar.barMin, (m_posHorz==1 ? attr : 0) | LEFT); - putsChannelValue(MENU_TITLE_LEFT+INDENT_WIDTH+200, y, barSource, bar.barMax, (m_posHorz==2 ? attr : 0) | LEFT); + putsChannelValue(MENUS_MARGIN_LEFT+INDENT_WIDTH+100, y, barSource, bar.barMin, (m_posHorz==1 ? attr : 0) | LEFT); + putsChannelValue(MENUS_MARGIN_LEFT+INDENT_WIDTH+200, y, barSource, bar.barMax, (m_posHorz==2 ? attr : 0) | LEFT); } } else if (attr) { @@ -711,7 +711,7 @@ void menuModelTelemetry(evt_t event) for (int c=0; c0) { CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, value, MIXSRC_LAST_TELEM, isSourceAvailable); diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h index 64ae48ff5..92f9ce2f0 100644 --- a/radio/src/gui/Horus/menus.h +++ b/radio/src/gui/Horus/menus.h @@ -49,14 +49,17 @@ #endif #define MENUS_SCROLLBAR_WIDTH 2 -#define MENU_COLUMN2_X 180 -#define lcd_putsColumnLeft(x, y, str) lcd_puts((x > MENU_COLUMN2_X+MENU_TITLE_LEFT+68) ? MENU_COLUMN2_X+MENU_TITLE_LEFT : MENU_TITLE_LEFT, y, str) +#define MENU_COLUMN2_X 280 + +#define lcd_putsColumnLeft(x, y, str) lcd_puts((x > MENU_COLUMN2_X+MENUS_MARGIN_LEFT+68) ? MENU_COLUMN2_X+MENUS_MARGIN_LEFT : MENUS_MARGIN_LEFT, y, str) typedef int16_t vertpos_t; typedef evt_t & check_event_t; #define horzpos_t int8_t +extern uint8_t menuPageIndex; +extern uint8_t menuPageCount; extern vertpos_t m_posVert; extern horzpos_t m_posHorz; extern vertpos_t s_pgOfs; @@ -189,8 +192,11 @@ int8_t checkIncDecMovedSwitch(int8_t val); #define CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, var, max, check) \ var = checkIncDec(event, var, 0, max, EE_MODEL, check) +bool isThrottleSourceAvailable(int source); bool isLogicalSwitchFunctionAvailable(int function); bool isAssignableFunctionAvailable(int function); +bool isSourceAvailableInResetSpecialFunction(int index); +bool isSourceAvailableInGlobalResetSpecialFunction(int index); bool isSwitchAvailableInLogicalSwitches(int swtch); bool isSwitchAvailableInCustomFunctions(int swtch); bool isSwitchAvailableInMixes(int swtch); @@ -222,18 +228,18 @@ bool check_submenu_simple(check_event_t event, uint8_t maxrow, uint16_t scrollba #define MENU_TAB(...) const uint8_t mstate_tab[] = __VA_ARGS__ -void drawMenuTemplate(const char *title, evt_t event, int pageIndex=0, int pageCount=0); +void drawMenuTemplate(const char *title, evt_t event); #define MENU(title, tab, menu, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ if (!check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X)) return; \ - drawMenuTemplate(title, event, menu, DIM(tab)); \ + drawMenuTemplate(title, event); \ #define SIMPLE_MENU(title, tab, menu, lines_count, scrollbar_X) \ if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ if (!check_simple(event, menu, tab, DIM(tab), lines_count, scrollbar_X)) return; \ - drawMenuTemplate(title, event, menu, DIM(tab)); \ + drawMenuTemplate(title, event); \ #define SUBMENU(title, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp index 843737df2..bb58ac257 100644 --- a/radio/src/gui/Horus/navigation.cpp +++ b/radio/src/gui/Horus/navigation.cpp @@ -216,6 +216,9 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags #define INC(val, min, max) if (valmin) {val--;} else {val=max;} +uint8_t menuPageIndex; +uint8_t menuPageCount; + bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t rowcount, uint16_t scrollbar_X, uint8_t flags) { vertpos_t l_posVert = m_posVert; @@ -239,8 +242,10 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t break; case EVT_KEY_BREAK(KEY_ENTER): - l_posVert = POS_VERT_INIT; - event = 0; + if (rowcount > 0) { + l_posVert = POS_VERT_INIT; + event = 0; + } break; } @@ -248,12 +253,15 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t chainMenu((MenuFuncP)pgm_read_adr(&menuTab[cc])); return false; } + + menuPageIndex = curr; + menuPageCount = menuTabSize; } switch(event) { case EVT_ENTRY: - l_posVert = -1; + l_posVert = (menuTab ? -1 : 0); l_posHorz = POS_HORZ_INIT(l_posVert); s_editMode = EDIT_MODE_INIT; break; @@ -267,9 +275,8 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t if (s_editMode > 1) break; if (m_posHorz < 0 && maxcol > 0 && READ_ONLY_UNLOCKED()) { l_posHorz = 0; - break; } - if (READ_ONLY_UNLOCKED()) { + else if (READ_ONLY_UNLOCKED()) { s_editMode = (s_editMode<=0); } break; @@ -280,7 +287,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t break; case EVT_KEY_BREAK(KEY_EXIT): - if (s_editMode>0) { + if (s_editMode > 0) { s_editMode = 0; break; } @@ -288,7 +295,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t if (l_posHorz >= 0 && (COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { l_posHorz = -1; } - else if (l_posVert >= 0) { + else if (menuTab && l_posVert >= 0) { l_posVert = -1; l_posHorz = 0; #if 0 @@ -313,6 +320,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_FIRST(KEY_LEFT): case EVT_KEY_REPT(KEY_LEFT): + if (s_editMode != 0) break; DEC(l_posHorz, 0, maxcol); break; @@ -439,7 +447,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t } if (scrollbar_X && linesCount > NUM_BODY_LINES) { - displayScrollbar(scrollbar_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, s_pgOfs, linesCount, NUM_BODY_LINES); + lcdDrawScrollbar(scrollbar_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, s_pgOfs, linesCount, NUM_BODY_LINES); } m_posVert = l_posVert; diff --git a/radio/src/gui/Horus/popups.cpp b/radio/src/gui/Horus/popups.cpp index 9705d796b..dd8402cd9 100644 --- a/radio/src/gui/Horus/popups.cpp +++ b/radio/src/gui/Horus/popups.cpp @@ -156,13 +156,13 @@ const char * displayMenu(evt_t event) uint8_t display_count = min(s_menu_count, (uint16_t)MENU_MAX_LINES); int y = (LCD_H - (display_count*(FH+1))) / 2; - drawBlackOverlay(); + // drawBlackOverlay(); lcdDrawFilledRect(MENU_X, y, MENU_W, display_count * (FH+1), TEXT_BGCOLOR); - lcd_rect(MENU_X, y, MENU_W, display_count * (FH+1), SOLID, WARNING_COLOR); + lcdDrawRect(MENU_X, y, MENU_W, display_count * (FH+1) + 1, ALARM_COLOR); for (uint8_t i=0; i display_count) { - displayScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); + lcdDrawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); } switch(event) { @@ -216,7 +216,6 @@ const char * displayMenu(evt_t event) } } break; - CASE_EVT_ROTARY_BREAK case EVT_KEY_BREAK(KEY_ENTER): result = s_menu[s_menu_item]; // no break diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index faf19e648..44b4df5c3 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -55,7 +55,8 @@ void drawPotsBars() for (x=LCD_W/2-9, i=NUM_STICKS; i NUM_BODY_LINES) { - displayScrollbar(LCD_W-5, 30, MENU_FOOTER_TOP-34, s_pgOfs, lines_count, NUM_BODY_LINES); + lcdDrawScrollbar(LCD_W-5, 30, MENU_FOOTER_TOP-34, s_pgOfs, lines_count, NUM_BODY_LINES); } } diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 3051de66d..286affdb6 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -38,19 +38,20 @@ #include #define COLUMN_HEADER_X 150 + void displayHeader(const char *header) { - lcd_putsAtt(COLUMN_HEADER_X, MENU_FOOTER_TOP, header, HEADER_COLOR); + // TODO ? lcd_putsAtt(COLUMN_HEADER_X, MENU_FOOTER_TOP, header, HEADER_COLOR); } void displayColumnHeader(const char * const *headers, uint8_t index) { - displayHeader(headers[index]); + // TODO ? displayHeader(headers[index]); } void drawStick(coord_t centrex, int16_t xval, int16_t yval) { -#define BOX_CENTERY (170 - FH - BOX_WIDTH/2) +#define BOX_CENTERY (220 - FH - BOX_WIDTH/2) #define MARKER_WIDTH 5 lcdDrawSquare(centrex-BOX_WIDTH/2, BOX_CENTERY-BOX_WIDTH/2, BOX_WIDTH, TEXT_COLOR); @@ -82,7 +83,7 @@ void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) } } -void displayScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) +void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { lcdDrawVerticalLine(x, y, h, LINE_COLOR); coord_t yofs = (h*offset + count/2) / count; @@ -103,22 +104,16 @@ void updateProgressBar(int num, int den) { if (num > 0 && den > 0) { int width = (200*num)/den; - lcd_hline(5, 6*FH+6, width, FORCE); - lcd_hline(5, 6*FH+7, width, FORCE); - lcd_hline(5, 6*FH+8, width, FORCE); + lcd_hline(5, 6*FH+6, width); + lcd_hline(5, 6*FH+7, width); + lcd_hline(5, 6*FH+8, width); lcdRefresh(); } } -void title(const pm_char * s) -{ - int width = getTextWidth(s); - lcd_putsAtt(MENU_TITLE_LEFT, MENU_TITLE_TOP+2, s, MENU_TITLE_COLOR); -} - #define MENU_ICONS_SPACING 31 -void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCount) +void drawMenuTemplate(const char * name, evt_t event) { // clear the screen lcdDrawFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); @@ -130,29 +125,29 @@ void drawMenuTemplate(const char *name, evt_t event, int pageIndex, int pageCoun lcdDrawBitmapPattern(0, 0, LBM_TOP_POLYGON, TITLE_BGCOLOR); if (m_posVert < 0) { - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING-10, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); + lcdDrawBitmapPattern(58+menuPageIndex*MENU_ICONS_SPACING-10, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); } else { - lcdDrawFilledRect(58+pageIndex*MENU_ICONS_SPACING-9, 0, 32, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); - lcdDrawBitmapPattern(58+pageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_DOT, MENU_TITLE_COLOR); + lcdDrawFilledRect(58+menuPageIndex*MENU_ICONS_SPACING-9, 0, 32, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); + lcdDrawBitmapPattern(58+menuPageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_DOT, MENU_TITLE_COLOR); } const uint8_t * const * icons = (g_menuPos[0] == 0 ? LBM_MODEL_ICONS : LBM_RADIO_ICONS); lcdDrawBitmapPattern(5, 7, icons[0], MENU_TITLE_COLOR); - for (int i=0; isrcRaw == 0) return false; + if (md->destCh == channel) return true; + if (md->destCh > channel) return false; + } + return false; +} + +int getChannelsUsed() +{ + int result = 0; + int lastCh = -1; + for (int i=0; isrcRaw == 0) return result; + if (md->destCh != lastCh) { ++result; lastCh = md->destCh; } + } + return result; +} + bool isSourceAvailable(int source) { if (source>=MIXSRC_FIRST_INPUT && source<=MIXSRC_LAST_INPUT) { @@ -98,17 +121,11 @@ bool isSourceAvailable(int source) #endif if (source>=MIXSRC_CH1 && source<=MIXSRC_LAST_CH) { - uint8_t destCh = source-MIXSRC_CH1; - for (uint8_t i = 0; i < MAX_MIXERS; i++) { - MixData *md = mixAddress(i); - if (md->srcRaw == 0) return false; - if (md->destCh==destCh) return true; - } - return false; + return isChannelUsed(source-MIXSRC_CH1); } if (source>=MIXSRC_FIRST_LOGICAL_SWITCH && source<=MIXSRC_LAST_LOGICAL_SWITCH) { - LogicalSwitchData * cs = lswAddress(source-MIXSRC_SW1); + LogicalSwitchData * cs = lswAddress(source-MIXSRC_FIRST_LOGICAL_SWITCH); return (cs->func != LS_FUNC_NONE); } @@ -225,6 +242,7 @@ bool isSwitchAvailable(int swtch, SwitchContext context) return true; } +#if NUM_XPOTS > 0 if (swtch >= SWSRC_FIRST_MULTIPOS_SWITCH && swtch <= SWSRC_LAST_MULTIPOS_SWITCH) { int index = (swtch - SWSRC_FIRST_MULTIPOS_SWITCH) / XPOTS_MULTIPOS_COUNT; if (IS_POT_MULTIPOS(POT1+index)) { @@ -235,6 +253,7 @@ bool isSwitchAvailable(int swtch, SwitchContext context) return false; } } +#endif if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH && swtch <= SWSRC_LAST_LOGICAL_SWITCH) { if (context == GeneralCustomFunctionsContext) { diff --git a/radio/src/gui/Taranis/navigation.cpp b/radio/src/gui/Taranis/navigation.cpp index 6930f1dbc..ad1aa29f6 100644 --- a/radio/src/gui/Taranis/navigation.cpp +++ b/radio/src/gui/Taranis/navigation.cpp @@ -597,7 +597,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP } if (scrollbar_X && linesCount > NUM_BODY_LINES) { - displayScrollbar(scrollbar_X, MENU_HEADER_HEIGHT, LCD_H-MENU_HEADER_HEIGHT, s_pgOfs, linesCount, NUM_BODY_LINES); + lcdDrawScrollbar(scrollbar_X, MENU_HEADER_HEIGHT, LCD_H-MENU_HEADER_HEIGHT, s_pgOfs, linesCount, NUM_BODY_LINES); } m_posVert = l_posVert; diff --git a/radio/src/gui/Taranis/popups.cpp b/radio/src/gui/Taranis/popups.cpp index 9eb322a37..1ac52edb6 100644 --- a/radio/src/gui/Taranis/popups.cpp +++ b/radio/src/gui/Taranis/popups.cpp @@ -139,7 +139,7 @@ const char * displayMenu(uint8_t event) } if (s_menu_count > display_count) { - displayScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_DISPLAY_LINES * (FH+1), s_menu_offset, s_menu_count, display_count); + lcdDrawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_DISPLAY_LINES * (FH+1), s_menu_offset, s_menu_count, display_count); } switch(event) { diff --git a/radio/src/gui/Taranis/view_text.cpp b/radio/src/gui/Taranis/view_text.cpp index 807a1a545..b71bb1a32 100644 --- a/radio/src/gui/Taranis/view_text.cpp +++ b/radio/src/gui/Taranis/view_text.cpp @@ -149,7 +149,7 @@ void menuTextView(uint8_t event) lcd_invert_line(0); if (lines_count > LCD_LINES-1) { - displayScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); + lcdDrawScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); } } diff --git a/radio/src/gui/Taranis/widgets.cpp b/radio/src/gui/Taranis/widgets.cpp index b9ed8bd23..80a4603dc 100644 --- a/radio/src/gui/Taranis/widgets.cpp +++ b/radio/src/gui/Taranis/widgets.cpp @@ -82,7 +82,7 @@ void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr) lcd_outdezAtt(x, 0, index+1, attr); } -void displayScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) +void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { lcd_vlineStip(x, y, h, DOTTED); coord_t yofs = (h * offset) / count; diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index bef3ff4ff..5ecf15fbf 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -1764,7 +1764,7 @@ enum SwitchSources { SWSRC_SF1, SWSRC_SF2, SWSRC_LAST_SWITCH = SWSRC_SF2, -#elif defined(PCBTARANIS) +#elif defined(PCBTARANIS) || defined(PCBHORUS) SWSRC_SA0 = SWSRC_FIRST_SWITCH, SWSRC_SA1, SWSRC_SA2, diff --git a/radio/src/opentx.h b/radio/src/opentx.h index f9c2eb077..958499164 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -337,42 +337,23 @@ #include #endif -#if defined(PCBHORUS) - #define NUM_SWITCHES 5 // 8 physical switches + 6 possible from 3POS - #define NUM_SW_SRCRAW 8 - // #define SWSRC_THR SWSRC_SF2 - // #define SWSRC_GEA SWSRC_SG2 - // #define SWSRC_ID0 SWSRC_SA0 - // #define SWSRC_ID1 SWSRC_SA1 - // #define SWSRC_ID2 SWSRC_SA2 - // #define SW_DSM2_BIND SW_SH2 -#elif defined(PCBFLAMENCO) - #define NUM_SWITCHES 5 // 8 physical switches + 6 possible from 3POS - #define NUM_SW_SRCRAW 8 - // #define SWSRC_THR SWSRC_SF2 - // #define SWSRC_GEA SWSRC_SG2 - // #define SWSRC_ID0 SWSRC_SA0 - // #define SWSRC_ID1 SWSRC_SA1 - // #define SWSRC_ID2 SWSRC_SA2 - // #define SW_DSM2_BIND SW_SH2 -#elif defined(PCBTARANIS) +#if defined(PCBFLAMENCO) + #define NUM_SWITCHES 5 +#elif defined(PCBTARANIS) || defined(PCBHORUS) #if defined(REV9E) #define NUM_SWITCHES 18 // yes, it's a lot! #else #define NUM_SWITCHES 8 #endif - #define NUM_SW_SRCRAW 8 #define SWSRC_THR SWSRC_SF2 #define SWSRC_GEA SWSRC_SG2 #define SWSRC_ID0 SWSRC_SA0 #define SWSRC_ID1 SWSRC_SA1 #define SWSRC_ID2 SWSRC_SA2 - #define SW_DSM2_BIND SW_SH2 #else #define NUM_SWITCHES 7 #define IS_3POS(sw) ((sw) == 0) #define IS_MOMENTARY(sw) (sw == SWSRC_TRN) - #define NUM_SW_SRCRAW 1 #define SW_DSM2_BIND SW_TRN #endif diff --git a/radio/src/stamp.cpp b/radio/src/stamp.cpp index a99d289c0..ab235b38b 100644 --- a/radio/src/stamp.cpp +++ b/radio/src/stamp.cpp @@ -46,11 +46,17 @@ #define EEPROM_STR DEFNUMSTR(EEPROM_VER); #endif +#if defined(PCBHORUS) +#define TAB "\037\075" +#elif defined(PCBFLAMENCO) +#define TAB "\037\050" +#endif + #if defined(COLORLCD) - const pm_char vers_stamp[] PROGMEM = "VERS\037\050: " "opentx-" FLAVOUR "-" VERS_STR; - const pm_char date_stamp[] PROGMEM = "DATE\037\050: " DATE_STR; - const pm_char time_stamp[] PROGMEM = "TIME\037\050: " TIME_STR; - const pm_char eeprom_stamp[] PROGMEM = "EEPR\037\050: " EEPROM_STR; + const pm_char vers_stamp[] PROGMEM = "VERS" TAB ": " "opentx-" FLAVOUR "-" VERS_STR; + const pm_char date_stamp[] PROGMEM = "DATE" TAB ": " DATE_STR; + const pm_char time_stamp[] PROGMEM = "TIME" TAB ": " TIME_STR; + const pm_char eeprom_stamp[] PROGMEM = "EEPR" TAB ": " EEPROM_STR; #else const pm_char vers_stamp[] PROGMEM = "FW\037\033: " "opentx-" FLAVOUR "\036VERS\037\033: " VERS_STR "\036DATE\037\033: " DATE_STR"\036TIME\037\033: " TIME_STR "\036EEPR\037\033: " EEPROM_STR; #endif diff --git a/radio/src/switches.cpp b/radio/src/switches.cpp index 0c3988fc4..de03ab3c3 100644 --- a/radio/src/switches.cpp +++ b/radio/src/switches.cpp @@ -71,7 +71,7 @@ volatile GETSWITCH_RECURSIVE_TYPE s_last_switch_value = 0; #endif -#if defined(PCBFLAMENCO) || defined(PCBHORUS) +#if defined(PCBFLAMENCO) tmr10ms_t potsLastposStart[1]; uint8_t potsPos[1]; tmr10ms_t switchesMidposStart[2]; @@ -178,7 +178,7 @@ void getSwitchesPosition(bool startup) } #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBHORUS) #if defined(REV9E) tmr10ms_t switchesMidposStart[16]; #else @@ -697,7 +697,7 @@ swsrc_t getMovedSwitch() } } #endif -#elif defined(PCBTARANIS) +#elif defined(PCBTARANIS) || defined(PCBHORUS) for (int i=0; i?°ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz~ ≥→←↑↓ △Ⓘ""" +chars = u""" !"#$%&'()*+,-./0123456789:;<=>?°ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz~ ≥→←↑↓↗↘↙↖△""" def getCharWidth(fontsize, metrics, index): if index == 0: @@ -46,7 +46,12 @@ def createFontBitmap(filename, fontname, fontsize, foreground, background, coord metrics = QtGui.QFontMetrics(font) width = getFontWidth(fontsize, metrics) top, bottom = getFontTopBottom(fontsize, metrics) - image = QtGui.QImage(width, fontsize+1, QtGui.QImage.Format_RGB32) + extraImage = QtGui.QImage("fonts/extra_%dpx.png" % (fontsize+1)) + if extraImage.isNull(): + extraWidth = 0 + else: + extraWidth = extraImage.size().width() + image = QtGui.QImage(width + extraWidth, fontsize+1, QtGui.QImage.Format_RGB32) image.fill(background) painter = QtGui.QPainter() painter.begin(image) @@ -64,6 +69,9 @@ def createFontBitmap(filename, fontname, fontsize, foreground, background, coord rect = metrics.boundingRect(c) painter.drawText(width-rect.left(), fontsize-2, c) # fontsize-bottom+1 -17 / 7 width += getCharWidth(fontsize, metrics, i) + if extraWidth: + painter.drawImage(QtCore.QPoint(width, 0), extraImage) + coords.append(width) painter.end() image.save(filename + ".png") @@ -71,6 +79,10 @@ def createFontBitmap(filename, fontname, fontsize, foreground, background, coord f = file(filename + ".specs", "w") f.write("{ ") f.write(",".join(str(tmp) for tmp in coords)) + if extraWidth: + for w in (7, 20, 30, 41, 52, 57, 70, 80, 92, 104, 113, 126, 136): + f.write(", %d" % (int(coords[-1])+w)) + # f.write(file("fonts/extra_%dpx.specs" % fontsize).read()) f.write(" }") f.close() return coords From 4f5ce6b0f01f9255bdae5f7fab00983b4195e9ff Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 16 Oct 2015 23:09:26 +0200 Subject: [PATCH 28/74] [Horus] Main view added --- radio/src/Makefile | 4 +- radio/src/bitmaps/Horus/corsair.png | Bin 0 -> 21663 bytes ...p_polygon.png => mask_topmenu_polygon.png} | Bin radio/src/bitmaps/Horus/mask_trim_bg.png | Bin 158 -> 0 bytes .../src/bitmaps/Horus/mask_trim_frame_bg.png | Bin 440 -> 0 bytes radio/src/gui/Horus/bitmaps.cpp | 32 ++- radio/src/gui/Horus/gui.h | 10 +- radio/src/gui/Horus/lcd.cpp | 33 ++-- radio/src/gui/Horus/lcd.h | 1 + radio/src/gui/Horus/view_main.cpp | 186 +++++++++++++----- radio/src/gui/Horus/widgets.cpp | 25 ++- 11 files changed, 211 insertions(+), 80 deletions(-) create mode 100644 radio/src/bitmaps/Horus/corsair.png rename radio/src/bitmaps/Horus/{mask_top_polygon.png => mask_topmenu_polygon.png} (100%) delete mode 100644 radio/src/bitmaps/Horus/mask_trim_bg.png delete mode 100644 radio/src/bitmaps/Horus/mask_trim_frame_bg.png diff --git a/radio/src/Makefile b/radio/src/Makefile index 71eef86cd..11b91810c 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -1833,8 +1833,8 @@ translations/se.h: translations/se.h.txt ifeq ($(PCB), $(filter $(PCB), HORUS)) FONTNAME = DejaVu\ Sans - TINSIZE = 5 - SMLSIZE = 7 + TINSIZE = 9 + SMLSIZE = 13 STDSIZE = 16 MIDSIZE = 16 DBLSIZE = 20 diff --git a/radio/src/bitmaps/Horus/corsair.png b/radio/src/bitmaps/Horus/corsair.png new file mode 100644 index 0000000000000000000000000000000000000000..25b6def09506f5b156066cd4038f4662237d7d8c GIT binary patch literal 21663 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 diff --git a/radio/src/bitmaps/Horus/mask_trim_frame_bg.png b/radio/src/bitmaps/Horus/mask_trim_frame_bg.png deleted file mode 100644 index bf0f329f00bd001819ffb7011a648c98bf42f428..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440 zcmeAS@N?(olHy`uVBq!ia0vp^+(5jDgAGVd-){~i8CZ&)JVQ8upoSx*1IXtr@Q5r1 zs=p4xj7}P}D}aKMC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NHH)lntHl8 zhD5Z!y}2>#P=ZL?!}k*H0Sld4-ImW-xFR^)@#USP8-wrM)_xh9V$}WiQnrBR7rv&2 zUPlu?@Xe7)+Q)Zo=@QMGGUvGee=D%~^O~`qtGU7duwwo!-?(X~_iW3x+og9{!9u7> zCBAmP?&(GED`VURFPl8I>UvQv7d`Fh^ouVVrgyVi_p+H6o>^m7c&2Rcxk`yk9@}m| zXTN^*Z$iVGbE`w=glbht_c!kLYoEV4^4XQ-i$y2bJr*z(RP&tF4K$1OfRLv|+r*uR z3R+Y7vM^;a zGTj{Ce%I= 0) { + lcdDrawVerticalLine(x+8, y+3, 9, TEXT_INVERTED_COLOR); + } + if (dir <= 0) { + lcdDrawVerticalLine(x+2, y+3, 9, TEXT_INVERTED_COLOR); + } + // if (exttrim) { + // lcdDrawVerticalLine(xm, ym, 9, TEXT_INVERTED_COLOR); + // } +} + +void drawVerticalTrimPosition(coord_t x, coord_t y, int16_t dir) +{ + drawTrimSquare(x, y); + if (dir >= 0) { + lcdDrawHorizontalLine(x+1, y+4, 9, TEXT_INVERTED_COLOR); + } + if (dir <= 0) { + lcdDrawHorizontalLine(x+1, y+10, 9, TEXT_INVERTED_COLOR); + } + // if (exttrim) { + // lcdDrawHorizontalLine(xm-1, ym, 3, TEXT_INVERTED_COLOR); + // } +} + +void drawHorizontalStick(coord_t x, int val) +{ + for (int i=0; i<=160; i+=4) { + if (i==0 || i==80 || i==160) + lcdDrawVerticalLine(x+i, 250, 13, TEXT_COLOR); + else + lcdDrawVerticalLine(x+i, 252, 9, TEXT_COLOR); + } + drawHorizontalTrimPosition(x+TRIM_LEN+val*TRIM_LEN/RESX-4, TRIM_H_Y+16, val); +} + +void drawVerticalStick(coord_t x, int val) +{ + for (int i=0; i<=160; i+=4) { + if (i==0 || i==80 || i==160) + lcdDrawHorizontalLine(x, 56+i, 13, TEXT_COLOR); + else + lcdDrawHorizontalLine(x+2, 56+i, 9, TEXT_COLOR); + } + drawVerticalTrimPosition(x, TRIM_V_Y+val*TRIM_LEN/RESX-6, val); +} + +void drawSticks() +{ + drawVerticalStick(6, calibratedStick[1]); + drawHorizontalStick(TRIM_LH_X-TRIM_LEN+1, calibratedStick[0]); + drawVerticalStick(LCD_W-18, calibratedStick[2]); + drawHorizontalStick(TRIM_RH_X-TRIM_LEN+1, calibratedStick[3]); +} + +void displayTrims(uint8_t flightMode) +{ + g_model.displayTrims = DISPLAY_TRIMS_ALWAYS; + for (uint8_t i=0; i<4; i++) { static coord_t x[4] = {TRIM_LH_X, TRIM_LV_X, TRIM_RV_X, TRIM_RH_X}; - static uint8_t vert[4] = {0,1,1,0}; - coord_t xm, ym; - xm = x[CONVERT_MODE(i)]; + static uint8_t vert[4] = {0, 1, 1, 0}; + unsigned int stickIndex = CONVERT_MODE(i); + coord_t xm = x[stickIndex]; - uint8_t att = ROUND; - int16_t val = getTrimValue(phase, i); - - int16_t dir = val; + int32_t trim = getTrimValue(flightMode, i); + int32_t val = trim * TRIM_LEN / 125; bool exttrim = false; - if (val < -125 || val > 125) { + if (val < -(TRIM_LEN+1)) { + val = -(TRIM_LEN+1); + exttrim = true; + } + else if (val > (TRIM_LEN+1)) { + val = TRIM_LEN+1; exttrim = true; } - if (val < -(TRIM_LEN+1)*4) { - val = -(TRIM_LEN+1); - } - else if (val > (TRIM_LEN+1)*4) { - val = TRIM_LEN+1; - } - else { - val /= 4; - } - if (vert[i]) { - ym = 31; - lcd_vline(xm, ym-TRIM_LEN, TRIM_LEN*2); + coord_t ym = TRIM_V_Y; + lcdDrawBitmapPattern(xm, ym-TRIM_LEN, LBM_VTRIM_FRAME, TEXT_COLOR); if (i!=2 || !g_model.thrTrim) { - lcd_vline(xm-1, ym-1, 3); - lcd_vline(xm+1, ym-1, 3); + // TODO } ym -= val; - - // TODO lcdDrawFilledRect(xm-3, ym-3, 7, 7, SOLID, att); - if (dir >= 0) { - lcd_hline(xm-1, ym-1, 3); - } - if (dir <= 0) { - lcd_hline(xm-1, ym+1, 3); - } - if (exttrim) { - lcd_hline(xm-1, ym, 3); + drawVerticalTrimPosition(xm-1, ym-6, val); + if (g_model.displayTrims != DISPLAY_TRIMS_NEVER && trim != 0) { + if (g_model.displayTrims == DISPLAY_TRIMS_ALWAYS || (trimsDisplayTimer > 0 && (trimsDisplayMask & (1<0 ? 100 : 200, xm-2, trim, TINSIZE|VERTICAL); + } } } else { - ym = 60; - lcd_hline(xm-TRIM_LEN, ym, TRIM_LEN*2); - lcd_hline(xm-1, ym-1, 3); - lcd_hline(xm-1, ym+1, 3); + coord_t ym = TRIM_H_Y; + lcdDrawBitmapPattern(xm-TRIM_LEN, ym, LBM_HTRIM_FRAME, TEXT_COLOR); xm += val; - - // TODO lcdDrawFilledRect(xm-3, ym-3, 7, 7, SOLID, att); - if (dir >= 0) { - lcd_vline(xm+1, ym-1, 3); - } - if (dir <= 0) { - lcd_vline(xm-1, ym-1, 3); - } - if (exttrim) { - lcd_vline(xm, ym-1, 3); + drawHorizontalTrimPosition(xm-3, ym-2, val); + if (g_model.displayTrims != DISPLAY_TRIMS_NEVER && trim != 0) { + if (g_model.displayTrims == DISPLAY_TRIMS_ALWAYS || (trimsDisplayTimer > 0 && (trimsDisplayMask & (1<0 ? -20 : 50), ym+2, trim, TINSIZE); + } } } - lcdDrawSquare(xm-3, ym-3, 7, att); } } @@ -287,6 +335,11 @@ const uint16_t LBM_MAINVIEW_FLAT[] = { #include "../../bitmaps/Horus/mainview_flat.lbm" }; +const uint16_t LBM_CORSAIR[] = { +#include "../../bitmaps/Horus/corsair.lbm" +}; + + void menuMainView(evt_t event) { // clear the screen @@ -340,7 +393,32 @@ void menuMainView(evt_t event) break; } - lcdDrawBitmap(0, 0, LBM_MAINVIEW_FLAT); + // lcdDrawBitmap(0, 0, LBM_MAINVIEW_FLAT); + + // Header + lcdDrawFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); + lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); + lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); + lcdDrawTopmenuDatetime(); + + // Flight Mode Name + int mode = mixerCurrentFlightMode; + lcd_putsnAtt(212, 237, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|SMLSIZE); + + // Sticks + drawSticks(); + + // Trims + displayTrims(mode); + + // Model bitmap + lcdDrawBitmap(256, 104, LBM_CORSAIR); + + // Model name + lcdDrawBitmapPattern(256, 62, LBM_MODEL_ICON, MENU_TITLE_COLOR); + lcd_putsAtt(293, 68, "MyPlane Name", SMLSIZE); + lcdDrawHorizontalLine(287, 85, 140, TITLE_BGCOLOR); + #if 0 displayMainViewIndex(); diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 286affdb6..79c40423f 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -49,6 +49,27 @@ void displayColumnHeader(const char * const *headers, uint8_t index) // TODO ? displayHeader(headers[index]); } +const char * STR_MONTHS[] = { "Jan", "Fev", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +#define DATETIME_SEPARATOR_X 425 +#define DATETIME_LINE1 9 +#define DATETIME_LINE2 23 +#define DATETIME_LEFT(s) (LCD_W+DATETIME_SEPARATOR_X+8-getTextWidth(s, SMLSIZE))/2 + +void lcdDrawTopmenuDatetime() +{ + lcdDrawVerticalLine(DATETIME_SEPARATOR_X, 7, 31, TEXT_INVERTED_COLOR); + + struct gtm t; + gettime(&t); + char str[10]; + sprintf(str, "%d %s", t.tm_mday, STR_MONTHS[t.tm_mon]); + lcd_putsAtt(DATETIME_LEFT(str), DATETIME_LINE1, str, SMLSIZE|TEXT_INVERTED_COLOR); + + getTimerString(str, getValue(MIXSRC_TX_TIME)); + lcd_putsAtt(DATETIME_LEFT(str), DATETIME_LINE2, str, SMLSIZE|TEXT_INVERTED_COLOR); +} + void drawStick(coord_t centrex, int16_t xval, int16_t yval) { #define BOX_CENTERY (220 - FH - BOX_WIDTH/2) @@ -122,7 +143,7 @@ void drawMenuTemplate(const char * name, evt_t event) lcdDrawFilledRect(0, MENU_BODY_TOP, LCD_W, MENU_BODY_HEIGHT, TEXT_BGCOLOR); lcdDrawFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); - lcdDrawBitmapPattern(0, 0, LBM_TOP_POLYGON, TITLE_BGCOLOR); + lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); if (m_posVert < 0) { lcdDrawBitmapPattern(58+menuPageIndex*MENU_ICONS_SPACING-10, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); @@ -145,6 +166,8 @@ void drawMenuTemplate(const char * name, evt_t event) lcdDrawBitmapPattern(58+menuPageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); } + lcdDrawTopmenuDatetime(); + if (name) { // must be done at the end so that we can write something at the right of the menu title lcd_putsAtt(MENUS_MARGIN_LEFT, MENU_TITLE_TOP+2, name, MENU_TITLE_COLOR); From 8a3de9184bc107b3192bce8f6fb3044842af69f7 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 17 Oct 2015 11:12:35 +0200 Subject: [PATCH 29/74] [Horus] Main view continued --- radio/src/bitmaps/Horus/background.png | Bin 0 -> 59319 bytes radio/src/bitmaps/Horus/mask_htrim_frame.png | Bin 0 -> 485 bytes radio/src/bitmaps/Horus/mask_vtrim_frame.png | Bin 0 -> 440 bytes radio/src/gui/Horus/lcd.cpp | 14 +++++++------- radio/src/gui/Horus/view_main.cpp | 14 +++++++++----- 5 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 radio/src/bitmaps/Horus/background.png create mode 100644 radio/src/bitmaps/Horus/mask_htrim_frame.png create mode 100644 radio/src/bitmaps/Horus/mask_vtrim_frame.png diff --git a/radio/src/bitmaps/Horus/background.png b/radio/src/bitmaps/Horus/background.png new file mode 100644 index 0000000000000000000000000000000000000000..02fa6695d60aea67d8f79a89cb12ae23fcdb8536 GIT binary patch literal 59319 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 literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_htrim_frame.png b/radio/src/bitmaps/Horus/mask_htrim_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..964827b53a35ab1fc4593460519bad5a715def77 GIT binary patch literal 485 zcmVGCb|Fs0dh%1 zK~z}7?b)$Q0$~`(@!u=r`yWKdg?dn^2vUOQ&?1q}LEs1yA^@Ws&Q;#MUFFBq=3_Sd;6zG`1|N83siZ zg_`v`5fO=(;gBvf89em+IPm*l&ts$6gQOI2fe+ZWjSf<0?FeTQ_$cT%4Uj?0LQPzFd9>Wju)6L`2y4`QTAh6^q%7 zi?E$o%w|wj6`r`Rqtj|}5w;VZRts@m2j80c)BG=P&7Np1hGM0HbTWyCX~MB>j=BLU zC8U%bVuLfGW7}w$Ceq0yij@kYu^7bHpOwjIgqw01PyIgTj>E;+p9yFh;<}FOQVHQ` b6aer6qono`369gP2NHH)lntHl8 zhD5Z!y}2>#P=ZL?!}k*H0Sld4-ImW-xFR^)@#USP8-wrM)_xh9V$}WiQnrBR7rv&2 zUPlu?@Xe7)+Q)Zo=@QMGGUvGee=D%~^O~`qtGU7duwwo!-?(X~_iW3x+og9{!9u7> zCBAmP?&(GED`VURFPl8I>UvQv7d`Fh^ouVVrgyVi_p+H6o>^m7c&2Rcxk`yk9@}m| zXTN^*Z$iVGbE`w=glbht_c!kLYoEV4^4XQ-i$y2bJr*z(RP&tF4K$1OfRLv|+r*uR z3R+Y7vM^;a zGTj{Ce%I> 24); while (w--) { - if (pat&1) { - *p = color; - // lcd_mask(p, mask, 0); + if (pat & 1) { + lcdDrawTransparentPixel(p, opacity, color); pat = (pat >> 1) | 0x80; } else { diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index 734f2079a..3b147eefe 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -331,6 +331,10 @@ void onMainViewMenu(const char *result) void displayTelemetryScreen(int index, unsigned int evt); +const uint16_t LBM_MAINVIEW_BACKGROUND[] = { +#include "../../bitmaps/Horus/background.lbm" +}; + const uint16_t LBM_MAINVIEW_FLAT[] = { #include "../../bitmaps/Horus/mainview_flat.lbm" }; @@ -393,6 +397,7 @@ void menuMainView(evt_t event) break; } + lcdDrawBitmap(0, 0, LBM_MAINVIEW_BACKGROUND); // lcdDrawBitmap(0, 0, LBM_MAINVIEW_FLAT); // Header @@ -411,13 +416,12 @@ void menuMainView(evt_t event) // Trims displayTrims(mode); - // Model bitmap - lcdDrawBitmap(256, 104, LBM_CORSAIR); - - // Model name - lcdDrawBitmapPattern(256, 62, LBM_MODEL_ICON, MENU_TITLE_COLOR); + // Model panel + lcdDrawFilledRectWithAttributes(248, 58, 188, 158, SOLID, TEXT_BGCOLOR | (5<<24)); + lcdDrawBitmapPattern(256, 62, LBM_MODEL_ICON, TITLE_BGCOLOR); lcd_putsAtt(293, 68, "MyPlane Name", SMLSIZE); lcdDrawHorizontalLine(287, 85, 140, TITLE_BGCOLOR); + lcdDrawBitmap(256, 104, LBM_CORSAIR); #if 0 From cad1cc443284829c70329e342842199957997c67 Mon Sep 17 00:00:00 2001 From: mhotar Date: Mon, 19 Oct 2015 20:26:12 +0200 Subject: [PATCH 30/74] timer graprics --- radio/src/bitmaps/Horus/mask_noscale_ico.png | Bin 0 -> 2590 bytes radio/src/bitmaps/Horus/mask_rscale_p1.png | Bin 0 -> 453 bytes radio/src/bitmaps/Horus/mask_rscale_p13.png | Bin 0 -> 852 bytes radio/src/bitmaps/Horus/mask_rscale_p2.png | Bin 0 -> 520 bytes radio/src/bitmaps/Horus/mask_rscale_p3.png | Bin 0 -> 318 bytes radio/src/bitmaps/Horus/mask_timer_bg.png | Bin 0 -> 1318 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_noscale_ico.png create mode 100644 radio/src/bitmaps/Horus/mask_rscale_p1.png create mode 100644 radio/src/bitmaps/Horus/mask_rscale_p13.png create mode 100644 radio/src/bitmaps/Horus/mask_rscale_p2.png create mode 100644 radio/src/bitmaps/Horus/mask_rscale_p3.png create mode 100644 radio/src/bitmaps/Horus/mask_timer_bg.png diff --git a/radio/src/bitmaps/Horus/mask_noscale_ico.png b/radio/src/bitmaps/Horus/mask_noscale_ico.png new file mode 100644 index 0000000000000000000000000000000000000000..7038d41c8e878866db2e083028423f448ade0eab GIT binary patch literal 2590 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 literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_rscale_p13.png b/radio/src/bitmaps/Horus/mask_rscale_p13.png new file mode 100644 index 0000000000000000000000000000000000000000..9a93beb95c371a21805f77b8919458876124ca2f GIT binary patch literal 852 zcmV-a1FQUrP)b72(6&#kMMXwxPdv1r6VOc>~7GnJWtfP~Fv zGKiZy_C zT3T949<0_ictk`Zk;s1oP(?)g`ue_1YMLhf{rwV+MrCMds44&gU>L^7$c4jU#>dCm z*x2Ct`T1j9PESvA3579*KV79{WP?pR!0q^G9`5g`_fRSzJqY&Of<+8Tp{ zgQ%)nu#lRYn+v+)Du8L4jE;^H2n335MP1jc1K>Cgot>Q}*K!~b;NjuHH2_cEjEbVj zwzgd9z$X*V%*>Po5E1I?>e$`gbsxa8 zEZW-I%7SQUXdoVsy9)rYy}ex)fc!P((b18+001K+BV|K`LLp4kbQj?M{+`y>*0KPm zrltTmI5@zvEO!6^PEJnn`~5|W4-5>D%jHNWlW3YoQ&SW3^Ydgfnco8dY;JB+Q&aP0 z@o+dyI-TbA^_AY<-hy0XVn+=`B7)E7BNB;_N~OvN0NB~t z!RPZaHZ}%;ZQFErcUM&W-$*19l>q=G5(!>jUI3V$p8j!h5#jv&ymElN&d$z0y%#Et zo12@T1Hk))w70ka7~t{o@pl05?t_Jeg^~}MuOgex{vIH&OeVwf@-kgrU1b$l6a}bY z3@g$8{yq~E6LfTRd<#%tUk`u+AXU`u?XB$X?MXZym#eEQF$_Zt!w|=DB%Mx6)d9X! eE|(KkRpk#DDW^h`gqMf_00003mA^{ZE=l#+J4{g@f> zJP+Hpubx6Q8f82lYdL`*L)dIKuL4AbSS-eDHq#IAJdbQP>kAQ&$C=OPdI12F$;1~x zzJ_wST~}U&Kt;PfT^vIq zTHjti$k}2bzPmDpFj{*}D= zin#51=;$EP)I3OuCA^|X=$lbQ7KO; zb{0CFPUGa{1e&Hn(=_P1ZfQK9&xij0e)RVCqNJomP)c$W;f2m*GKfZ_h(sbdJUqnR z-JQtc0RSG42c4ar7#J9U-|vUV;}QN2%1=TI9gRk@zP^sDt1D4Meyg~+7(G2b7#ti# zO-+qs@1?&0(1psK3093+>eLMM|+tgWpf5{Zb+ zWQot`!_3SK>g(%eMFr3I_3$NFUP$-0!mKNDj z$VT7(zV-O{i23>Xye=~UJUl#Lc6Ju0r>C-`kd3}AbSMrJ7LkB@P5bR;Va zk!9IJo2H3yI4m;@k!9IJ9~>OynM(=^#Rh=fpq=Y1PQZlETUNk{L|AhMHsad9C#1CcOlzMn)PdQDBGQnE7; z3BwthT!k|3~; zKK*$8Yr3vudU_hXWr_Z}A6+z4QBi@Fl@Q65s$|a z4u=to#YAKx%9)TtzlcVoSYKb~XW~d2p@jy3OeTY9G>S+hg2Tf@+}+)Y9G>V)!VCRc zI-SPJ$q6(~gQjWFb=|I45dpBd(Epc6Byf3o3Bxd87zR?Q)SG8TQQ-A@;q`h^SXk&( cEOL?m05>$f!^Xs{9smFU07*qoM6N<$f|dnj^Z)<= literal 0 HcmV?d00001 From d353f3c3881fe5a21f52e0fd1d06c413f7cef7ec Mon Sep 17 00:00:00 2001 From: mhotar Date: Tue, 20 Oct 2015 20:14:10 +0200 Subject: [PATCH 31/74] round scale image --- radio/src/bitmaps/Horus/mask_rscale.png | Bin 0 -> 2604 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_rscale.png diff --git a/radio/src/bitmaps/Horus/mask_rscale.png b/radio/src/bitmaps/Horus/mask_rscale.png new file mode 100644 index 0000000000000000000000000000000000000000..967766871c16328b9ce69532a76a67812265bf7f GIT binary patch literal 2604 zcmV+{3e)w8P)0RSxl%w{tM1qIps9cb|UEx_pLC_Q`jjGjM#PAZj( z)M_>T`0;}P(Ad}*5fQCixsv#NKCNE8npUq~O>5V#rS03d)6ShcDJCYypY({}2fTj$ z8u|J8NJvNk5!r1LiNtCzkw_fcZQQsK1qB6o@#2L)sKRFg7z_qnzI+*}si_W=S=e|y zp4FGf<8ksyPEN+<%a>s=7<@(BdjU))6KZQ~A(zWJC9$wE3jDg3)MP zS^?VH+OTQUCbtuqZ(6MuJ3A0{{S{(TL5PH+y{b`uqFQ-{0@Ce@aSw zxy}DyC=}xM?c2T;ptiQwE`gUXUjhKmojd1{s8lLNUS1w5D=X37-VU`|ZTB1&iv=o` z3QbK-C@n3;zJ2=;9v<#6Zdq9w0N~A=HxP@(tYbVL5B2r+z7e3cwH16m-!6&R*jQ*Z z8kkHboAFH^7cXtB- zPMtc%iCcVpJTw}O!ux(jAd7VCr_Sm z4jvH^fwynp`Yl;U=;`T!OeW*Zv%S6DZX6o{W@cuvVZ#PaL07C;f!D8J`#o7l=<4c1 zXlN)Wu1cj6)6>&7F>C}VFE8g5P%4$8tE(#@i8@0^M+bs~gE?_7E-tq5X9+MnJByf@ z7?**>Vlg^9I|G)aD?E7c03wlyQ(wg}49k}#!1e3bIVpMU*s*{n$OR`(oZ!U$>eZ_( zKdS&1iv>wZNiG9~g@s{kY%Jgjx`I}#<zSE97J7Ro%J^Xz}K%|t%|K%w+=^-9>x9p_c1j!Wi|Zx@grtt zW&)Ik=P(+LtndHH$w@RfH{-~WBdmeb{HT-wps}&hZif*Z9E{z&cjMy4iw=je%MruF z!#IEbJhHN~Y=+A7?cTk60OkZZe*C!0-t)$d8%vp!TWDx#aG6hGVIhFIw?J=iughLG zAtAw~&2j-3uCHFbA|fI_5z)_|KV3SkU%%c0fvW%iD*s$tyb&QI5afW|7&zU zL|j}Pi9{l&HaeY-h=}+A==bm6PK}jHrO(I(0s?`6;^X6;+E@k9>-F^K&mX78|I+h6 z4_BPcW-}QK2I30@0++#NXJ>uID_{_d#V&2kW;5}_!otXv2tz|dzTy=yxZ*4j2q-)} z{BI{19v<%0_}jN{E+3QShOb}0Iz1i{5kWj2@2>z65fM&}jYgyOwec@tbaa%arly?Q z$mMb(ntKh##l^V{`Y$~ODwWEm&8k(ah-glLjEoGI4*!w>T)3vErxVef09&_iwQD36 zizzEBi_V`vPdPa`KBVYhWM^m7#fujyJ3HIq+rWC!0YI%*TL%ei)~vyiBS+BO+{~Il z`0(L_PfAk!4TfRx@#9D9@2RONw6wIKpr8P2*RHkZIWjT=V8Mh)U0odp2M1Zfe*OA| z3l}a}7d{tGBrI1{RaJqAkd~H)+S*#1`AfSKA`8JV3>q67v1iX7n<=sA=xDpsO3MX0 zoz7+@Stt}@@7}$*d-ty02_35ddc7Wp4jpnivpT;nzZ`Mq%o(TLBO)S@pP$dVh^%RG zE)`!gnary-e}7?gbd=LFSxQO@%a8R4T)A?E)8fL36DITN={8$1^ zPfv4Nb{2_5c<|ssKofL^M~@zHs#99EYL(5K%%&48E-vPzWN>gWIyyQ6mZT#*efkt3 zAt9VNmzS5@_}d6DJv|MjQpqV`XlUq?t`K*3ce^a%nQw`Si8c$PfP(^w_V#v8LFYTg zo}M1RC%YKEy}eGW$QHN9k00BO3k~k_$^@7q*3klIiK`tYeXpkti!G^Xk#iYPBdTDzf=H zO-)UO$z*~?qd{zJtX*z=J|8VDEiTW&sZ_SUzTRe6$HG$Cva&LVS0Ow+9Q*d|LuqL# znwpxRQmJ6ESnQrhtyZJGy&aX6mB`D>b6P)q?%X*5z{{5}?Uu*X)YNbinRBu9ty{NP zgQNLk>At=`PW3s9Z_SL13=9noL8Ve5Gc(ie9VbGe5bxf-1HfJj6bJ;kcI_J1wA|Y; z(c0RIkdP2oxqM1Wid)1NHgC)2H*el-{jxi7-~c2N38&lMV*!A_1&Z@M{n@i;Jtj{? z`10iozI^%ORg9vdA^?9s;5_*r`M=*XKAiTU(nq zDR{qa&S*5Es;bJZy50Ha>9#q!T#nk>T9`~GZ|VDf<79GjGN&Xw**KY+n!5ZpPTIqZ z7cWpyP=JjaH#$s)>(*JNQi=The7_ZC2W0bfXJ;pMb#>AA@83zSR+Cz-CX2;l-8`*O zC#Kcf^bTlcIO4_kw2W4hva$2?W27VX75f*NlH Date: Thu, 22 Oct 2015 07:09:53 +0200 Subject: [PATCH 32/74] [Horus] SD Storage started. UI continued. + Some boyscout work on LCD functions --- companion/src/file.cpp | 1 - .../firmwares/opentx/simulator/CMakeLists.txt | 48 +- .../opentx/simulator/opentxsimulator.cpp | 67 +-- radio/src/.gitignore | 2 + radio/src/Makefile | 186 ++++---- radio/src/audio_arm.cpp | 2 +- radio/src/bmp.cpp | 12 +- radio/src/cli.cpp | 2 + radio/src/gui/9X/helpers.cpp | 231 ---------- radio/src/gui/9X/lcd.cpp | 86 ++-- radio/src/gui/9X/lcd.h | 29 +- radio/src/gui/9X/menu_general_calib.cpp | 2 +- radio/src/gui/9X/menu_general_diagkeys.cpp | 2 +- radio/src/gui/9X/menu_general_hardware.cpp | 2 +- radio/src/gui/9X/menu_general_sdmanager.cpp | 4 +- radio/src/gui/9X/menu_general_setup.cpp | 8 +- radio/src/gui/9X/menu_general_trainer.cpp | 2 +- radio/src/gui/9X/menu_model.cpp | 4 +- radio/src/gui/9X/menu_model_curves.cpp | 6 +- .../gui/9X/menu_model_custom_functions.cpp | 10 +- radio/src/gui/9X/menu_model_inputs_mixes.cpp | 30 +- radio/src/gui/9X/menu_model_limits.cpp | 8 +- .../gui/9X/menu_model_logical_switches.cpp | 6 +- radio/src/gui/9X/menu_model_select.cpp | 12 +- radio/src/gui/9X/menu_model_setup.cpp | 26 +- radio/src/gui/9X/menu_model_telemetry.cpp | 8 +- radio/src/gui/9X/menus.h | 13 - radio/src/gui/9X/navigation.cpp | 4 +- radio/src/gui/9X/popups.cpp | 4 +- radio/src/gui/9X/view_main.cpp | 10 +- radio/src/gui/9X/view_mavlink.cpp | 14 +- radio/src/gui/9X/view_statistics.cpp | 4 +- radio/src/gui/9X/view_telemetry.cpp | 12 +- radio/src/gui/9X/widgets.cpp | 14 +- radio/src/gui/Horus/helpers.cpp | 388 ---------------- radio/src/gui/Horus/lcd.cpp | 292 ++++++++++-- radio/src/gui/Horus/lcd.h | 97 ++-- radio/src/gui/Horus/menu_general_calib.cpp | 2 +- .../src/gui/Horus/menu_general_sdmanager.cpp | 4 +- radio/src/gui/Horus/menu_general_setup.cpp | 11 +- radio/src/gui/Horus/menu_general_trainer.cpp | 2 +- radio/src/gui/Horus/menu_model.cpp | 4 +- radio/src/gui/Horus/menu_model_curves.cpp | 18 +- .../gui/Horus/menu_model_custom_functions.cpp | 14 +- radio/src/gui/Horus/menu_model_gvars.cpp | 8 +- .../src/gui/Horus/menu_model_inputs_mixes.cpp | 45 +- radio/src/gui/Horus/menu_model_limits.cpp | 2 +- .../gui/Horus/menu_model_logical_switches.cpp | 6 +- radio/src/gui/Horus/menu_model_select.cpp | 26 +- radio/src/gui/Horus/menu_model_setup.cpp | 52 +-- radio/src/gui/Horus/menu_model_telemetry.cpp | 6 +- radio/src/gui/Horus/menus.cpp | 4 +- radio/src/gui/Horus/menus.h | 15 +- radio/src/gui/Horus/navigation.cpp | 2 +- radio/src/gui/Horus/popups.cpp | 35 +- radio/src/gui/Horus/splash.cpp | 4 +- radio/src/gui/Horus/view_channels.cpp | 6 +- radio/src/gui/Horus/view_main.cpp | 71 +-- radio/src/gui/Horus/view_statistics.cpp | 14 +- radio/src/gui/Horus/view_telemetry.cpp | 14 +- radio/src/gui/Horus/widgets.cpp | 48 +- radio/src/gui/Taranis/helpers.cpp | 388 ---------------- radio/src/gui/Taranis/lcd.cpp | 114 ++--- radio/src/gui/Taranis/lcd.h | 25 +- radio/src/gui/Taranis/menu_general_calib.cpp | 2 +- .../src/gui/Taranis/menu_general_diagkeys.cpp | 2 +- .../gui/Taranis/menu_general_sdmanager.cpp | 8 +- radio/src/gui/Taranis/menu_general_setup.cpp | 8 +- .../src/gui/Taranis/menu_general_trainer.cpp | 2 +- .../src/gui/Taranis/menu_general_version.cpp | 23 +- radio/src/gui/Taranis/menu_model.cpp | 6 +- radio/src/gui/Taranis/menu_model_curves.cpp | 4 +- .../Taranis/menu_model_custom_functions.cpp | 26 +- .../gui/Taranis/menu_model_custom_scripts.cpp | 6 +- radio/src/gui/Taranis/menu_model_gvars.cpp | 8 +- .../gui/Taranis/menu_model_inputs_mixes.cpp | 32 +- radio/src/gui/Taranis/menu_model_limits.cpp | 6 +- .../Taranis/menu_model_logical_switches.cpp | 6 +- radio/src/gui/Taranis/menu_model_select.cpp | 14 +- radio/src/gui/Taranis/menu_model_setup.cpp | 36 +- .../src/gui/Taranis/menu_model_telemetry.cpp | 14 +- radio/src/gui/Taranis/menus.h | 17 - radio/src/gui/Taranis/navigation.cpp | 2 +- radio/src/gui/Taranis/popups.cpp | 4 +- radio/src/gui/Taranis/view_main.cpp | 14 +- radio/src/gui/Taranis/view_statistics.cpp | 4 +- radio/src/gui/Taranis/view_telemetry.cpp | 6 +- radio/src/gui/Taranis/widgets.cpp | 18 +- radio/src/gui/gui.h | 4 + radio/src/gui/gui_helpers.cpp | 436 ++++++++++++++++++ radio/src/gui/gui_helpers.h | 64 +++ radio/src/loadboot.cpp | 2 +- radio/src/logs.cpp | 10 +- radio/src/lua_api.cpp | 28 +- radio/src/main_arm.cpp | 10 +- radio/src/main_avr.cpp | 2 +- radio/src/myeeprom.h | 8 +- radio/src/opentx.cpp | 63 ++- radio/src/opentx.h | 31 +- radio/src/sdcard.cpp | 22 +- radio/src/sdcard.h | 6 +- radio/src/{ => storage}/eeprom_common.cpp | 44 +- radio/src/{ => storage}/eeprom_common.h | 22 +- .../src/{ => storage}/eeprom_conversions.cpp | 16 +- radio/src/{ => storage}/eeprom_raw.cpp | 135 +----- radio/src/{ => storage}/eeprom_raw.h | 3 - radio/src/{ => storage}/eeprom_rlc.cpp | 149 +----- radio/src/{ => storage}/eeprom_rlc.h | 7 +- radio/src/storage/sdcard_raw.cpp | 215 +++++++++ radio/src/storage/sdcard_raw.h | 39 ++ radio/src/storage/storage.h | 76 +++ radio/src/storage/storage_common.cpp | 129 ++++++ radio/src/switches.cpp | 6 +- .../src/targets/{stock => 9x}/board_stock.cpp | 0 radio/src/targets/{stock => 9x}/board_stock.h | 0 .../src/targets/{stock => 9x}/lcd_driver.cpp | 0 radio/src/targets/{stock => 9x}/voice.cpp | 0 radio/src/targets/{stock => 9x}/voice.h | 0 .../{gruvin9x => Gruvin9x}/board_gruvin9x.cpp | 0 .../{gruvin9x => Gruvin9x}/board_gruvin9x.h | 0 .../targets/{gruvin9x => Gruvin9x}/diskio.cpp | 0 .../{gruvin9x => Gruvin9x}/fuses_2561.txt | 0 .../{gruvin9x => Gruvin9x}/rtc_driver.cpp | 0 .../{gruvin9x => Gruvin9x}/somo14d.cpp | 0 .../targets/{gruvin9x => Gruvin9x}/somo14d.h | 0 radio/src/targets/Horus/board_horus.cpp | 8 - radio/src/targets/Horus/board_horus.h | 2 +- radio/src/targets/Horus/lcd_driver.cpp | 2 +- .../{mega2560 => Mega2560}/00readme.txt | 0 .../{mega2560 => Mega2560}/board_mega2560.cpp | 0 .../{mega2560 => Mega2560}/board_mega2560.h | 0 .../src/targets/{sky9x => Sky9x}/AT91SAM3S2.h | 0 .../src/targets/{sky9x => Sky9x}/AT91SAM3S4.h | 0 .../src/targets/{sky9x => Sky9x}/MEDSdcard.c | 4 +- radio/src/targets/{sky9x => Sky9x}/Media.h | 0 .../targets/{sky9x => Sky9x}/adc_driver.cpp | 0 .../targets/{sky9x => Sky9x}/audio_driver.cpp | 0 .../targets/{sky9x => Sky9x}/audio_driver.h | 0 .../targets/{sky9x => Sky9x}/bluetooth.cpp | 0 radio/src/targets/{sky9x => Sky9x}/board.h | 11 +- .../targets/{sky9x => Sky9x}/board_lowlevel.c | 2 +- .../targets/{sky9x => Sky9x}/board_sky9x.cpp | 0 .../targets/{sky9x => Sky9x}/board_sky9x.h | 0 .../{sky9x => Sky9x}/buzzer_driver.cpp | 0 radio/src/targets/{sky9x => Sky9x}/chip.h | 0 .../{sky9x => Sky9x}/coproc_driver.cpp | 0 radio/src/targets/{sky9x => Sky9x}/core_cm3.c | 0 radio/src/targets/{sky9x => Sky9x}/core_cm3.h | 0 radio/src/targets/{sky9x => Sky9x}/crt.c | 0 radio/src/targets/{sky9x => Sky9x}/diskio.cpp | 2 +- .../{sky9x => Sky9x}/eeprom_driver.cpp | 0 .../targets/{sky9x => Sky9x}/flash_driver.cpp | 0 .../{sky9x => Sky9x}/haptic_driver.cpp | 0 .../targets/{sky9x => Sky9x}/keys_driver.cpp | 0 .../targets/{sky9x => Sky9x}/lcd_driver.cpp | 0 .../targets/{sky9x => Sky9x}/massstorage.cpp | 2 +- .../{sky9x => Sky9x}/pulses_driver.cpp | 0 .../targets/{sky9x => Sky9x}/pwr_driver.cpp | 0 .../{sky9x => Sky9x}/rotenc_driver.cpp | 0 .../targets/{sky9x => Sky9x}/rtc_driver.cpp | 0 .../targets/{sky9x => Sky9x}/sam3s2c_flash.ld | 0 .../targets/{sky9x => Sky9x}/sam3s4c_flash.ld | 0 .../targets/{sky9x => Sky9x}/sam3s8c_flash.ld | 0 .../{sky9x => Sky9x}/sdcard_driver.cpp | 0 .../{sky9x => Sky9x}/serial2_driver.cpp | 0 .../{sky9x => Sky9x}/telemetry_driver.cpp | 0 .../common/core/USBConfigurationDescriptor.c | 2 +- .../common/core/USBConfigurationDescriptor.h | 6 +- .../usb/common/core/USBDeviceDescriptor.h | 4 +- .../core/USBDeviceQualifierDescriptor.h | 0 .../usb/common/core/USBEndpointDescriptor.c | 2 +- .../usb/common/core/USBEndpointDescriptor.h | 0 .../usb/common/core/USBFeatureRequest.c | 2 +- .../usb/common/core/USBFeatureRequest.h | 2 +- .../usb/common/core/USBGenericDescriptor.c | 2 +- .../usb/common/core/USBGenericDescriptor.h | 0 .../usb/common/core/USBGenericRequest.c | 2 +- .../usb/common/core/USBGenericRequest.h | 0 .../usb/common/core/USBGetDescriptorRequest.c | 2 +- .../usb/common/core/USBGetDescriptorRequest.h | 2 +- .../core/USBInterfaceAssociationDescriptor.h | 0 .../usb/common/core/USBInterfaceDescriptor.h | 0 .../usb/common/core/USBInterfaceRequest.c | 2 +- .../usb/common/core/USBInterfaceRequest.h | 2 +- .../usb/common/core/USBIrqHandler.c | 5 +- .../usb/common/core/USBIrqHandler.h | 0 .../usb/common/core/USBSetAddressRequest.c | 2 +- .../usb/common/core/USBSetAddressRequest.h | 2 +- .../common/core/USBSetConfigurationRequest.c | 2 +- .../common/core/USBSetConfigurationRequest.h | 2 +- .../usb/common/core/USBStringDescriptor.h | 0 .../common/massstorage/MSDeviceDescriptor.h | 0 .../massstorage/MSInterfaceDescriptor.h | 0 .../{sky9x => Sky9x}/usb/device/core/USBD.h | 4 +- .../usb/device/core/USBDDriver.c | 38 +- .../usb/device/core/USBDDriver.h | 4 +- .../usb/device/core/USBDDriverDescriptors.h | 6 +- .../usb/device/core/USBD_UDP.c | 8 +- .../usb/device/massstorage/MSD.h | 0 .../usb/device/massstorage/MSDDStateMachine.c | 5 +- .../usb/device/massstorage/MSDDStateMachine.h | 4 +- .../usb/device/massstorage/MSDDriver.c | 17 +- .../usb/device/massstorage/MSDDriver.h | 4 +- .../device/massstorage/MSDDriverDescriptors.c | 21 +- .../device/massstorage/MSDDriverDescriptors.h | 2 +- .../usb/device/massstorage/MSDIOFifo.h | 0 .../usb/device/massstorage/MSDLun.c | 5 +- .../usb/device/massstorage/MSDLun.h | 8 +- .../usb/device/massstorage/SBC.h | 0 .../usb/device/massstorage/SBCMethods.c | 8 +- .../usb/device/massstorage/SBCMethods.h | 5 +- .../targets/{sky9x => Sky9x}/vectors_sam3s.c | 0 .../{taranis => Taranis}/adc_driver.cpp | 0 radio/src/targets/{taranis => Taranis}/aspi.c | 2 +- radio/src/targets/{taranis => Taranis}/aspi.h | 0 .../{taranis => Taranis}/audio_driver.cpp | 0 .../{taranis => Taranis}/bluetooth_driver.cpp | 2 +- .../{taranis => Taranis}/board_taranis.cpp | 0 .../{taranis => Taranis}/board_taranis.h | 10 +- .../bootloader/.gitignore | 0 .../{taranis => Taranis}/bootloader/Makefile | 0 .../{taranis => Taranis}/bootloader/boot.cpp | 10 +- .../{taranis => Taranis}/bootloader/init.c | 0 .../{taranis => Taranis}/configure_pins.cpp | 0 .../src/targets/{taranis => Taranis}/delays.c | 2 +- .../targets/{taranis => Taranis}/diskio.cpp | 4 +- .../{taranis => Taranis}/flash_driver.cpp | 0 radio/src/targets/{taranis => Taranis}/hal.h | 0 .../{taranis => Taranis}/haptic_driver.cpp | 0 .../{taranis => Taranis}/i2c_driver.cpp | 2 +- .../{taranis => Taranis}/keys_driver.cpp | 8 +- .../{taranis => Taranis}/lcd_driver.cpp | 0 .../{taranis => Taranis}/pulses_driver.cpp | 0 .../targets/{taranis => Taranis}/pwr_driver.c | 2 +- .../{taranis => Taranis}/rtc_driver.cpp | 0 .../{taranis => Taranis}/serial2_driver.cpp | 0 .../{taranis => Taranis}/stm32_ramboot.ld | 0 .../{taranis => Taranis}/stm32f2_flash.ld | 0 .../{taranis => Taranis}/stm32f4_flash.ld | 0 .../{taranis => Taranis}/system_stm32f2xx.c | 0 .../{taranis => Taranis}/system_stm32f4xx.c | 0 .../{taranis => Taranis}/telemetry_driver.cpp | 0 .../{taranis => Taranis}/top_lcd_driver.cpp | 2 +- .../{taranis => Taranis}/trainer_driver.cpp | 0 .../targets/{taranis => Taranis}/usb_bsp.c | 5 +- .../targets/{taranis => Taranis}/usb_conf.h | 0 .../targets/{taranis => Taranis}/usb_driver.c | 2 +- .../targets/{taranis => Taranis}/usbd_cdc.cpp | 2 +- .../targets/{taranis => Taranis}/usbd_conf.h | 2 +- .../targets/{taranis => Taranis}/usbd_desc.c | 8 +- .../targets/{taranis => Taranis}/usbd_desc.h | 3 +- .../{taranis => Taranis}/usbd_hid_joystick.c | 2 +- .../{taranis => Taranis}/usbd_storage_msd.cpp | 4 +- .../targets/{taranis => Taranis}/usbd_usr.cpp | 0 radio/src/targets/simu/simpgmspace.cpp | 36 +- radio/src/telemetry/ardupilot.cpp | 2 +- radio/src/telemetry/frsky_d_arm.cpp | 2 +- radio/src/telemetry/frsky_sport.cpp | 2 +- radio/src/telemetry/jeti.cpp | 4 +- radio/src/telemetry/mavlink.cpp | 4 +- radio/src/telemetry/nmea.cpp | 30 +- radio/src/telemetry/telemetry.cpp | 2 +- radio/src/templates.cpp | 2 +- radio/src/tests/eeprom.cpp | 14 +- radio/src/tests/lcd.cpp | 88 ++-- ...4.png => lcdDrawHorizontalLine_212x64.png} | Bin ...line_212x64.png => lcdDrawLine_212x64.png} | Bin ...x64.png => lcdDrawVerticalLine_212x64.png} | Bin radio/src/timers.cpp | 4 +- radio/util/font2png.py | 17 +- 270 files changed, 2403 insertions(+), 2474 deletions(-) create mode 100644 radio/src/gui/gui_helpers.cpp create mode 100644 radio/src/gui/gui_helpers.h rename radio/src/{ => storage}/eeprom_common.cpp (68%) rename radio/src/{ => storage}/eeprom_common.h (70%) rename radio/src/{ => storage}/eeprom_conversions.cpp (96%) rename radio/src/{ => storage}/eeprom_raw.cpp (86%) rename radio/src/{ => storage}/eeprom_raw.h (97%) rename radio/src/{ => storage}/eeprom_rlc.cpp (88%) rename radio/src/{ => storage}/eeprom_rlc.h (98%) create mode 100644 radio/src/storage/sdcard_raw.cpp create mode 100644 radio/src/storage/sdcard_raw.h create mode 100644 radio/src/storage/storage.h create mode 100644 radio/src/storage/storage_common.cpp rename radio/src/targets/{stock => 9x}/board_stock.cpp (100%) rename radio/src/targets/{stock => 9x}/board_stock.h (100%) rename radio/src/targets/{stock => 9x}/lcd_driver.cpp (100%) rename radio/src/targets/{stock => 9x}/voice.cpp (100%) rename radio/src/targets/{stock => 9x}/voice.h (100%) rename radio/src/targets/{gruvin9x => Gruvin9x}/board_gruvin9x.cpp (100%) rename radio/src/targets/{gruvin9x => Gruvin9x}/board_gruvin9x.h (100%) rename radio/src/targets/{gruvin9x => Gruvin9x}/diskio.cpp (100%) rename radio/src/targets/{gruvin9x => Gruvin9x}/fuses_2561.txt (100%) rename radio/src/targets/{gruvin9x => Gruvin9x}/rtc_driver.cpp (100%) rename radio/src/targets/{gruvin9x => Gruvin9x}/somo14d.cpp (100%) rename radio/src/targets/{gruvin9x => Gruvin9x}/somo14d.h (100%) rename radio/src/targets/{mega2560 => Mega2560}/00readme.txt (100%) rename radio/src/targets/{mega2560 => Mega2560}/board_mega2560.cpp (100%) rename radio/src/targets/{mega2560 => Mega2560}/board_mega2560.h (100%) rename radio/src/targets/{sky9x => Sky9x}/AT91SAM3S2.h (100%) rename radio/src/targets/{sky9x => Sky9x}/AT91SAM3S4.h (100%) rename radio/src/targets/{sky9x => Sky9x}/MEDSdcard.c (96%) rename radio/src/targets/{sky9x => Sky9x}/Media.h (100%) rename radio/src/targets/{sky9x => Sky9x}/adc_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/audio_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/audio_driver.h (100%) rename radio/src/targets/{sky9x => Sky9x}/bluetooth.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/board.h (96%) rename radio/src/targets/{sky9x => Sky9x}/board_lowlevel.c (96%) rename radio/src/targets/{sky9x => Sky9x}/board_sky9x.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/board_sky9x.h (100%) rename radio/src/targets/{sky9x => Sky9x}/buzzer_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/chip.h (100%) rename radio/src/targets/{sky9x => Sky9x}/coproc_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/core_cm3.c (100%) rename radio/src/targets/{sky9x => Sky9x}/core_cm3.h (100%) rename radio/src/targets/{sky9x => Sky9x}/crt.c (100%) rename radio/src/targets/{sky9x => Sky9x}/diskio.cpp (96%) rename radio/src/targets/{sky9x => Sky9x}/eeprom_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/flash_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/haptic_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/keys_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/lcd_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/massstorage.cpp (94%) rename radio/src/targets/{sky9x => Sky9x}/pulses_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/pwr_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/rotenc_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/rtc_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/sam3s2c_flash.ld (100%) rename radio/src/targets/{sky9x => Sky9x}/sam3s4c_flash.ld (100%) rename radio/src/targets/{sky9x => Sky9x}/sam3s8c_flash.ld (100%) rename radio/src/targets/{sky9x => Sky9x}/sdcard_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/serial2_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/telemetry_driver.cpp (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBConfigurationDescriptor.c (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBConfigurationDescriptor.h (94%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBDeviceDescriptor.h (94%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBDeviceQualifierDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBEndpointDescriptor.c (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBEndpointDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBFeatureRequest.c (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBFeatureRequest.h (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBGenericDescriptor.c (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBGenericDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBGenericRequest.c (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBGenericRequest.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBGetDescriptorRequest.c (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBGetDescriptorRequest.h (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBInterfaceAssociationDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBInterfaceDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBInterfaceRequest.c (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBInterfaceRequest.h (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBIrqHandler.c (94%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBIrqHandler.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBSetAddressRequest.c (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBSetAddressRequest.h (94%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBSetConfigurationRequest.c (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBSetConfigurationRequest.h (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/core/USBStringDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/massstorage/MSDeviceDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/common/massstorage/MSInterfaceDescriptor.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/core/USBD.h (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/core/USBDDriver.c (92%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/core/USBDDriver.h (94%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/core/USBDDriverDescriptors.h (92%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/core/USBD_UDP.c (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSD.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDDStateMachine.c (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDDStateMachine.h (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDDriver.c (93%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDDriver.h (94%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDDriverDescriptors.c (93%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDDriverDescriptors.h (95%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDIOFifo.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDLun.c (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/MSDLun.h (94%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/SBC.h (100%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/SBCMethods.c (96%) rename radio/src/targets/{sky9x => Sky9x}/usb/device/massstorage/SBCMethods.h (95%) rename radio/src/targets/{sky9x => Sky9x}/vectors_sam3s.c (100%) rename radio/src/targets/{taranis => Taranis}/adc_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/aspi.c (92%) rename radio/src/targets/{taranis => Taranis}/aspi.h (100%) rename radio/src/targets/{taranis => Taranis}/audio_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/bluetooth_driver.cpp (95%) rename radio/src/targets/{taranis => Taranis}/board_taranis.cpp (100%) rename radio/src/targets/{taranis => Taranis}/board_taranis.h (95%) rename radio/src/targets/{taranis => Taranis}/bootloader/.gitignore (100%) rename radio/src/targets/{taranis => Taranis}/bootloader/Makefile (100%) rename radio/src/targets/{taranis => Taranis}/bootloader/boot.cpp (98%) rename radio/src/targets/{taranis => Taranis}/bootloader/init.c (100%) rename radio/src/targets/{taranis => Taranis}/configure_pins.cpp (100%) rename radio/src/targets/{taranis => Taranis}/delays.c (94%) rename radio/src/targets/{taranis => Taranis}/diskio.cpp (96%) rename radio/src/targets/{taranis => Taranis}/flash_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/hal.h (100%) rename radio/src/targets/{taranis => Taranis}/haptic_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/i2c_driver.cpp (96%) rename radio/src/targets/{taranis => Taranis}/keys_driver.cpp (95%) rename radio/src/targets/{taranis => Taranis}/lcd_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/pulses_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/pwr_driver.c (95%) rename radio/src/targets/{taranis => Taranis}/rtc_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/serial2_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/stm32_ramboot.ld (100%) rename radio/src/targets/{taranis => Taranis}/stm32f2_flash.ld (100%) rename radio/src/targets/{taranis => Taranis}/stm32f4_flash.ld (100%) rename radio/src/targets/{taranis => Taranis}/system_stm32f2xx.c (100%) rename radio/src/targets/{taranis => Taranis}/system_stm32f4xx.c (100%) rename radio/src/targets/{taranis => Taranis}/telemetry_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/top_lcd_driver.cpp (99%) rename radio/src/targets/{taranis => Taranis}/trainer_driver.cpp (100%) rename radio/src/targets/{taranis => Taranis}/usb_bsp.c (95%) rename radio/src/targets/{taranis => Taranis}/usb_conf.h (100%) rename radio/src/targets/{taranis => Taranis}/usb_driver.c (98%) rename radio/src/targets/{taranis => Taranis}/usbd_cdc.cpp (96%) rename radio/src/targets/{taranis => Taranis}/usbd_conf.h (96%) rename radio/src/targets/{taranis => Taranis}/usbd_desc.c (96%) rename radio/src/targets/{taranis => Taranis}/usbd_desc.h (94%) rename radio/src/targets/{taranis => Taranis}/usbd_hid_joystick.c (96%) rename radio/src/targets/{taranis => Taranis}/usbd_storage_msd.cpp (96%) rename radio/src/targets/{taranis => Taranis}/usbd_usr.cpp (100%) rename radio/src/tests/{lcd_hlineStip_212x64.png => lcdDrawHorizontalLine_212x64.png} (100%) rename radio/src/tests/{lcd_line_212x64.png => lcdDrawLine_212x64.png} (100%) rename radio/src/tests/{lcd_vlineStip_212x64.png => lcdDrawVerticalLine_212x64.png} (100%) diff --git a/companion/src/file.cpp b/companion/src/file.cpp index 4e7c5f353..d633fa4f8 100644 --- a/companion/src/file.cpp +++ b/companion/src/file.cpp @@ -36,7 +36,6 @@ eepromFatHeader(NULL) #define EEPROM_BLOCK_SIZE (4*1024) #define EEPROM_MARK 0x84697771 /* thanks ;) */ #define EEPROM_ZONE_SIZE (8*1024) -#define EEPROM_BUFFER_SIZE 256 #define EEPROM_FAT_SIZE 128 #define EEPROM_MAX_ZONES (EEPROM_SIZE / EEPROM_ZONE_SIZE) #define EEPROM_MAX_FILES (EEPROM_MAX_ZONES - 1) diff --git a/companion/src/firmwares/opentx/simulator/CMakeLists.txt b/companion/src/firmwares/opentx/simulator/CMakeLists.txt index 63531c07d..3a35e6ef7 100644 --- a/companion/src/firmwares/opentx/simulator/CMakeLists.txt +++ b/companion/src/firmwares/opentx/simulator/CMakeLists.txt @@ -118,63 +118,63 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") # 9X with ATmega64 -target_include_directories(opentx-9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/stock) -target_compile_definitions(opentx-9x${SUFFIX}-simulator PRIVATE PCBSTD PCB9X CPUM64 EEPROM_RLC ${COMMON_DEFINITIONS}) +target_include_directories(opentx-9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/9x) +target_compile_definitions(opentx-9x${SUFFIX}-simulator PRIVATE PCBSTD PCB9X CPUM64 EEPROM EEPROM_RLC ${COMMON_DEFINITIONS}) target_link_libraries(opentx-9x${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # 9XR with ATmega64 -target_include_directories(opentx-9xr${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/stock) -target_compile_definitions(opentx-9xr${SUFFIX}-simulator PRIVATE PCBSTD PCB9XR CPUM64 EEPROM_RLC ${COMMON_DEFINITIONS}) +target_include_directories(opentx-9xr${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/9x) +target_compile_definitions(opentx-9xr${SUFFIX}-simulator PRIVATE PCBSTD PCB9XR CPUM64 EEPROM EEPROM_RLC ${COMMON_DEFINITIONS}) target_link_libraries(opentx-9xr${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # 9X with ATmega128 -target_include_directories(opentx-9x128${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/stock) -target_compile_definitions(opentx-9x128${SUFFIX}-simulator PRIVATE PCBSTD PCB9X CPUM128 EEPROM_RLC ${COMMON_DEFINITIONS}) +target_include_directories(opentx-9x128${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/9x) +target_compile_definitions(opentx-9x128${SUFFIX}-simulator PRIVATE PCBSTD PCB9X CPUM128 EEPROM EEPROM_RLC ${COMMON_DEFINITIONS}) target_link_libraries(opentx-9x128${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # 9XR with ATmega128 -target_include_directories(opentx-9xr128${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/stock) -target_compile_definitions(opentx-9xr128${SUFFIX}-simulator PRIVATE PCBSTD PCB9XR CPUM128 EEPROM_RLC ${COMMON_DEFINITIONS}) +target_include_directories(opentx-9xr128${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/9x) +target_compile_definitions(opentx-9xr128${SUFFIX}-simulator PRIVATE PCBSTD PCB9XR CPUM128 EEPROM EEPROM_RLC ${COMMON_DEFINITIONS}) target_link_libraries(opentx-9xr128${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # Gruvin9x -target_include_directories(opentx-gruvin9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/gruvin9x) -target_compile_definitions(opentx-gruvin9x${SUFFIX}-simulator PRIVATE PCBGRUVIN9X CPUM2560 EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) +target_include_directories(opentx-gruvin9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Gruvin9x) +target_compile_definitions(opentx-gruvin9x${SUFFIX}-simulator PRIVATE PCBGRUVIN9X CPUM2560 EEPROM EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-gruvin9x${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # Mega2560 -target_include_directories(opentx-mega2560${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/mega2560) -target_compile_definitions(opentx-mega2560${SUFFIX}-simulator PRIVATE PCBMEGA2560 CPUM2560 EEPROM_RLC SDCARD ROTARY_ENCODERS=2 ${COMMON_DEFINITIONS}) +target_include_directories(opentx-mega2560${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Mega2560) +target_compile_definitions(opentx-mega2560${SUFFIX}-simulator PRIVATE PCBMEGA2560 CPUM2560 EEPROM EEPROM_RLC SDCARD ROTARY_ENCODERS=2 ${COMMON_DEFINITIONS}) target_link_libraries(opentx-mega2560${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # Sky9x -target_include_directories(opentx-sky9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/sky9x) -target_compile_definitions(opentx-sky9x${SUFFIX}-simulator PRIVATE PCBSKY9X CPUARM VOICE SDCARD ${COMMON_DEFINITIONS}) +target_include_directories(opentx-sky9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Sky9x) +target_compile_definitions(opentx-sky9x${SUFFIX}-simulator PRIVATE PCBSKY9X CPUARM VOICE EEPROM SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-sky9x${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # 9XR-PRO -target_include_directories(opentx-9xrpro${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/sky9x) -target_compile_definitions(opentx-9xrpro${SUFFIX}-simulator PRIVATE PCBSKY9X REVX CPUARM VOICE SDCARD ${COMMON_DEFINITIONS}) +target_include_directories(opentx-9xrpro${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Sky9x) +target_compile_definitions(opentx-9xrpro${SUFFIX}-simulator PRIVATE PCBSKY9X REVX CPUARM VOICE EEPROM SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-9xrpro${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # AR9X -target_include_directories(opentx-ar9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/sky9x) -target_compile_definitions(opentx-ar9x${SUFFIX}-simulator PRIVATE PCBSKY9X AR9X CPUARM VOICE SDCARD ${COMMON_DEFINITIONS}) +target_include_directories(opentx-ar9x${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Sky9x) +target_compile_definitions(opentx-ar9x${SUFFIX}-simulator PRIVATE PCBSKY9X AR9X CPUARM VOICE EEPROM SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-ar9x${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # Taranis standard -target_include_directories(opentx-taranis${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/taranis) -target_compile_definitions(opentx-taranis${SUFFIX}-simulator PRIVATE PCBTARANIS CPUARM CPUSTM32 EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) +target_include_directories(opentx-taranis${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Taranis) +target_compile_definitions(opentx-taranis${SUFFIX}-simulator PRIVATE PCBTARANIS CPUARM CPUSTM32 EEPROM EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-taranis${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # Taranis Plus -target_include_directories(opentx-taranisplus${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/taranis) -target_compile_definitions(opentx-taranisplus${SUFFIX}-simulator PRIVATE PCBTARANIS REVPLUS CPUARM CPUSTM32 EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) +target_include_directories(opentx-taranisplus${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Taranis) +target_compile_definitions(opentx-taranisplus${SUFFIX}-simulator PRIVATE PCBTARANIS REVPLUS CPUARM CPUSTM32 EEPROM EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-taranisplus${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) # Taranis X9E -target_include_directories(opentx-taranisx9e${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/taranis) -target_compile_definitions(opentx-taranisx9e${SUFFIX}-simulator PRIVATE PCBTARANIS REVPLUS REV9E CPUARM CPUSTM32 STM32F40_41xxx EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) +target_include_directories(opentx-taranisx9e${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Taranis) +target_compile_definitions(opentx-taranisx9e${SUFFIX}-simulator PRIVATE PCBTARANIS REVPLUS REV9E CPUARM CPUSTM32 STM32F40_41xxx EEPROM EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-taranisx9e${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) add_custom_target(opentx-simulators diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp index f81d7665e..861e0535b 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp @@ -97,15 +97,18 @@ namespace NAMESPACE { #include "radio/src/pulses/pulses_avr.cpp" #endif -#include "radio/src/eeprom_common.cpp" #if defined(EEPROM_RLC) -#include "radio/src/eeprom_rlc.cpp" -#else -#include "radio/src/eeprom_raw.cpp" +#include "radio/src/storage/eeprom_common.cpp" +#include "radio/src/storage/eeprom_rlc.cpp" +#elif defined(EEPROM) +#include "radio/src/storage/eeprom_common.cpp" +#include "radio/src/storage/eeprom_raw.cpp" +#elif defined(SDCARD) +#include "radio/src/storage/sdcard_raw.cpp" #endif #if defined(PCBTARANIS) || defined(PCBSKY9X) -#include "radio/src/eeprom_conversions.cpp" +#include "radio/src/storage/eeprom_conversions.cpp" #endif #include "radio/src/opentx.cpp" @@ -222,37 +225,37 @@ namespace NAMESPACE { #endif #if defined(PCBTARANIS) -#include "radio/src/targets/taranis/board_taranis.cpp" -#include "radio/src/targets/taranis/keys_driver.cpp" -#include "radio/src/targets/taranis/audio_driver.cpp" -#include "radio/src/targets/taranis/telemetry_driver.cpp" -#include "radio/src/targets/taranis/haptic_driver.cpp" +#include "radio/src/targets/Taranis/board_taranis.cpp" +#include "radio/src/targets/Taranis/keys_driver.cpp" +#include "radio/src/targets/Taranis/audio_driver.cpp" +#include "radio/src/targets/Taranis/telemetry_driver.cpp" +#include "radio/src/targets/Taranis/haptic_driver.cpp" #if defined(REV9E) -#include "radio/src/targets/taranis/top_lcd_driver.cpp" +#include "radio/src/targets/Taranis/top_lcd_driver.cpp" #endif -#include "radio/src/targets/taranis/pulses_driver.cpp" -#include "radio/src/targets/taranis/rtc_driver.cpp" -#include "radio/src/targets/taranis/trainer_driver.cpp" -#include "radio/src/targets/taranis/serial2_driver.cpp" +#include "radio/src/targets/Taranis/pulses_driver.cpp" +#include "radio/src/targets/Taranis/rtc_driver.cpp" +#include "radio/src/targets/Taranis/trainer_driver.cpp" +#include "radio/src/targets/Taranis/serial2_driver.cpp" #elif defined(PCBSKY9X) -#include "radio/src/targets/sky9x/board_sky9x.cpp" -#include "radio/src/targets/sky9x/telemetry_driver.cpp" -#include "radio/src/targets/sky9x/pwr_driver.cpp" -#include "radio/src/targets/sky9x/eeprom_driver.cpp" -#include "radio/src/targets/sky9x/keys_driver.cpp" -#include "radio/src/targets/sky9x/audio_driver.cpp" -#include "radio/src/targets/sky9x/sdcard_driver.cpp" -#include "radio/src/targets/sky9x/coproc_driver.cpp" -#include "radio/src/targets/sky9x/haptic_driver.cpp" -#include "radio/src/targets/sky9x/serial2_driver.cpp" -#include "radio/src/targets/sky9x/pulses_driver.cpp" +#include "radio/src/targets/Sky9x/board_sky9x.cpp" +#include "radio/src/targets/Sky9x/telemetry_driver.cpp" +#include "radio/src/targets/Sky9x/pwr_driver.cpp" +#include "radio/src/targets/Sky9x/eeprom_driver.cpp" +#include "radio/src/targets/Sky9x/keys_driver.cpp" +#include "radio/src/targets/Sky9x/audio_driver.cpp" +#include "radio/src/targets/Sky9x/sdcard_driver.cpp" +#include "radio/src/targets/Sky9x/coproc_driver.cpp" +#include "radio/src/targets/Sky9x/haptic_driver.cpp" +#include "radio/src/targets/Sky9x/serial2_driver.cpp" +#include "radio/src/targets/Sky9x/pulses_driver.cpp" #elif defined(PCBGRUVIN9X) -#include "radio/src/targets/gruvin9x/board_gruvin9x.cpp" -#include "radio/src/targets/gruvin9x/somo14d.cpp" +#include "radio/src/targets/Gruvin9x/board_gruvin9x.cpp" +#include "radio/src/targets/Gruvin9x/somo14d.cpp" #elif defined(PCBMEGA2560) -#include "radio/src/targets/mega2560/board_mega2560.cpp" +#include "radio/src/targets/Mega2560/board_mega2560.cpp" #else -#include "radio/src/targets/stock/board_stock.cpp" +#include "../../../../../radio/src/targets/9x/board_stock.cpp" #endif #include "radio/src/keys.cpp" @@ -458,9 +461,9 @@ void OpenTxSimulator::wheelEvent(int steps) { #if defined(REV9E) if (steps == 255) - x9de_rotenc -= 2; + rotencValue -= 2; else - x9de_rotenc += 2; + rotencValue += 2; #elif defined(PCBSKY9X) && !defined(REVX) g_rotenc[0] += steps*4; #elif defined(PCBGRUVIN9X) diff --git a/radio/src/.gitignore b/radio/src/.gitignore index 32e628d55..a33e0f3ed 100644 --- a/radio/src/.gitignore +++ b/radio/src/.gitignore @@ -19,6 +19,7 @@ /allsimusrc.cpp /BMP /traces +/RADIO /MODELS /FIRMWARES /EEPROMS @@ -27,4 +28,5 @@ /gtests.d /lua_exports* /lua_fields* +.directory diff --git a/radio/src/Makefile b/radio/src/Makefile index 11b91810c..515715e7d 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -438,14 +438,14 @@ ifeq ($(PCB), $(filter $(PCB), STD 9X 9XR)) OPT = s SDCARD = NO THR_TRACE = NO - INCDIRS += targets/stock + INCDIRS += targets/9x CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM64 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) - BOARDSRC = main_avr.cpp targets/stock/board_stock.cpp + BOARDSRC = main_avr.cpp targets/9x/board_stock.cpp EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/common_avr/telemetry_driver.cpp ifeq ($(GUI), YES) - EXTRABOARDSRC += targets/stock/lcd_driver.cpp + EXTRABOARDSRC += targets/9x/lcd_driver.cpp endif - EEPROMSRC = eeprom_common.cpp eeprom_rlc.cpp + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp PULSESSRC = pulses/pulses_avr.cpp CPPSRC += debug.cpp BITMAPS += bitmaps/9X/splash.lbm bitmaps/9X/asterisk.lbm bitmaps/9X/about.lbm @@ -482,7 +482,7 @@ ifeq ($(PCB), $(filter $(PCB), STD 9X 9XR)) ifeq ($(VOICE), YES) CPPDEFS += -DVOICE - CPPSRC += targets/stock/voice.cpp + CPPSRC += targets/9x/voice.cpp endif ifeq ($(PWM_BACKLIGHT), YES) @@ -521,10 +521,10 @@ ifeq ($(PCB), $(filter $(PCB), STD128 9X128 9XR128)) THR_TRACE = NO EEPROM_VARIANT += ${M128_VARIANT} CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM128 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) - INCDIRS += targets/stock - BOARDSRC = main_avr.cpp targets/stock/board_stock.cpp - EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/stock/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp - EEPROMSRC = eeprom_common.cpp eeprom_rlc.cpp + INCDIRS += targets/9x + BOARDSRC = main_avr.cpp targets/9x/board_stock.cpp + EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/9x/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp PULSESSRC = pulses/pulses_avr.cpp CPPSRC += debug.cpp BITMAPS += bitmaps/9X/splash.lbm bitmaps/9X/asterisk.lbm bitmaps/9X/about.lbm @@ -561,7 +561,7 @@ ifeq ($(PCB), $(filter $(PCB), STD128 9X128 9XR128)) ifeq ($(VOICE), YES) CPPDEFS += -DVOICE - CPPSRC += targets/stock/voice.cpp + CPPSRC += targets/9x/voice.cpp endif ifeq ($(PWM_BACKLIGHT), YES) @@ -596,10 +596,10 @@ ifeq ($(PCB), $(filter $(PCB), 9X2561)) THR_TRACE = YES EEPROM_VARIANT += ${M2561_VARIANT} CPPDEFS = -DF_CPU=$(F_CPU)UL -DPCBSTD -DCPUM2561 -DEEPROM -DEEPROM_RLC -DEEPROM_VARIANT=$(shell echo ${EEPROM_VARIANT} | bc) - INCDIRS += targets/stock - BOARDSRC = main_avr.cpp targets/stock/board_stock.cpp - EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/stock/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp - EEPROMSRC = eeprom_common.cpp eeprom_rlc.cpp + INCDIRS += targets/9x + BOARDSRC = main_avr.cpp targets/9x/board_stock.cpp + EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/9x/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp PULSESSRC = pulses/pulses_avr.cpp CPPSRC += debug.cpp BITMAPS += bitmaps/9X/splash.lbm bitmaps/9X/asterisk.lbm bitmaps/9X/about.lbm @@ -628,7 +628,7 @@ ifeq ($(PCB), $(filter $(PCB), 9X2561)) ifeq ($(VOICE), YES) CPPDEFS += -DVOICE - CPPSRC += targets/stock/voice.cpp + CPPSRC += targets/9x/voice.cpp endif ifeq ($(PWM_BACKLIGHT), YES) @@ -669,10 +669,10 @@ ifeq ($(PCB), GRUVIN9X) SDCARD = NO BUZZER = NO THR_TRACE = YES - INCDIRS += targets/gruvin9x targets/stock $(FATFSDIR) $(FATFSDIR)/option - BOARDSRC = main_avr.cpp targets/gruvin9x/board_gruvin9x.cpp - EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/stock/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp - EEPROMSRC = eeprom_common.cpp eeprom_rlc.cpp + INCDIRS += targets/Gruvin9x targets/9x $(FATFSDIR) $(FATFSDIR)/option + BOARDSRC = main_avr.cpp targets/Gruvin9x/board_gruvin9x.cpp + EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/9x/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp PULSESSRC = pulses/pulses_avr.cpp CPPSRC += audio_avr.cpp haptic.cpp debug.cpp BITMAPS += bitmaps/9X/splash.lbm bitmaps/9X/asterisk.lbm bitmaps/9X/about.lbm @@ -680,9 +680,9 @@ ifeq ($(PCB), GRUVIN9X) ifeq ($(SDCARD), YES) CPPDEFS += -DRTCLOCK -DSDCARD - CPPSRC += rtc.cpp sdcard.cpp logs.cpp targets/gruvin9x/rtc_driver.cpp + CPPSRC += rtc.cpp sdcard.cpp logs.cpp targets/Gruvin9x/rtc_driver.cpp GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp - EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/gruvin9x/diskio.cpp + EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Gruvin9x/diskio.cpp endif ifeq ($(BUZZER), YES) @@ -692,7 +692,7 @@ ifeq ($(PCB), GRUVIN9X) ifeq ($(VOICE), YES) CPPDEFS += -DVOICE - CPPSRC += targets/gruvin9x/somo14d.cpp + CPPSRC += targets/Gruvin9x/somo14d.cpp endif # Enable extra rotary encoders (Gruvin9x board only) @@ -715,25 +715,25 @@ ifeq ($(PCB), MEGA2560) BUZZER = YES THR_TRACE = YES PWRMANAGE = NO - INCDIRS += targets/mega2560 targets/stock $(FATFSDIR) $(FATFSDIR)/option - BOARDSRC = main_avr.cpp targets/mega2560/board_mega2560.cpp - EEPROMSRC = eeprom_common.cpp eeprom_rlc.cpp + INCDIRS += targets/Mega2560 targets/9x $(FATFSDIR) $(FATFSDIR)/option + BOARDSRC = main_avr.cpp targets/Mega2560/board_mega2560.cpp + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp PULSESSRC = pulses/pulses_avr.cpp CPPSRC += audio_avr.cpp debug.cpp BITMAPS += bitmaps/9X/splash.lbm bitmaps/9X/asterisk.lbm bitmaps/9X/about.lbm GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_diagkeys.cpp gui/$(GUIDIRECTORY)/menu_general_diaganas.cpp - EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/stock/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp + EXTRABOARDSRC = targets/common_avr/adc_driver.cpp targets/9x/lcd_driver.cpp targets/common_avr/telemetry_driver.cpp ifeq ($(PWRMANAGE), YES) CPPDEFS += -DPWRMANAGE endif ifeq ($(SDCARD), YES) - EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/gruvin9x/diskio.cpp + EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Gruvin9x/diskio.cpp CPPDEFS += -DSDCARD -DRTCLOCK GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp - CPPSRC += rtc.cpp sdcard.cpp logs.cpp targets/gruvin9x/rtc_driver.cpp + CPPSRC += rtc.cpp sdcard.cpp logs.cpp targets/Gruvin9x/rtc_driver.cpp endif ifeq ($(AUDIO), YES) @@ -751,7 +751,7 @@ ifeq ($(PCB), MEGA2560) ifeq ($(VOICE), YES) CPPDEFS += -DVOICE - CPPSRC += targets/gruvin9x/somo14d.cpp + CPPSRC += targets/Gruvin9x/somo14d.cpp endif ifeq ($(HAPTIC), YES) @@ -774,27 +774,27 @@ ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X)) CPPDEFS = -Dat91sam3s8 FLAVOUR = 9xrpro CPPDEFS += -DREVX - LDSCRIPT = targets/sky9x/sam3s8c_flash.ld + LDSCRIPT = targets/Sky9x/sam3s8c_flash.ld else ifeq ($(PCB), AR9X) CPPDEFS = -Dat91sam3s8 FLAVOUR = ar9x CPPDEFS += -DAR9X - LDSCRIPT = targets/sky9x/sam3s8c_flash.ld + LDSCRIPT = targets/Sky9x/sam3s8c_flash.ld else ifeq ($(PCBREV), REVA) FLAVOUR = sky9x CPPDEFS += -DREVA - LDSCRIPT = targets/sky9x/sam3s2c_flash.ld - CPPSRC += targets/sky9x/coproc_driver.cpp + LDSCRIPT = targets/Sky9x/sam3s2c_flash.ld + CPPSRC += targets/Sky9x/coproc_driver.cpp else ifeq ($(PCBREV), REVB) FLAVOUR = sky9x CPPDEFS += -DREVB -DCOPROCESSOR - LDSCRIPT = targets/sky9x/sam3s4c_flash.ld - CPPSRC += targets/sky9x/coproc_driver.cpp + LDSCRIPT = targets/Sky9x/sam3s4c_flash.ld + CPPSRC += targets/Sky9x/coproc_driver.cpp else FLAVOUR = sky9x CPPDEFS += -DREVC -DCOPROCESSOR - LDSCRIPT = targets/sky9x/sam3s4c_flash.ld - CPPSRC += targets/sky9x/coproc_driver.cpp + LDSCRIPT = targets/Sky9x/sam3s4c_flash.ld + CPPSRC += targets/Sky9x/coproc_driver.cpp endif TRGT = arm-none-eabi- MCU = cortex-m3 @@ -806,25 +806,25 @@ ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X)) CPPDEFS += -DPCBSKY9X -DCPUARM -DEEPROM -DEEPROM_VARIANT=0 -DAUDIO -DHAPTIC -DDSM2 -DDSM2_PPM -DPXX ifeq ($(PCB), SKY9X) CPPDEFS += -DROTARY_ENCODERS=1 - CPPSRC += targets/sky9x/rotenc_driver.cpp + CPPSRC += targets/Sky9x/rotenc_driver.cpp endif ifeq ($(SPORT_FILE_LOG), YES) CPPDEFS += -DSPORT_FILE_LOG endif - INCDIRS += targets/sky9x $(COOSDIR) $(COOSDIR)/kernel $(COOSDIR)/portable + INCDIRS += targets/Sky9x $(COOSDIR) $(COOSDIR)/kernel $(COOSDIR)/portable GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_hardware.cpp gui/$(GUIDIRECTORY)/menu_general_diagkeys.cpp gui/$(GUIDIRECTORY)/menu_general_diaganas.cpp - BOARDSRC = main_arm.cpp targets/sky9x/board_sky9x.cpp - EXTRABOARDSRC = targets/sky9x/lcd_driver.cpp - SRC += targets/sky9x/core_cm3.c targets/sky9x/board_lowlevel.c targets/sky9x/crt.c targets/sky9x/vectors_sam3s.c + BOARDSRC = main_arm.cpp targets/Sky9x/board_sky9x.cpp + EXTRABOARDSRC = targets/Sky9x/lcd_driver.cpp + SRC += targets/Sky9x/core_cm3.c targets/Sky9x/board_lowlevel.c targets/Sky9x/crt.c targets/Sky9x/vectors_sam3s.c SRC += $(COOSDIR)/kernel/core.c $(COOSDIR)/kernel/hook.c $(COOSDIR)/kernel/task.c $(COOSDIR)/kernel/event.c $(COOSDIR)/kernel/time.c $(COOSDIR)/kernel/timer.c $(COOSDIR)/kernel/flag.c $(COOSDIR)/kernel/mutex.c $(COOSDIR)/kernel/serviceReq.c $(COOSDIR)/portable/GCC/port.c $(COOSDIR)/portable/arch.c - SRC += targets/sky9x/usb/device/core/USBD_UDP.c targets/sky9x/usb/device/core/USBDDriver.c - SRC += targets/sky9x/usb/device/massstorage/MSDDriver.c targets/sky9x/usb/device/massstorage/MSDDStateMachine.c targets/sky9x/usb/device/massstorage/MSDLun.c targets/sky9x/usb/device/massstorage/MSDDriverDescriptors.c targets/sky9x/usb/device/massstorage/SBCMethods.c - SRC += targets/sky9x/usb/common/core/USBEndpointDescriptor.c targets/sky9x/usb/common/core/USBGenericRequest.c targets/sky9x/usb/common/core/USBFeatureRequest.c targets/sky9x/usb/common/core/USBInterfaceRequest.c targets/sky9x/usb/common/core/USBGetDescriptorRequest.c targets/sky9x/usb/common/core/USBSetAddressRequest.c targets/sky9x/usb/common/core/USBSetConfigurationRequest.c targets/sky9x/usb/common/core/USBConfigurationDescriptor.c targets/sky9x/usb/common/core/USBGenericDescriptor.c - SRC += targets/sky9x/MEDSdcard.c - EEPROMSRC = eeprom_common.cpp eeprom_raw.cpp eeprom_conversions.cpp + SRC += targets/Sky9x/usb/device/core/USBD_UDP.c targets/Sky9x/usb/device/core/USBDDriver.c + SRC += targets/Sky9x/usb/device/massstorage/MSDDriver.c targets/Sky9x/usb/device/massstorage/MSDDStateMachine.c targets/Sky9x/usb/device/massstorage/MSDLun.c targets/Sky9x/usb/device/massstorage/MSDDriverDescriptors.c targets/Sky9x/usb/device/massstorage/SBCMethods.c + SRC += targets/Sky9x/usb/common/core/USBEndpointDescriptor.c targets/Sky9x/usb/common/core/USBGenericRequest.c targets/Sky9x/usb/common/core/USBFeatureRequest.c targets/Sky9x/usb/common/core/USBInterfaceRequest.c targets/Sky9x/usb/common/core/USBGetDescriptorRequest.c targets/Sky9x/usb/common/core/USBSetAddressRequest.c targets/Sky9x/usb/common/core/USBSetConfigurationRequest.c targets/Sky9x/usb/common/core/USBConfigurationDescriptor.c targets/Sky9x/usb/common/core/USBGenericDescriptor.c + SRC += targets/Sky9x/MEDSdcard.c + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_raw.cpp storage/eeprom_conversions.cpp PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp pulses/dsm2_arm.cpp CPPSRC += tasks_arm.cpp audio_arm.cpp haptic.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp telemetry/telemetry.cpp - CPPSRC += targets/sky9x/telemetry_driver.cpp targets/sky9x/serial2_driver.cpp targets/sky9x/pwr_driver.cpp targets/sky9x/adc_driver.cpp targets/sky9x/eeprom_driver.cpp targets/sky9x/pulses_driver.cpp targets/sky9x/keys_driver.cpp targets/sky9x/audio_driver.cpp targets/sky9x/buzzer_driver.cpp targets/sky9x/haptic_driver.cpp targets/sky9x/sdcard_driver.cpp targets/sky9x/massstorage.cpp + CPPSRC += targets/Sky9x/telemetry_driver.cpp targets/Sky9x/serial2_driver.cpp targets/Sky9x/pwr_driver.cpp targets/Sky9x/adc_driver.cpp targets/Sky9x/eeprom_driver.cpp targets/Sky9x/pulses_driver.cpp targets/Sky9x/keys_driver.cpp targets/Sky9x/audio_driver.cpp targets/Sky9x/buzzer_driver.cpp targets/Sky9x/haptic_driver.cpp targets/Sky9x/sdcard_driver.cpp targets/Sky9x/massstorage.cpp CPPSRC += loadboot.cpp debug.cpp BITMAPS += bitmaps/9X/splash.lbm bitmaps/9X/asterisk.lbm bitmaps/9X/about.lbm ifeq ($(SDCARD), YES) @@ -832,7 +832,7 @@ ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X)) INCDIRS += $(FATFSDIR) $(FATFSDIR)/option CPPSRC += sdcard.cpp logs.cpp GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp - EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/sky9x/diskio.cpp + EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Sky9x/diskio.cpp endif ifeq ($(BUZZER), YES) CPPDEFS += -DBUZZER @@ -840,11 +840,11 @@ ifeq ($(PCB), $(filter $(PCB), SKY9X 9XRPRO AR9X)) endif ifeq ($(RTCLOCK), YES) CPPDEFS += -DRTCLOCK - CPPSRC += rtc.cpp targets/sky9x/rtc_driver.cpp + CPPSRC += rtc.cpp targets/Sky9x/rtc_driver.cpp endif ifeq ($(BLUETOOTH), YES) CPPDEFS += -DBLUETOOTH - CPPSRC += targets/sky9x/bluetooth.cpp + CPPSRC += targets/Sky9x/bluetooth.cpp endif ifeq ($(DEBUG), YES) SRC += syscalls.c @@ -867,7 +867,7 @@ ifeq ($(PCB), TARANIS) CPPDEFS += -DLCD_DUAL_BUFFER endif HAPTIC = YES - EXTRABOARDSRC += targets/taranis/bluetooth_driver.cpp + EXTRABOARDSRC += targets/Taranis/bluetooth_driver.cpp else ifeq ($(PCBREV), REVPLUS) MCU = cortex-m3 FLAVOUR = taranis-plus @@ -927,18 +927,18 @@ ifeq ($(PCB), TARANIS) PPM_CENTER_ADJUSTABLE = YES DSM2 = PPM CPPDEFS += -DPCBTARANIS -DCPUARM -DCPUSTM32 -DVIRTUALINPUTS -DLUAINPUTS -DXCURVES -DEEPROM -DEEPROM_RLC -DAUDIO -DPXX - INCDIRS += targets/taranis $(THIRDPARTY) $(COOSDIR) $(COOSDIR)/kernel $(COOSDIR)/portable + INCDIRS += targets/Taranis $(THIRDPARTY) $(COOSDIR) $(COOSDIR)/kernel $(COOSDIR)/portable CPPDEFS += -DHSE_VALUE=12000000 ifeq ($(PCBREV), REV9E) STM32LIBPATH = $(THIRDPARTY)/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries CPPDEFS += -DSTM32F4 -DSTM32F40_41xxx -DEEPROM_VARIANT=32768 INCDIRS += $(STM32LIBPATH)/CMSIS/Device/ST/STM32F4xx/Include $(STM32LIBPATH)/CMSIS/Include $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/inc - LDSCRIPT = targets/taranis/stm32f4_flash.ld + LDSCRIPT = targets/Taranis/stm32f4_flash.ld else STM32LIBPATH= $(THIRDPARTY)/STM32F2xx_StdPeriph_Lib_V1.1.0/Libraries CPPDEFS += -DEEPROM_VARIANT=0 INCDIRS += $(STM32LIBPATH)/STM32F2xx_StdPeriph_Driver/inc $(STM32LIBPATH)/CMSIS/Device/ST/STM32F2xx/Include $(STM32LIBPATH)/CMSIS/include - LDSCRIPT = targets/taranis/stm32f2_flash.ld + LDSCRIPT = targets/Taranis/stm32f2_flash.ld endif STM32USBPATH = $(THIRDPARTY)/STM32_USB-Host-Device_Lib_V2.1.0/Libraries INCDIRS += $(STM32USBPATH)/STM32_USB_OTG_Driver/inc @@ -947,19 +947,19 @@ ifeq ($(PCB), TARANIS) INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/hid/inc INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/cdc/inc GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_hardware.cpp - BOARDSRC = main_arm.cpp targets/taranis/board_taranis.cpp - EXTRABOARDSRC += targets/taranis/configure_pins.cpp targets/taranis/lcd_driver.cpp targets/taranis/flash_driver.cpp targets/taranis/i2c_driver.cpp targets/taranis/aspi.c targets/taranis/delays.c serial.cpp + BOARDSRC = main_arm.cpp targets/Taranis/board_taranis.cpp + EXTRABOARDSRC += targets/Taranis/configure_pins.cpp targets/Taranis/lcd_driver.cpp targets/Taranis/flash_driver.cpp targets/Taranis/i2c_driver.cpp targets/Taranis/aspi.c targets/Taranis/delays.c serial.cpp SRC += $(COOSDIR)/kernel/core.c $(COOSDIR)/kernel/hook.c $(COOSDIR)/kernel/task.c $(COOSDIR)/kernel/event.c $(COOSDIR)/kernel/time.c $(COOSDIR)/kernel/timer.c $(COOSDIR)/kernel/flag.c $(COOSDIR)/kernel/mutex.c $(COOSDIR)/kernel/serviceReq.c $(COOSDIR)/portable/GCC/port.c $(COOSDIR)/portable/arch.c - SRC += targets/taranis/pwr_driver.c targets/taranis/usb_driver.c - EEPROMSRC = eeprom_common.cpp eeprom_rlc.cpp eeprom_conversions.cpp + SRC += targets/Taranis/pwr_driver.c targets/Taranis/usb_driver.c + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_rlc.cpp storage/eeprom_conversions.cpp PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp CPPSRC += tasks_arm.cpp audio_arm.cpp sbus.cpp telemetry/telemetry.cpp - CPPSRC += targets/taranis/pulses_driver.cpp targets/taranis/keys_driver.cpp targets/taranis/adc_driver.cpp targets/taranis/trainer_driver.cpp targets/taranis/audio_driver.cpp targets/taranis/serial2_driver.cpp targets/taranis/telemetry_driver.cpp + CPPSRC += targets/Taranis/pulses_driver.cpp targets/Taranis/keys_driver.cpp targets/Taranis/adc_driver.cpp targets/Taranis/trainer_driver.cpp targets/Taranis/audio_driver.cpp targets/Taranis/serial2_driver.cpp targets/Taranis/telemetry_driver.cpp CPPSRC += bmp.cpp gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp CPPSRC += loadboot.cpp ifeq ($(PCBREV), REV9E) - CPPSRC += targets/taranis/top_lcd_driver.cpp - SRC += targets/taranis/system_stm32f4xx.c + CPPSRC += targets/Taranis/top_lcd_driver.cpp + SRC += targets/Taranis/system_stm32f4xx.c SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/misc.c SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dbgmcu.c @@ -972,7 +972,7 @@ ifeq ($(PCB), TARANIS) SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_usart.c SRC += $(STM32LIBPATH)/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc_ride7/startup_stm32f40_41xxx.s else - SRC += targets/taranis/system_stm32f2xx.c + SRC += targets/Taranis/system_stm32f2xx.c SRC += $(STM32LIBPATH)/STM32F2xx_StdPeriph_Driver/src/misc.c SRC += $(STM32LIBPATH)/STM32F2xx_StdPeriph_Driver/src/stm32f2xx_gpio.c SRC += $(STM32LIBPATH)/STM32F2xx_StdPeriph_Driver/src/stm32f2xx_dbgmcu.c @@ -985,29 +985,29 @@ ifeq ($(PCB), TARANIS) SRC += $(STM32LIBPATH)/STM32F2xx_StdPeriph_Driver/src/stm32f2xx_usart.c SRC += $(STM32LIBPATH)/CMSIS/Device/ST/STM32F2xx/Source/Templates/gcc_ride7/startup_stm32f2xx.s endif - SRC += targets/taranis/usb_bsp.c targets/taranis/usbd_desc.c + SRC += targets/Taranis/usb_bsp.c targets/Taranis/usbd_desc.c SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_core.c SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_dcd.c SRC += $(STM32USBPATH)/STM32_USB_OTG_Driver/src/usb_dcd_int.c SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_core.c SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_ioreq.c SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Core/src/usbd_req.c - EXTRABOARDSRC += targets/taranis/usbd_usr.cpp + EXTRABOARDSRC += targets/Taranis/usbd_usr.cpp SRC += syscalls.c ifeq ($(USB), JOYSTICK) CPPDEFS += -DUSB_JOYSTICK - SRC += targets/taranis/usbd_hid_joystick.c + SRC += targets/Taranis/usbd_hid_joystick.c else ifeq ($(USB), SERIAL) CPPDEFS += -DUSB_SERIAL SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/cdc/src/usbd_cdc_core.c - EXTRABOARDSRC += targets/taranis/usbd_cdc.cpp + EXTRABOARDSRC += targets/Taranis/usbd_cdc.cpp else CPPDEFS += -DUSB_MASS_STORAGE SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_data.c SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_scsi.c SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_bot.c SRC += $(STM32USBPATH)/STM32_USB_Device_Library/Class/msc/src/usbd_msc_core.c - EXTRABOARDSRC += targets/taranis/usbd_storage_msd.cpp + EXTRABOARDSRC += targets/Taranis/usbd_storage_msd.cpp endif ifeq ($(NANO), YES) # use newlib-nano for linking @@ -1035,8 +1035,8 @@ ifeq ($(PCB), TARANIS) CPPSRC += bin_allocator.cpp endif endif - EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/taranis/diskio.cpp - CPPSRC += sdcard.cpp logs.cpp rtc.cpp targets/taranis/rtc_driver.cpp + EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Taranis/diskio.cpp + CPPSRC += sdcard.cpp logs.cpp rtc.cpp targets/Taranis/rtc_driver.cpp GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp gui/$(GUIDIRECTORY)/menu_general_diagkeys.cpp gui/$(GUIDIRECTORY)/menu_general_diaganas.cpp CPPDEFS += -DSDCARD -DVOICE -DRTCLOCK INCDIRS += $(FATFSDIR) $(FATFSDIR)/option @@ -1046,12 +1046,12 @@ ifeq ($(PCB), TARANIS) ifeq ($(HUBSAN), YES) CPPDEFS += -DHUBSAN CPPSRC += pulses/hubsan.cpp - CPPSRC += targets/taranis/a7105_driver.cpp + CPPSRC += targets/Taranis/a7105_driver.cpp endif ifeq ($(HAPTIC), YES) CPPDEFS += -DHAPTIC CPPSRC += haptic.cpp - CPPSRC += targets/taranis/haptic_driver.cpp + CPPSRC += targets/Taranis/haptic_driver.cpp endif ifeq ($(MIXERS_MONITOR), YES) CPPDEFS += -DMIXERS_MONITOR @@ -1097,10 +1097,10 @@ ifeq ($(PCB), FLAMENCO) INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/hid/inc INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/cdc/inc BOARDSRC = main_arm.cpp targets/Flamenco/board_flamenco.cpp - EXTRABOARDSRC += targets/Flamenco/lcd_driver.cpp targets/Flamenco/delays.c targets/Flamenco/i2c_driver.cpp targets/taranis/configure_pins.cpp serial.cpp # targets/taranis/flash_driver.cpp + EXTRABOARDSRC += targets/Flamenco/lcd_driver.cpp targets/Flamenco/delays.c targets/Flamenco/i2c_driver.cpp targets/Taranis/configure_pins.cpp serial.cpp # targets/Taranis/flash_driver.cpp SRC += $(COOSDIR)/kernel/core.c $(COOSDIR)/kernel/hook.c $(COOSDIR)/kernel/task.c $(COOSDIR)/kernel/event.c $(COOSDIR)/kernel/time.c $(COOSDIR)/kernel/timer.c $(COOSDIR)/kernel/flag.c $(COOSDIR)/kernel/mutex.c $(COOSDIR)/kernel/serviceReq.c $(COOSDIR)/portable/GCC/port.c $(COOSDIR)/portable/arch.c SRC += targets/Flamenco/pwr_driver.c targets/Flamenco/usb_driver.c - EEPROMSRC = eeprom_common.cpp eeprom_raw.cpp + EEPROMSRC = storage/storage_common.cpp storage/eeprom_common.cpp storage/eeprom_raw.cpp PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp CPPSRC += tasks_arm.cpp audio_arm.cpp sbus.cpp telemetry/telemetry.cpp CPPSRC += targets/Flamenco/tw8823_driver.cpp targets/Flamenco/pulses_driver.cpp targets/Flamenco/keys_driver.cpp targets/Flamenco/adc_driver.cpp targets/Flamenco/eeprom_driver.cpp targets/Flamenco/trainer_driver.cpp targets/Flamenco/audio_driver.cpp targets/Flamenco/serial2_driver.cpp targets/Flamenco/telemetry_driver.cpp @@ -1157,7 +1157,7 @@ ifeq ($(PCB), FLAMENCO) EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Flamenco/diskio.cpp CPPDEFS += -DSDCARD -DVOICE -DRTCLOCK - CPPSRC += sdcard.cpp rtc.cpp logs.cpp targets/taranis/rtc_driver.cpp + CPPSRC += sdcard.cpp rtc.cpp logs.cpp targets/Taranis/rtc_driver.cpp GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp INCDIRS += $(FATFSDIR) $(FATFSDIR)/option @@ -1219,7 +1219,7 @@ ifeq ($(PCB), HORUS) INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/hid/inc INCDIRS += $(STM32USBPATH)/STM32_USB_Device_Library/Class/cdc/inc BOARDSRC = main_arm.cpp targets/Horus/board_horus.cpp targets/Horus/led_driver.cpp - EXTRABOARDSRC += targets/Horus/lcd_driver.cpp targets/Horus/delays.c targets/Horus/i2c_driver.cpp targets/taranis/configure_pins.cpp serial.cpp # targets/taranis/flash_driver.cpp + EXTRABOARDSRC += targets/Horus/lcd_driver.cpp targets/Horus/delays.c targets/Horus/i2c_driver.cpp targets/Taranis/configure_pins.cpp serial.cpp # targets/Taranis/flash_driver.cpp SRC += $(COOSDIR)/kernel/core.c $(COOSDIR)/kernel/hook.c $(COOSDIR)/kernel/task.c $(COOSDIR)/kernel/event.c $(COOSDIR)/kernel/time.c $(COOSDIR)/kernel/timer.c $(COOSDIR)/kernel/flag.c $(COOSDIR)/kernel/mutex.c $(COOSDIR)/kernel/serviceReq.c $(COOSDIR)/portable/GCC/port.c $(COOSDIR)/portable/arch.c SRC += targets/Horus/pwr_driver.c targets/Horus/usb_driver.c targets/Horus/sdram_driver.c PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp @@ -1227,6 +1227,7 @@ ifeq ($(PCB), HORUS) CPPSRC += targets/Horus/pulses_driver.cpp targets/Horus/keys_driver.cpp targets/Horus/adc_driver.cpp targets/Horus/trainer_driver.cpp targets/Horus/audio_driver.cpp targets/Horus/serial2_driver.cpp targets/Horus/telemetry_driver.cpp CPPSRC += gui/$(GUIDIRECTORY)/bitmaps.cpp gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp SRC += targets/Horus/system_stm32f4xx.c + CPPSRC += storage/storage_common.cpp storage/sdcard_raw.cpp SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/misc.c SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c SRC += $(STM32LIBPATH)/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dbgmcu.c @@ -1286,7 +1287,7 @@ ifeq ($(PCB), HORUS) EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Horus/diskio.cpp CPPDEFS += -DSDCARD -DVOICE -DRTCLOCK - CPPSRC += sdcard.cpp rtc.cpp logs.cpp targets/taranis/rtc_driver.cpp + CPPSRC += sdcard.cpp rtc.cpp logs.cpp targets/Taranis/rtc_driver.cpp GUIGENERALSRC += gui/$(GUIDIRECTORY)/menu_general_sdmanager.cpp INCDIRS += $(FATFSDIR) $(FATFSDIR)/option @@ -1332,6 +1333,9 @@ endif ifeq ($(GUI), YES) GUISRC = gui/$(GUIDIRECTORY)/helpers.cpp gui/$(GUIDIRECTORY)/navigation.cpp gui/$(GUIDIRECTORY)/popups.cpp gui/$(GUIDIRECTORY)/widgets.cpp gui/$(GUIDIRECTORY)/menus.cpp $(GUIMODELSRC) $(GUIGENERALSRC) gui/$(GUIDIRECTORY)/view_main.cpp gui/$(GUIDIRECTORY)/view_statistics.cpp + ifeq ($(ARCH), ARM) + GUISRC += gui/gui_helpers.cpp + endif CPPDEFS += -DGUI endif @@ -1736,11 +1740,11 @@ endif # build bootloader ifeq ($(PCB), $(filter $(PCB), TARANIS)) -bootloader.lbm: targets/taranis/bootloader/bootloader_ramBoot.bin +bootloader.lbm: targets/Taranis/bootloader/bootloader_ramBoot.bin $(BIN2LBM) $^ $@ -.PHONY targets/taranis/bootloader/bootloader_ramBoot.bin: +.PHONY targets/Taranis/bootloader/bootloader_ramBoot.bin: @echo "Building bootloader..." - $(MAKE) -C targets/taranis/bootloader PCB=$(PCB) PCBREV=$(PCBREV) NANO=$(NANO) WARNINGS_AS_ERRORS=$(WARNINGS_AS_ERRORS) + $(MAKE) -C targets/Taranis/bootloader PCB=$(PCB) PCBREV=$(PCBREV) NANO=$(NANO) WARNINGS_AS_ERRORS=$(WARNINGS_AS_ERRORS) else ifeq ($(PCB), $(filter $(PCB), 9XRPRO AR9X)) bootloader.lbm: @echo "Fetching Mike's bootloader..." @@ -1836,9 +1840,10 @@ ifeq ($(PCB), $(filter $(PCB), HORUS)) TINSIZE = 9 SMLSIZE = 13 STDSIZE = 16 - MIDSIZE = 16 - DBLSIZE = 20 - XXLSIZE = 38 + MIDSIZE = 24 + DBLSIZE = 32 + DBLBOLD = True + XXLSIZE = 48 MASKFORMAT = 8bits else FONTNAME = Roboto @@ -1847,6 +1852,7 @@ else STDSIZE = 8 MIDSIZE = 11 DBLSIZE = 14 + DBLBOLD = False XXLSIZE = 38 MASKFORMAT = 4bits endif @@ -1858,22 +1864,22 @@ fonts/font_%size.lbm: fonts/font_%size.png $(IMG2LBM) $^ $@ $(LCDSIZE) $(MASKFORMAT) fonts/font_tinsize.png: - $(FONT2PNG) $(FONTNAME) $(TINSIZE) fonts/font_tinsize + $(FONT2PNG) $(FONTNAME) $(TINSIZE) False fonts/font_tinsize fonts/font_smlsize.png: - $(FONT2PNG) $(FONTNAME) $(SMLSIZE) fonts/font_smlsize + $(FONT2PNG) $(FONTNAME) $(SMLSIZE) False fonts/font_smlsize fonts/font_stdsize.png: - $(FONT2PNG) $(FONTNAME) $(STDSIZE) fonts/font_stdsize + $(FONT2PNG) $(FONTNAME) $(STDSIZE) False fonts/font_stdsize fonts/font_midsize.png: - $(FONT2PNG) $(FONTNAME) $(MIDSIZE) fonts/font_midsize + $(FONT2PNG) $(FONTNAME) $(MIDSIZE) False fonts/font_midsize fonts/font_dblsize.png: - $(FONT2PNG) $(FONTNAME) $(DBLSIZE) fonts/font_dblsize + $(FONT2PNG) $(FONTNAME) $(DBLSIZE) $(DBLBOLD) fonts/font_dblsize fonts/font_xxlsize.png: - $(FONT2PNG) $(FONTNAME) $(XXLSIZE) fonts/font_xxlsize + $(FONT2PNG) $(FONTNAME) $(XXLSIZE) False fonts/font_xxlsize fonts/%.lbm: fonts/%.png $(IMG2LBM) $^ $@ $(LCDSIZE) @@ -2090,7 +2096,7 @@ clean_list : $(REMOVE) fonts/font_* $(REMOVE) fonts/*/*.lbm $(REMOVE) lua_exports* lua_fields.txt - $(MAKE) -C targets/taranis/bootloader clean + $(MAKE) -C targets/Taranis/bootloader clean #### Install diff --git a/radio/src/audio_arm.cpp b/radio/src/audio_arm.cpp index 58962c696..ca7254927 100644 --- a/radio/src/audio_arm.cpp +++ b/radio/src/audio_arm.cpp @@ -290,7 +290,7 @@ char * getModelAudioPath(char * path) { strcpy(path, SOUNDS_PATH "/"); strncpy(path+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); - char * result = strcat_modelname(path+sizeof(SOUNDS_PATH), g_eeGeneral.currModel); + char * result = strcat_currentmodelname(path+sizeof(SOUNDS_PATH)); *result++ = '/'; *result = '\0'; return result; diff --git a/radio/src/bmp.cpp b/radio/src/bmp.cpp index 7051e4703..3d76c2c51 100644 --- a/radio/src/bmp.cpp +++ b/radio/src/bmp.cpp @@ -228,23 +228,19 @@ const char *writeScreenshot() FIL bmpFile; UINT written; char filename[42]; // /SCREENSHOTS/screen-2013-01-01-123540.bmp - DIR folder; // check and create folder here strcpy_P(filename, SCREENSHOTS_PATH); - FRESULT result = f_opendir(&folder, filename); - if (result != FR_OK) { - if (result == FR_NO_PATH) - result = f_mkdir(filename); - if (result != FR_OK) - return SDCARD_ERROR(result); + const char * error = sdCheckAndCreateDirectory(filename); + if (error) { + return error; } char *tmp = strAppend(&filename[sizeof(SCREENSHOTS_PATH)-1], "/screen"); tmp = strAppendDate(tmp, true); strcpy(tmp, BITMAPS_EXT); - result = f_open(&bmpFile, filename, FA_CREATE_ALWAYS | FA_WRITE); + FRESULT result = f_open(&bmpFile, filename, FA_CREATE_ALWAYS | FA_WRITE); if (result != FR_OK) { return SDCARD_ERROR(result); } diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index 78003b42d..119fc156f 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -269,7 +269,9 @@ int cliDisplay(const char ** argv) name[len] = '\0'; serialPrint("[%s] = %s", name, switchState(EnumKeys(i)) ? "on" : "off"); } +#if defined(ROTARY_ENCODER_NAVIGATION) serialPrint("[Enc.] = %d", rotencValue / 2); +#endif for (int i=TRM_BASE; i<=TRM_LAST; i++) { serialPrint("[Trim%d] = %s", i-TRM_BASE, switchState(EnumKeys(i)) ? "on" : "off"); } diff --git a/radio/src/gui/9X/helpers.cpp b/radio/src/gui/9X/helpers.cpp index 82440939e..0db4fb4ff 100644 --- a/radio/src/gui/9X/helpers.cpp +++ b/radio/src/gui/9X/helpers.cpp @@ -43,234 +43,3 @@ uint8_t switchToMix(uint8_t source) else return MIXSRC_FIRST_SWITCH - 3 + source; } - -#if defined(CPUARM) -bool isSourceAvailable(int source) -{ -#if !defined(HELI) - if (source>=MIXSRC_CYC1 && source<=MIXSRC_CYC3) - return false; -#endif - - if (source>=MIXSRC_CH1 && source<=MIXSRC_LAST_CH) { - uint8_t destCh = source-MIXSRC_CH1; - for (uint8_t i = 0; i < MAX_MIXERS; i++) { - MixData *md = mixAddress(i); - if (md->srcRaw == 0) return false; - if (md->destCh==destCh) return true; - } - return false; - } - - if (source>=MIXSRC_SW1 && source<=MIXSRC_LAST_LOGICAL_SWITCH) { - LogicalSwitchData * cs = lswAddress(source-MIXSRC_SW1); - return (cs->func != LS_FUNC_NONE); - } - -#if !defined(GVARS) - if (source>=MIXSRC_GVAR1 && source<=MIXSRC_LAST_GVAR) - return false; -#endif - - if (source>=MIXSRC_RESERVE1 && source<=MIXSRC_RESERVE5) - return false; - - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - if (qr.rem == 0) - return isTelemetryFieldAvailable(qr.quot); - else - return isTelemetryFieldComparisonAvailable(qr.quot); - } - - return true; -} - -bool isSourceAvailableInGlobalFunctions(int source) -{ - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - return false; - } - return isSourceAvailable(source); -} - -bool isSourceAvailableInCustomSwitches(int source) -{ - bool result = isSourceAvailable(source); - -#if defined(FRSKY) - if (result && source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - result = isTelemetryFieldComparisonAvailable(qr.quot); - } -#endif - - return result; -} - -bool isInputSourceAvailable(int source) -{ - if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) - return true; - - if (source>=MIXSRC_TrimRud && source=MIXSRC_FIRST_CH && source<=MIXSRC_LAST_CH) - return true; - - if (source>=MIXSRC_FIRST_TRAINER && source<=MIXSRC_LAST_TRAINER) - return true; - - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) - return isTelemetryFieldAvailable(source-MIXSRC_FIRST_TELEM); - - return false; -} - -enum SwitchContext -{ - LogicalSwitchesContext, - ModelCustomFunctionsContext, - GeneralCustomFunctionsContext, - TimersContext, - MixesContext -}; - -bool isSwitchAvailable(int swtch, SwitchContext context) -{ - if (swtch < 0) { - if (swtch == -SWSRC_ON || swtch == -SWSRC_ONE) { - return false; - } - swtch = -swtch; - } - - if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH && swtch <= SWSRC_LAST_LOGICAL_SWITCH) { - if (context == GeneralCustomFunctionsContext) { - return false; - } - else if (context != LogicalSwitchesContext) { - LogicalSwitchData * cs = lswAddress(swtch-SWSRC_FIRST_LOGICAL_SWITCH); - return (cs->func != LS_FUNC_NONE); - } - } - - if (context != ModelCustomFunctionsContext && context != GeneralCustomFunctionsContext && (swtch == SWSRC_ON || swtch == SWSRC_ONE)) { - return false; - } - - if (swtch >= SWSRC_FIRST_FLIGHT_MODE && swtch <= SWSRC_LAST_FLIGHT_MODE) { - if (context == MixesContext || context == GeneralCustomFunctionsContext) { - return false; - } - else { - swtch -= SWSRC_FIRST_FLIGHT_MODE; - if (swtch == 0) { - return true; - } - FlightModeData * fm = flightModeAddress(swtch); - return (fm->swtch != SWSRC_NONE); - } - } - - return true; -} - -bool isSwitchAvailableInLogicalSwitches(int swtch) -{ - return isSwitchAvailable(swtch, LogicalSwitchesContext); -} - -bool isSwitchAvailableInCustomFunctions(int swtch) -{ - if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) - return isSwitchAvailable(swtch, ModelCustomFunctionsContext); - else - return isSwitchAvailable(swtch, GeneralCustomFunctionsContext); -} - -bool isSwitchAvailableInMixes(int swtch) -{ - return isSwitchAvailable(swtch, MixesContext); -} - -bool isSwitchAvailableInTimers(int swtch) -{ - if (swtch >= 0) { - if (swtch < TMRMODE_COUNT) - return true; - else - swtch -= TMRMODE_COUNT-1; - } - else { - if (swtch > -TMRMODE_COUNT) - return false; - else - swtch += TMRMODE_COUNT-1; - } - - return isSwitchAvailable(swtch, TimersContext); -} - -bool isLogicalSwitchFunctionAvailable(int function) -{ - return function != LS_FUNC_RANGE; -} - -bool isAssignableFunctionAvailable(int function) -{ - bool modelFunctions = (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions); - - switch (function) { - case FUNC_OVERRIDE_CHANNEL: -#if defined(OVERRIDE_CHANNEL_FUNCTION) - return modelFunctions; -#else - return false; -#endif - case FUNC_ADJUST_GVAR: -#if defined(GVARS) - return modelFunctions; -#else - return false; -#endif -#if !defined(HAPTIC) - case FUNC_HAPTIC: -#endif - case FUNC_RESERVE4: -#if !defined(DANGEROUS_MODULE_FUNCTIONS) - case FUNC_RANGECHECK: - case FUNC_BIND: -#endif - case FUNC_PLAY_SCRIPT: - case FUNC_RESERVE5: - return false; - - default: - return true; - } -} - -bool isModuleAvailable(int module) -{ - if (module == MODULE_TYPE_NONE) - return false; - -#if !defined(PXX) - if (module == MODULE_TYPE_XJT) - return false; -#endif - - return true; -} - -bool modelHasNotes() -{ - char filename[sizeof(MODELS_PATH)+1+sizeof(g_model.header.name)+sizeof(TEXT_EXT)] = MODELS_PATH "/"; - char *buf = strcat_modelname(&filename[sizeof(MODELS_PATH)], g_eeGeneral.currModel); - strcpy(buf, TEXT_EXT); - return isFileAvailable(filename); -} - -#endif diff --git a/radio/src/gui/9X/lcd.cpp b/radio/src/gui/9X/lcd.cpp index ee9b09f60..0636a3c1d 100644 --- a/radio/src/gui/9X/lcd.cpp +++ b/radio/src/gui/9X/lcd.cpp @@ -36,7 +36,7 @@ #include "opentx.h" -display_t displayBuf[DISPLAY_BUF_SIZE]; +display_t displayBuf[DISPLAY_BUFFER_SIZE]; void lcd_clear() { @@ -132,7 +132,7 @@ void lcdPutPattern(coord_t x, coord_t y, const uint8_t * pattern, uint8_t width, } } -void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) +void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) { const pm_uchar * q; @@ -205,7 +205,7 @@ void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) void lcd_putc(coord_t x, coord_t y, const unsigned char c) { - lcd_putcAtt(x, y, c, 0); + lcdDrawChar(x, y, c, 0); } void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) @@ -240,7 +240,7 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags break; } else if (c >= 0x20) { - lcd_putcAtt(x, y, c, flags); + lcdDrawChar(x, y, c, flags); x = lcdNextPos; } else if (c == 0x1F) { //X-coord prefix @@ -317,7 +317,7 @@ void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags flags) x -= FWNUM; char c = val & 0xf; c = c>9 ? c+'A'-10 : c+'0'; - lcd_putcAtt(x, y, c, flags|(c>='A' ? CONDENSED : 0)); + lcdDrawChar(x, y, c, flags|(c>='A' ? CONDENSED : 0)); val >>= 4; } } @@ -329,7 +329,7 @@ void lcd_outhex4(coord_t x, coord_t y, uint16_t val) x -= FWNUM; char c = val & 0xf; c = c>9 ? c+'A'-10 : c+'0'; - lcd_putcAtt(x, y, c, c>='A' ? CONDENSED : 0); + lcdDrawChar(x, y, c, c>='A' ? CONDENSED : 0); val >>= 4; } } @@ -430,7 +430,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t if (c=='1' && i==len && xn>x+10) { x+=1; } if ((lcduint_t)val >= 1000) { x+=FWNUM; f &= ~DBLSIZE; } } - lcd_putcAtt(x, y, c, f); + lcdDrawChar(x, y, c, f); if (mode == i) { flags &= ~PREC2; // TODO not needed but removes 20bytes, could be improved for sure, check asm if (dblsize) { @@ -449,7 +449,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t } else if (xxlsize) { x -= 17; - lcd_putcAtt(x+2, y, '.', f); + lcdDrawChar(x+2, y, '.', f); } else if (midsize) { x -= 3; @@ -472,7 +472,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t } else { x -= 2; - lcd_putcAtt(x, y, '.', f); + lcdDrawChar(x, y, '.', f); } } if (dblsize && (lcduint_t)val >= 1000 && (lcduint_t)val < 10000) x-=2; @@ -497,17 +497,17 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t drawFilledRect(xn, y+2*FH-3, ln, 2); } } - if (neg) lcd_putcAtt(x, y, '-', flags); + if (neg) lcdDrawChar(x, y, '-', flags); } #endif void lcd_hline(coord_t x, coord_t y, coord_t w, LcdFlags att) { - lcd_hlineStip(x, y, w, 0xff, att); + lcdDrawHorizontalLine(x, y, w, 0xff, att); } #if defined(CPUARM) && !defined(BOOT) -void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFlags att) +void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFlags att) { int dx = x2-x1; /* the horizontal distance of the line */ int dy = y2-y1; /* the vertical distance of the line */ @@ -553,16 +553,16 @@ void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFl void lcd_vline(coord_t x, scoord_t y, scoord_t h) { - lcd_vlineStip(x, y, h, SOLID); + lcdDrawVerticalLine(x, y, h, SOLID); } -void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) +void lcdDrawRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) { - lcd_vlineStip(x, y, h, pat); - lcd_vlineStip(x+w-1, y, h, pat); + lcdDrawVerticalLine(x, y, h, pat); + lcdDrawVerticalLine(x+w-1, y, h, pat); if (~att & ROUND) { x+=1; w-=2; } - lcd_hlineStip(x, y+h-1, w, pat); - lcd_hlineStip(x, y, w, pat); + lcdDrawHorizontalLine(x, y+h-1, w, pat); + lcdDrawHorizontalLine(x, y, w, pat); } #if !defined(BOOT) @@ -570,15 +570,15 @@ void drawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat, Lc { #if defined(CPUM64) for (scoord_t i=y; i> 1) + ((pat & 1) << 7); } #else for (scoord_t i=y; i> 1) + ((pat & 1) << 7); } #endif @@ -617,7 +617,7 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 } if (tme < 0) { - lcd_putcAtt(x - ((att & DBLSIZE) ? FW+2 : ((att & MIDSIZE) ? FW+0 : FWNUM)), y, '-', att); + lcdDrawChar(x - ((att & DBLSIZE) ? FW+2 : ((att & MIDSIZE) ? FW+0 : FWNUM)), y, '-', att); tme = -tme; } @@ -635,10 +635,10 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 lcd_outdezNAtt(x, y, qr.quot, att|LEADING0|LEFT, 2); #if defined(CPUARM) && defined(RTCLOCK) if (att&TIMEBLINK) - lcd_putcAtt(lcdLastPos, y, separator, BLINK); + lcdDrawChar(lcdLastPos, y, separator, BLINK); else #endif - lcd_putcAtt(lcdLastPos, y, separator, att&att2); + lcdDrawChar(lcdLastPos, y, separator, att&att2); lcd_outdezNAtt(lcdNextPos, y, qr.rem, att2|LEADING0|LEFT, 2); } @@ -646,7 +646,7 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 void putsVolts(coord_t x, coord_t y, uint16_t volts, LcdFlags att) { lcd_outdezAtt(x, y, (int16_t)volts, (~NO_UNIT) & (att | ((att&PREC2)==PREC2 ? 0 : PREC1))); - if (~att & NO_UNIT) lcd_putcAtt(lcdLastPos, y, 'V', att); + if (~att & NO_UNIT) lcdDrawChar(lcdLastPos, y, 'V', att); } void putsVBat(coord_t x, coord_t y, LcdFlags att) @@ -685,7 +685,7 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) idx -= MIXSRC_FIRST_TELEM; div_t qr = div(idx, 3); lcd_putsnAtt(x, y, g_model.telemetrySensors[qr.quot].label, ZLEN(g_model.telemetrySensors[qr.quot].label), ZCHAR|att); - if (qr.rem) lcd_putcAtt(lcdLastPos, y, qr.rem==2 ? '+' : '-', att); + if (qr.rem) lcdDrawChar(lcdLastPos, y, qr.rem==2 ? '+' : '-', att); } #else else @@ -715,7 +715,7 @@ void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) if (idx == SWSRC_OFF) return lcd_putsiAtt(x, y, STR_OFFON, 0, att); if (idx < 0) { - lcd_putcAtt(x-2, y, '!', att); + lcdDrawChar(x-2, y, '!', att); idx = -idx; } #if defined(CPUARM) && defined(FLIGHT_MODES) @@ -730,7 +730,7 @@ void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx==0) { lcd_putsiAtt(x, y, STR_MMMINV, 0, att); return; } - if (idx < 0) { lcd_putcAtt(x-2, y, '!', att); idx = -idx; } + if (idx < 0) { lcdDrawChar(x-2, y, '!', att); idx = -idx; } if (att & CONDENSED) lcd_outdezNAtt(x+FW*1, y, idx-1, (att & ~CONDENSED), 1); else @@ -741,7 +741,7 @@ void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att) void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx < 0) { - lcd_putcAtt(x-3, y, '!', att); + lcdDrawChar(x-3, y, '!', att); idx = -idx+CURVE_BASE-1; } if (idx < CURVE_BASE) @@ -768,7 +768,7 @@ void putsTrimMode(coord_t x, coord_t y, uint8_t phase, uint8_t idx, LcdFlags att if (v > TRIM_EXTENDED_MAX) { uint8_t p = v - TRIM_EXTENDED_MAX - 1; if (p >= phase) p++; - lcd_putcAtt(x, y, '0'+p, att); + lcdDrawChar(x, y, '0'+p, att); } else { putsChnLetter(x, y, idx+1, att); @@ -783,10 +783,10 @@ void putsRotaryEncoderMode(coord_t x, coord_t y, uint8_t phase, uint8_t idx, Lcd if (v > ROTARY_ENCODER_MAX) { uint8_t p = v - ROTARY_ENCODER_MAX - 1; if (p >= phase) p++; - lcd_putcAtt(x, y, '0'+p, att); + lcdDrawChar(x, y, '0'+p, att); } else { - lcd_putcAtt(x, y, 'a'+idx, att); + lcdDrawChar(x, y, 'a'+idx, att); } } #endif @@ -816,7 +816,7 @@ void displayGpsCoord(coord_t x, coord_t y, char direction, int16_t bp, int16_t a { if (!direction) direction = '-'; lcd_outdezAtt(x, y, bp / 100, att); // ddd before '.' - lcd_putcAtt(lcdLastPos, y, '@', att); + lcdDrawChar(lcdLastPos, y, '@', att); uint8_t mn = bp % 100; // TODO div_t if (g_eeGeneral.gpsFormat == 0) { lcd_outdezNAtt(lcdNextPos, y, mn, att|LEFT|LEADING0, 2); // mm before '.' @@ -845,22 +845,22 @@ void displayDate(coord_t x, coord_t y, TelemetryItem & telemetryItem, LcdFlags a x -= 42; att &= ~0x0F00; // TODO constant lcd_outdezNAtt(x, y, telemetryItem.datetime.day, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos-1, y, '-', att); + lcdDrawChar(lcdLastPos-1, y, '-', att); lcd_outdezNAtt(lcdNextPos-1, y, telemetryItem.datetime.month, att|LEFT, 2); - lcd_putcAtt(lcdLastPos-1, y, '-', att); + lcdDrawChar(lcdLastPos-1, y, '-', att); lcd_outdezAtt(lcdNextPos-1, y, telemetryItem.datetime.year, att|LEFT); y += FH; lcd_outdezNAtt(x, y, telemetryItem.datetime.hour, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.min, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.sec, att|LEADING0|LEFT, 2); } else { lcd_outdezNAtt(x, y, telemetryItem.datetime.hour, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.min, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.sec, att|LEADING0|LEFT, 2); } } @@ -1110,7 +1110,7 @@ void lcdSetContrast() #define LCD_BYTE_FILTER(p, keep, add) *(p) = (*(p) & (keep)) | (add) #if !defined(CPUARM) -void lcd_putcAtt(coord_t x, uint8_t y, const unsigned char c, LcdFlags flags) +void lcdDrawChar(coord_t x, uint8_t y, const unsigned char c, LcdFlags flags) { uint8_t *p = &displayBuf[ y / 8 * LCD_W + x ]; const pm_uchar *q = &font_5x7[(c-0x20)*5]; @@ -1280,7 +1280,7 @@ void lcd_plot(coord_t x, coord_t y, LcdFlags att) lcd_mask(p, BITMASK(y%8), att); } -void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) +void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) { if (y >= LCD_H) return; if (x+w > LCD_W) { w = LCD_W - x; } @@ -1300,7 +1300,7 @@ void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) } #if defined(CPUM64) -void lcd_vlineStip(coord_t x, int8_t y, int8_t h, uint8_t pat) +void lcdDrawVerticalLine(coord_t x, int8_t y, int8_t h, uint8_t pat) { if (x >= LCD_W) return; if (h<0) { y+=h; h=-h; } @@ -1333,7 +1333,7 @@ void lcd_vlineStip(coord_t x, int8_t y, int8_t h, uint8_t pat) } #else // allows the att parameter... -void lcd_vlineStip(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att) +void lcdDrawVerticalLine(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att) { if (x >= LCD_W) return; #if defined(CPUARM) diff --git a/radio/src/gui/9X/lcd.h b/radio/src/gui/9X/lcd.h index a5b87b5e3..862bfba12 100644 --- a/radio/src/gui/9X/lcd.h +++ b/radio/src/gui/9X/lcd.h @@ -135,17 +135,16 @@ #endif #define display_t uint8_t -#define DISPLAY_BUF_SIZE (LCD_W*((LCD_H+7)/8)) +#define DISPLAY_BUFFER_SIZE (LCD_W*((LCD_H+7)/8)) -extern display_t displayBuf[DISPLAY_BUF_SIZE]; +extern display_t displayBuf[DISPLAY_BUFFER_SIZE]; #define lcdRefreshWait() extern coord_t lcdLastPos; extern coord_t lcdNextPos; -#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) -#define DISPLAY_END (displayBuf + DISPLAY_BUF_SIZE) +#define DISPLAY_END (displayBuf + DISPLAY_BUFFER_SIZE) #define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) #if defined(PCBSTD) && defined(VOICE) @@ -164,10 +163,10 @@ typedef const char pm_char; #endif void lcd_putc(coord_t x, coord_t y, const unsigned char c); -void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags mode); -void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags mode); -void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags mode); -void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s,unsigned char len, LcdFlags mode); +void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags flags); +void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags flags); +void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags); +void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s,unsigned char len, LcdFlags flags); void lcd_puts(coord_t x, coord_t y, const pm_char * s); void lcd_putsn(coord_t x, coord_t y, const pm_char * s, unsigned char len); void lcd_putsLeft(coord_t y, const pm_char * s); @@ -227,24 +226,24 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 void lcd_plot(coord_t x, coord_t y, LcdFlags att=0); void lcd_mask(uint8_t *p, uint8_t mask, LcdFlags att=0); void lcd_hline(coord_t x, coord_t y, coord_t w, LcdFlags att=0); -void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); +void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); void lcd_vline(coord_t x, scoord_t y, scoord_t h); #if defined(CPUM64) - void lcd_vlineStip(coord_t x, scoord_t y, int8_t h, uint8_t pat); + void lcdDrawVerticalLine(coord_t x, scoord_t y, int8_t h, uint8_t pat); #else - void lcd_vlineStip(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); + void lcdDrawVerticalLine(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); #endif #if defined(CPUARM) - void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); + void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); #endif void drawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); -void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); +void lcdDrawRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); void lcd_invert_line(int8_t line); #define lcd_status_line() lcd_invert_line(LCD_LINES-1) -inline void lcd_square(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { lcd_rect(x, y, w, w, SOLID, att); } +inline void lcd_square(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { lcdDrawRect(x, y, w, w, SOLID, att); } void lcdDrawTelemetryTopBar(); @@ -271,7 +270,7 @@ void lcdRefresh(); #if defined(SIMU) extern bool lcd_refresh; - extern display_t lcd_buf[DISPLAY_BUF_SIZE]; + extern display_t lcd_buf[DISPLAY_BUFFER_SIZE]; #endif char *strAppend(char * dest, const char * source, int len=0); diff --git a/radio/src/gui/9X/menu_general_calib.cpp b/radio/src/gui/9X/menu_general_calib.cpp index 72e04e491..366c60d4b 100644 --- a/radio/src/gui/9X/menu_general_calib.cpp +++ b/radio/src/gui/9X/menu_general_calib.cpp @@ -104,7 +104,7 @@ void menuCommonCalib(uint8_t event) case 3: g_eeGeneral.chkSum = evalChkSum(); - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); reusableBuffer.calib.state = 4; break; diff --git a/radio/src/gui/9X/menu_general_diagkeys.cpp b/radio/src/gui/9X/menu_general_diagkeys.cpp index ecd4533e6..7d61c50d9 100644 --- a/radio/src/gui/9X/menu_general_diagkeys.cpp +++ b/radio/src/gui/9X/menu_general_diagkeys.cpp @@ -39,7 +39,7 @@ void displayKeyState(uint8_t x, uint8_t y, EnumKeys key) { uint8_t t = switchState(key); - lcd_putcAtt(x, y, t+'0', t ? INVERS : 0); + lcdDrawChar(x, y, t+'0', t ? INVERS : 0); } void menuGeneralDiagKeys(uint8_t event) diff --git a/radio/src/gui/9X/menu_general_hardware.cpp b/radio/src/gui/9X/menu_general_hardware.cpp index b0e9e0579..da257a1b3 100644 --- a/radio/src/gui/9X/menu_general_hardware.cpp +++ b/radio/src/gui/9X/menu_general_hardware.cpp @@ -79,7 +79,7 @@ void menuGeneralHardware(uint8_t event) lcd_puts(INDENT_WIDTH+3*FW, y, PSTR("Gain")); uint8_t mask = (1<<(k-ITEM_SETUP_HW_STICK_LV_GAIN)); uint8_t val = (g_eeGeneral.sticksGain & mask ? 1 : 0); - lcd_putcAtt(GENERAL_HW_PARAM_OFS, y, val ? '2' : '1', attr); + lcdDrawChar(GENERAL_HW_PARAM_OFS, y, val ? '2' : '1', attr); if (attr) { CHECK_INCDEC_GENVAR(event, val, 0, 1); if (checkIncDec_Ret) { diff --git a/radio/src/gui/9X/menu_general_sdmanager.cpp b/radio/src/gui/9X/menu_general_sdmanager.cpp index d8ea44a2f..a4aa1b1fa 100644 --- a/radio/src/gui/9X/menu_general_sdmanager.cpp +++ b/radio/src/gui/9X/menu_general_sdmanager.cpp @@ -291,9 +291,9 @@ void menuGeneralSdManager(uint8_t _event) lcdNextPos = 0; uint8_t attr = (m_posVert-1-s_pgOfs == i ? BSS|INVERS : BSS); if (reusableBuffer.sdmanager.lines[i][0]) { - if (!reusableBuffer.sdmanager.lines[i][SD_SCREEN_FILE_LENGTH+1]) { lcd_putcAtt(0, y, '[', attr); } + if (!reusableBuffer.sdmanager.lines[i][SD_SCREEN_FILE_LENGTH+1]) { lcdDrawChar(0, y, '[', attr); } lcd_putsAtt(lcdNextPos, y, reusableBuffer.sdmanager.lines[i], attr); - if (!reusableBuffer.sdmanager.lines[i][SD_SCREEN_FILE_LENGTH+1]) { lcd_putcAtt(lcdNextPos, y, ']', attr); } + if (!reusableBuffer.sdmanager.lines[i][SD_SCREEN_FILE_LENGTH+1]) { lcdDrawChar(lcdNextPos, y, ']', attr); } } } } diff --git a/radio/src/gui/9X/menu_general_setup.cpp b/radio/src/gui/9X/menu_general_setup.cpp index ac0edda35..accd6ff6b 100644 --- a/radio/src/gui/9X/menu_general_setup.cpp +++ b/radio/src/gui/9X/menu_general_setup.cpp @@ -149,7 +149,7 @@ void menuGeneralSetup(uint8_t event) if (s_warning_result) { s_warning_result = 0; g_eeGeneral.fai = true; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); } #endif @@ -304,7 +304,7 @@ void menuGeneralSetup(uint8_t event) case ITEM_SETUP_SPEAKER_PITCH: lcd_putsLeft( y, STR_SPKRPITCH); #if defined(CPUARM) - lcd_putcAtt(RADIO_SETUP_2ND_COLUMN, y, '+', attr); + lcdDrawChar(RADIO_SETUP_2ND_COLUMN, y, '+', attr); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN+FW, y, g_eeGeneral.speakerPitch*15, attr|LEFT); lcd_putsAtt(lcdLastPos, y, "Hz", attr); #else @@ -565,13 +565,13 @@ void menuGeneralSetup(uint8_t event) if (attr) { s_editMode = 0; CHECK_INCDEC_GENVAR(event, g_eeGeneral.stickReverse, 0, 15); - lcd_rect(6*FW-1, y-1, 15*FW+2, 9); + lcdDrawRect(6*FW-1, y-1, 15*FW+2, 9); } #endif break; case ITEM_SETUP_STICK_MODE: - lcd_putcAtt(2*FW, y, '1'+g_eeGeneral.stickMode, attr); + lcdDrawChar(2*FW, y, '1'+g_eeGeneral.stickMode, attr); for (uint8_t i=0; i<4; i++) { putsMixerSource((6+4*i)*FW, y, MIXSRC_Rud + pgm_read_byte(modn12x3 + 4*g_eeGeneral.stickMode + i), 0); } diff --git a/radio/src/gui/9X/menu_general_trainer.cpp b/radio/src/gui/9X/menu_general_trainer.cpp index cc24541f7..f660c06ea 100644 --- a/radio/src/gui/9X/menu_general_trainer.cpp +++ b/radio/src/gui/9X/menu_general_trainer.cpp @@ -106,7 +106,7 @@ void menuGeneralTrainer(uint8_t event) if (attr) { if (event==EVT_KEY_LONG(KEY_ENTER)){ memcpy(g_eeGeneral.trainer.calib, ppmInput, sizeof(g_eeGeneral.trainer.calib)); - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); AUDIO_WARNING1(); } } diff --git a/radio/src/gui/9X/menu_model.cpp b/radio/src/gui/9X/menu_model.cpp index 23cdc2456..3d06688f5 100644 --- a/radio/src/gui/9X/menu_model.cpp +++ b/radio/src/gui/9X/menu_model.cpp @@ -199,10 +199,10 @@ void editName(coord_t x, coord_t y, char *name, uint8_t size, uint8_t event, uin if (c != v) { name[cur] = v; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } - lcd_putcAtt(x+editNameCursorPos*FW, y, idx2char(v), ERASEBG|INVERS|FIXEDWIDTH); + lcdDrawChar(x+editNameCursorPos*FW, y, idx2char(v), ERASEBG|INVERS|FIXEDWIDTH); } else { cur = 0; diff --git a/radio/src/gui/9X/menu_model_curves.cpp b/radio/src/gui/9X/menu_model_curves.cpp index bcfc77215..89be0723b 100644 --- a/radio/src/gui/9X/menu_model_curves.cpp +++ b/radio/src/gui/9X/menu_model_curves.cpp @@ -98,7 +98,7 @@ bool moveCurve(uint8_t index, int8_t shift, int8_t custom=0) for (uint8_t i=0; i0 && i0 || p1valdiff)) { switch (m_posHorz) { @@ -729,7 +729,7 @@ void menuModelSetup(uint8_t event) lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN, y, (int16_t)g_model.moduleData[1].ppmFrameLength*5 + 225, (m_posHorz<=0 ? attr : 0) | PREC1|LEFT); lcd_putc(MODEL_SETUP_2ND_COLUMN+8*FW+2, y, 'u'); lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN+8*FW+2, y, (g_model.moduleData[1].ppmDelay*50)+300, (m_posHorz < 0 || m_posHorz==1) ? attr : 0); - lcd_putcAtt(MODEL_SETUP_2ND_COLUMN+10*FW, y, g_model.moduleData[1].ppmPulsePol ? '+' : '-', (m_posHorz < 0 || m_posHorz==2) ? attr : 0); + lcdDrawChar(MODEL_SETUP_2ND_COLUMN+10*FW, y, g_model.moduleData[1].ppmPulsePol ? '+' : '-', (m_posHorz < 0 || m_posHorz==2) ? attr : 0); if (attr && (editMode>0 || p1valdiff)) { switch (m_posHorz) { case 0: @@ -754,7 +754,7 @@ void menuModelSetup(uint8_t event) lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN, y, (int16_t)g_model.ppmFrameLength*5 + 225, (m_posHorz<=0 ? attr : 0) | PREC1|LEFT); lcd_putc(MODEL_SETUP_2ND_COLUMN+8*FW+2, y, 'u'); lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN+8*FW+2, y, (g_model.ppmDelay*50)+300, (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0); - lcd_putcAtt(MODEL_SETUP_2ND_COLUMN+10*FW, y, g_model.pulsePol ? '+' : '-', (CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0); + lcdDrawChar(MODEL_SETUP_2ND_COLUMN+10*FW, y, g_model.pulsePol ? '+' : '-', (CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0); if (attr && (editMode>0 || p1valdiff)) { switch (m_posHorz) { case 0: @@ -821,7 +821,7 @@ void menuModelFailsafe(uint8_t event) if (event == EVT_KEY_LONG(KEY_ENTER) && s_editMode) { s_noHi = NO_HI_LEN; g_model.moduleData[g_moduleIdx].failsafeChannels[m_posVert] = channelOutputs[m_posVert]; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); AUDIO_WARNING1(); SEND_FAILSAFE_NOW(g_moduleIdx); } @@ -876,7 +876,7 @@ void menuModelFailsafe(uint8_t event) #endif // Gauge - lcd_rect(x+COL_W-3-wbar-ofs, y, wbar+1, 6); + lcdDrawRect(x+COL_W-3-wbar-ofs, y, wbar+1, 6); uint16_t lim = g_model.extendedLimits ? 640*2 : 512*2; uint8_t len = limit((uint8_t)1, uint8_t((abs(val) * wbar/2 + lim/2) / lim), uint8_t(wbar/2)); coord_t x0 = (val>0) ? x+COL_W-ofs-3-wbar/2 : x+COL_W-ofs-2-wbar/2-len; diff --git a/radio/src/gui/9X/menu_model_telemetry.cpp b/radio/src/gui/9X/menu_model_telemetry.cpp index d6c6cc44e..70fb23f12 100644 --- a/radio/src/gui/9X/menu_model_telemetry.cpp +++ b/radio/src/gui/9X/menu_model_telemetry.cpp @@ -409,7 +409,7 @@ void menuModelSensor(uint8_t event) lcd_putsLeft(y, STR_RATIO); if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.ratio, 0, 30000); if (sensor->custom.ratio == 0) - lcd_putcAtt(SENSOR_2ND_COLUMN, y, '-', attr); + lcdDrawChar(SENSOR_2ND_COLUMN, y, '-', attr); else lcd_outdezAtt(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); break; @@ -452,7 +452,7 @@ void menuModelSensor(uint8_t event) source = checkIncDec(event, source, -MAX_SENSORS, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); } if (source < 0) { - lcd_putcAtt(SENSOR_2ND_COLUMN, y, '-', attr); + lcdDrawChar(SENSOR_2ND_COLUMN, y, '-', attr); putsMixerSource(lcdNextPos, y, MIXSRC_FIRST_TELEM+3*(-1-source), attr); } else { @@ -511,7 +511,7 @@ void onSensorMenu(const char *result) TelemetryItem & sourceItem = telemetryItems[index]; TelemetryItem & newItem = telemetryItems[newIndex]; newItem = sourceItem; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else { POPUP_WARNING(STR_TELEMETRYFULL); @@ -569,7 +569,7 @@ void menuModelTelemetry(uint8_t event) if (k>=ITEM_TELEMETRY_SENSOR1 && k val ? 1 : -1); } else { @@ -306,7 +306,7 @@ int16_t checkIncDec(uint8_t event, int16_t val, int16_t i_min, int16_t i_max, ui else AUDIO_KEYPAD_DOWN(); } - eeDirty(i_flags & (EE_GENERAL|EE_MODEL)); + storageDirty(i_flags & (EE_GENERAL|EE_MODEL)); checkIncDec_Ret = (newval > val ? 1 : -1); } else { diff --git a/radio/src/gui/9X/popups.cpp b/radio/src/gui/9X/popups.cpp index 29ea6874d..1f50cfab0 100644 --- a/radio/src/gui/9X/popups.cpp +++ b/radio/src/gui/9X/popups.cpp @@ -52,7 +52,7 @@ int16_t s_warning_input_max; void displayBox() { drawFilledRect(10, 16, LCD_W-20, 40, SOLID, ERASE); - lcd_rect(10, 16, LCD_W-20, 40); + lcdDrawRect(10, 16, LCD_W-20, 40); #if defined(CPUARM) lcd_putsn(WARNING_LINE_X, WARNING_LINE_Y, s_warning, WARNING_LINE_LEN); #else @@ -152,7 +152,7 @@ const char * displayMenu(uint8_t event) uint8_t display_count = min(s_menu_count, MENU_MAX_LINES); uint8_t y = (display_count >= 5 ? MENU_Y - FH - 1 : MENU_Y); drawFilledRect(MENU_X, y, MENU_W, display_count * (FH+1) + 2, SOLID, ERASE); - lcd_rect(MENU_X, y, MENU_W, display_count * (FH+1) + 2); + lcdDrawRect(MENU_X, y, MENU_W, display_count * (FH+1) + 2); for (uint8_t i=0; iWBAR2) len = WBAR2; // prevent bars from going over the end - comment for debugging - lcd_hlineStip(x0-WBAR2, y0, WBAR2*2+1, DOTTED); + lcdDrawHorizontalLine(x0-WBAR2, y0, WBAR2*2+1, DOTTED); lcd_vline(x0,y0-2,5); if (val>0) x0+=1; @@ -567,7 +567,7 @@ void menuMainView(uint8_t event) // And ! in case of unexpected shutdown if (unexpectedShutdown) { - lcd_putcAtt(REBOOT_X, 0*FH, '!', INVERS); + lcdDrawChar(REBOOT_X, 0*FH, '!', INVERS); } #if defined(GVARS) && !defined(PCBSTD) diff --git a/radio/src/gui/9X/view_mavlink.cpp b/radio/src/gui/9X/view_mavlink.cpp index 672e28081..4bd6f7d4a 100644 --- a/radio/src/gui/9X/view_mavlink.cpp +++ b/radio/src/gui/9X/view_mavlink.cpp @@ -131,7 +131,7 @@ void lcd_outdezFloat(uint8_t x, uint8_t y, float val, uint8_t precis, uint8_t mo for (; i < 4; i++) { c = (lnum % 10) + '0'; x1 -= xinc; - lcd_putcAtt(x1, y, c, mode); + lcdDrawChar(x1, y, c, mode); lnum /= 10; if (lnum == 0) { break; @@ -142,20 +142,20 @@ void lcd_outdezFloat(uint8_t x, uint8_t y, float val, uint8_t precis, uint8_t mo x1 = x; for (i = 0; i < 4; i++) { x1 -= FW; - lcd_putcAtt(x1, y, '?', mode); + lcdDrawChar(x1, y, '?', mode); } } else { if (val < 0) { val = -val; x1 -= xinc; - lcd_putcAtt(x1, y, '-', mode); + lcdDrawChar(x1, y, '-', mode); } if (precis) { uint8_t y_temp = y; if (mode & DBLSIZE) y_temp += FH; - lcd_putcAtt(x, y_temp, '.', (mode & (~DBLSIZE))); + lcdDrawChar(x, y_temp, '.', (mode & (~DBLSIZE))); x -= (xinc / 2); } for (i = 0; i < precis; i++) { @@ -163,7 +163,7 @@ void lcd_outdezFloat(uint8_t x, uint8_t y, float val, uint8_t precis, uint8_t mo int a = val; c = a + '0'; x += xinc; - lcd_putcAtt(x, y, c, mode); + lcdDrawChar(x, y, c, mode); val -= a; } } @@ -203,7 +203,7 @@ void mav_title(const pm_char * s, uint8_t index) displayScreenIndex(index, MAX_MAVLINK_MENU, INVERS); lcd_putc(7 * FW, 0, (mav_heartbeat > 0) ? '*' : ' '); if (telemetry_data.active) - lcd_putcAtt(8 * FW, 0, 'A', BLINK); + lcdDrawChar(8 * FW, 0, 'A', BLINK); } /*! \brief Global info menu @@ -444,7 +444,7 @@ void lcd_outhex2(uint8_t x, uint8_t y, uint8_t val) { x -= FWNUM; char c = val & 0xf; c = c > 9 ? c + 'A' - 10 : c + '0'; - lcd_putcAtt(x, y, c, c >= 'A' ? CONDENSED : 0); + lcdDrawChar(x, y, c, c >= 'A' ? CONDENSED : 0); val >>= 4; } } diff --git a/radio/src/gui/9X/view_statistics.cpp b/radio/src/gui/9X/view_statistics.cpp index 209c3b033..b8de64ba0 100644 --- a/radio/src/gui/9X/view_statistics.cpp +++ b/radio/src/gui/9X/view_statistics.cpp @@ -50,7 +50,7 @@ void menuStatisticsView(uint8_t event) #if defined(CPUARM) case EVT_KEY_LONG(KEY_MENU): g_eeGeneral.globalTimer = 0; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); sessionTimer = 0; break; #endif @@ -110,7 +110,7 @@ void menuStatisticsDebug(uint8_t event) case EVT_KEY_LONG(KEY_ENTER): g_eeGeneral.mAhUsed = 0; g_eeGeneral.globalTimer = 0; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); #if defined(PCBSKY9X) Current_used = 0; #endif diff --git a/radio/src/gui/9X/view_telemetry.cpp b/radio/src/gui/9X/view_telemetry.cpp index d948da6b8..e1599e59e 100644 --- a/radio/src/gui/9X/view_telemetry.cpp +++ b/radio/src/gui/9X/view_telemetry.cpp @@ -56,12 +56,12 @@ void displayRssiLine() #if !defined(CPUARM) rssi = min((uint8_t)99, frskyData.rssi[1].value); lcd_putsLeft(STATUS_BAR_Y, STR_TX); lcd_outdezNAtt(4*FW+1, STATUS_BAR_Y, rssi, LEADING0, 2); - lcd_rect(BAR_LEFT+1, 57, 38, 7); + lcdDrawRect(BAR_LEFT+1, 57, 38, 7); drawFilledRect(BAR_LEFT+1, 58, 4*rssi/11, 5, (rssi < getRssiAlarmValue(0)) ? DOTTED : SOLID); #endif rssi = min((uint8_t)99, TELEMETRY_RSSI()); lcd_puts(104, STATUS_BAR_Y, STR_RX); lcd_outdezNAtt(105+4*FW, STATUS_BAR_Y, rssi, LEADING0, 2); - lcd_rect(65, 57, 38, 7); + lcdDrawRect(65, 57, 38, 7); uint8_t v = 4*rssi/11; drawFilledRect(66+36-v, 58, v, 5, (rssi < getRssiAlarmValue(0)) ? DOTTED : SOLID); } @@ -76,9 +76,9 @@ void displayGpsTime() { uint8_t att = (TELEMETRY_STREAMING() ? LEFT|LEADING0 : LEFT|LEADING0|BLINK); lcd_outdezNAtt(CENTER_OFS+6*FW+7, STATUS_BAR_Y, frskyData.hub.hour, att, 2); - lcd_putcAtt(CENTER_OFS+8*FW+4, STATUS_BAR_Y, ':', att); + lcdDrawChar(CENTER_OFS+8*FW+4, STATUS_BAR_Y, ':', att); lcd_outdezNAtt(CENTER_OFS+9*FW+2, STATUS_BAR_Y, frskyData.hub.min, att, 2); - lcd_putcAtt(CENTER_OFS+11*FW-1, STATUS_BAR_Y, ':', att); + lcdDrawChar(CENTER_OFS+11*FW-1, STATUS_BAR_Y, ':', att); lcd_outdezNAtt(CENTER_OFS+12*FW-3, STATUS_BAR_Y, frskyData.hub.sec, att, 2); lcd_status_line(); } @@ -261,7 +261,7 @@ bool displayGaugesTelemetryScreen(FrSkyScreenData & screen) #else lcd_putsiAtt(0, y+barHeight-5, STR_VTELEMCHNS, source, 0); #endif - lcd_rect(BAR_LEFT, y, BAR_WIDTH+1, barHeight+2); + lcdDrawRect(BAR_LEFT, y, BAR_WIDTH+1, barHeight+2); #if defined(CPUARM) getvalue_t value = getValue(source); #else @@ -314,7 +314,7 @@ bool displayGaugesTelemetryScreen(FrSkyScreenData & screen) } if (thresholdX) { - lcd_vlineStip(BAR_LEFT+1+thresholdX, y-2, barHeight+3, DOTTED); + lcdDrawVerticalLine(BAR_LEFT+1+thresholdX, y-2, barHeight+3, DOTTED); lcd_hline(BAR_LEFT+thresholdX, y-2, 3); } } diff --git a/radio/src/gui/9X/widgets.cpp b/radio/src/gui/9X/widgets.cpp index d4805cfd7..f2ada57cf 100644 --- a/radio/src/gui/9X/widgets.cpp +++ b/radio/src/gui/9X/widgets.cpp @@ -67,19 +67,19 @@ void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr) { lcd_outdezAtt(LCD_W, 0, count, attr); coord_t x = 1+LCD_W-FW*(count>9 ? 3 : 2); - lcd_putcAtt(x, 0, '/', attr); + lcdDrawChar(x, 0, '/', attr); lcd_outdezAtt(x, 0, index+1, attr); } #if !defined(CPUM64) void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { - lcd_vlineStip(x, y, h, DOTTED); + lcdDrawVerticalLine(x, y, h, DOTTED); coord_t yofs = (h * offset) / count; coord_t yhgt = (h * visible) / count; if (yhgt + yofs > h) yhgt = h - yofs; - lcd_vlineStip(x, y + yofs, yhgt, SOLID, FORCE); + lcdDrawVerticalLine(x, y + yofs, yhgt, SOLID, FORCE); } #endif @@ -149,7 +149,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode)*10 : delta); else value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode) : delta); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } if (GV_IS_GV_VALUE(value, min, max)) { @@ -169,7 +169,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m if (idx < 0) { value = (int16_t) GV_CALC_VALUE_IDX_NEG(idx, delta); idx = -idx; - lcd_putcAtt(x-6, y, '-', attr); + lcdDrawChar(x-6, y, '-', attr); } else { value = (int16_t) GV_CALC_VALUE_IDX_POS(idx-1, delta); @@ -193,7 +193,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m if (invers && event == EVT_KEY_LONG(KEY_ENTER)) { s_editMode = !s_editMode; value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode) : delta); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } if (GV_IS_GV_VALUE(value, min, max)) { if (attr & LEFT) @@ -208,7 +208,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m if (idx < 0) { value = (int16_t) GV_CALC_VALUE_IDX_NEG(idx, delta); idx = -idx; - lcd_putcAtt(x-6, y, '-', attr); + lcdDrawChar(x-6, y, '-', attr); } else { value = (int16_t) GV_CALC_VALUE_IDX_POS(idx, delta); diff --git a/radio/src/gui/Horus/helpers.cpp b/radio/src/gui/Horus/helpers.cpp index f63f00db2..58de6d51d 100644 --- a/radio/src/gui/Horus/helpers.cpp +++ b/radio/src/gui/Horus/helpers.cpp @@ -41,391 +41,3 @@ uint8_t switchToMix(uint8_t source) div_t qr = div(source-1, 3); return qr.quot+MIXSRC_FIRST_SWITCH; } - -int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable) -{ - do { - current += inc; - if (current < min) - current = max; - else if (current > max) - current = min; - if (!isValueAvailable || isValueAvailable(current)) - return current; - } while(1); - return 0; -} - -bool isInputAvailable(int input) -{ - for (int i=0; ichn == input) - return true; - } - return false; -} - -bool isChannelUsed(int channel) -{ - for (int i=0; isrcRaw == 0) return false; - if (md->destCh == channel) return true; - if (md->destCh > channel) return false; - } - return false; -} - -int getChannelsUsed() -{ - int result = 0; - int lastCh = -1; - for (int i=0; isrcRaw == 0) return result; - if (md->destCh != lastCh) { ++result; lastCh = md->destCh; } - } - return result; -} - -bool isSourceAvailable(int source) -{ - if (source>=MIXSRC_FIRST_INPUT && source<=MIXSRC_LAST_INPUT) { - return isInputAvailable(source - MIXSRC_FIRST_INPUT); - } - -#if defined(LUA_MODEL_SCRIPTS) - if (source>=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) { - div_t qr = div(source-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); - return (qr.rem=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) - return false; -#endif - - if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { - return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); - } - - if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) { - return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); - } - -#if !defined(HELI) - if (source>=MIXSRC_CYC1 && source<=MIXSRC_CYC3) - return false; -#endif - - if (source>=MIXSRC_CH1 && source<=MIXSRC_LAST_CH) { - return isChannelUsed(source-MIXSRC_CH1); - } - - if (source>=MIXSRC_FIRST_LOGICAL_SWITCH && source<=MIXSRC_LAST_LOGICAL_SWITCH) { - LogicalSwitchData * cs = lswAddress(source-MIXSRC_FIRST_LOGICAL_SWITCH); - return (cs->func != LS_FUNC_NONE); - } - -#if !defined(GVARS) - if (source>=MIXSRC_GVAR1 && source<=MIXSRC_LAST_GVAR) - return false; -#endif - - if (source>=MIXSRC_RESERVE1 && source<=MIXSRC_RESERVE5) - return false; - - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - if (qr.rem == 0) - return isTelemetryFieldAvailable(qr.quot); - else - return isTelemetryFieldComparisonAvailable(qr.quot); - } - - return true; -} - -bool isSourceAvailableInGlobalFunctions(int source) -{ - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - return false; - } - return isSourceAvailable(source); -} - -bool isSourceAvailableInCustomSwitches(int source) -{ - bool result = isSourceAvailable(source); - -#if defined(FRSKY) - if (result && source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - result = isTelemetryFieldComparisonAvailable(qr.quot); - } -#endif - - return result; -} - -bool isInputSourceAvailable(int source) -{ - if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { - return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); - } - - if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) - return true; - - if (source>=MIXSRC_FIRST_TRIM && source<=MIXSRC_LAST_TRIM) - return true; - - if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) - return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); - - if (source>=MIXSRC_FIRST_CH && source<=MIXSRC_LAST_CH) - return true; - - if (source>=MIXSRC_FIRST_TRAINER && source<=MIXSRC_LAST_TRAINER) - return true; - - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - return isTelemetryFieldAvailable(qr.quot) && isTelemetryFieldComparisonAvailable(qr.quot); - } - - return false; -} - -enum SwitchContext -{ - LogicalSwitchesContext, - ModelCustomFunctionsContext, - GeneralCustomFunctionsContext, - TimersContext, - MixesContext -}; - -bool isLogicalSwitchAvailable(int index) -{ - LogicalSwitchData * lsw = lswAddress(index); - return (lsw->func != LS_FUNC_NONE); -} - -bool isSwitchAvailable(int swtch, SwitchContext context) -{ - bool negative = false; - - if (swtch < 0) { - negative = true; - if (swtch == -SWSRC_ON || swtch == -SWSRC_ONE) { - return false; - } - swtch = -swtch; - } - - if (swtch >= SWSRC_SA0 && swtch <= SWSRC_LAST_SWITCH) { - div_t swinfo = switchInfo(swtch); - if (!SWITCH_EXISTS(swinfo.quot)) { - return false; - } - if (!IS_3POS(swinfo.quot)) { - if (negative) { - return false; - } - if (IS_3POS_MIDDLE(swinfo.rem)) { - return false; - } - } - return true; - } - -#if NUM_XPOTS > 0 - if (swtch >= SWSRC_FIRST_MULTIPOS_SWITCH && swtch <= SWSRC_LAST_MULTIPOS_SWITCH) { - int index = (swtch - SWSRC_FIRST_MULTIPOS_SWITCH) / XPOTS_MULTIPOS_COUNT; - if (IS_POT_MULTIPOS(POT1+index)) { - StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[POT1+index]; - return (calib->count >= ((swtch - SWSRC_FIRST_MULTIPOS_SWITCH) % XPOTS_MULTIPOS_COUNT)); - } - else { - return false; - } - } -#endif - - if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH && swtch <= SWSRC_LAST_LOGICAL_SWITCH) { - if (context == GeneralCustomFunctionsContext) { - return false; - } - else if (context != LogicalSwitchesContext) { - return isLogicalSwitchAvailable(swtch - SWSRC_FIRST_LOGICAL_SWITCH); - } - } - - if (context != ModelCustomFunctionsContext && context != GeneralCustomFunctionsContext && (swtch == SWSRC_ON || swtch == SWSRC_ONE)) { - return false; - } - - if (swtch >= SWSRC_FIRST_FLIGHT_MODE && swtch <= SWSRC_LAST_FLIGHT_MODE) { - if (context == MixesContext || context == GeneralCustomFunctionsContext) { - return false; - } - else { - swtch -= SWSRC_FIRST_FLIGHT_MODE; - if (swtch == 0) { - return true; - } - FlightModeData * fm = flightModeAddress(swtch); - return (fm->swtch != SWSRC_NONE); - } - } - - if (swtch >= SWSRC_FIRST_SENSOR && swtch <= SWSRC_LAST_SENSOR) { - return isTelemetryFieldAvailable(swtch - SWSRC_FIRST_SENSOR); - } - - return true; -} - -bool isSwitchAvailableInLogicalSwitches(int swtch) -{ - return isSwitchAvailable(swtch, LogicalSwitchesContext); -} - -bool isSwitchAvailableInCustomFunctions(int swtch) -{ - if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) - return isSwitchAvailable(swtch, ModelCustomFunctionsContext); - else - return isSwitchAvailable(swtch, GeneralCustomFunctionsContext); -} - -bool isSwitchAvailableInMixes(int swtch) -{ - return isSwitchAvailable(swtch, MixesContext); -} - -bool isSwitchAvailableInTimers(int swtch) -{ - if (swtch >= 0) { - if (swtch < TMRMODE_COUNT) - return true; - else - swtch -= TMRMODE_COUNT-1; - } - else { - if (swtch > -TMRMODE_COUNT) - return false; - else - swtch += TMRMODE_COUNT-1; - } - - return isSwitchAvailable(swtch, TimersContext); -} - -bool isThrottleSourceAvailable(int source) -{ - if (source >= THROTTLE_SOURCE_FIRST_POT && source < THROTTLE_SOURCE_FIRST_POT+NUM_POTS && !IS_POT_AVAILABLE(POT1+source-THROTTLE_SOURCE_FIRST_POT)) - return false; - else - return true; -} - -bool isLogicalSwitchFunctionAvailable(int function) -{ - return function != LS_FUNC_RANGE; -} - -bool isAssignableFunctionAvailable(int function) -{ -#if defined(OVERRIDE_CHANNEL_FUNCTION) || defined(GVARS) - bool modelFunctions = (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions); -#endif - - switch (function) { - case FUNC_OVERRIDE_CHANNEL: -#if defined(OVERRIDE_CHANNEL_FUNCTION) - return modelFunctions; -#else - return false; -#endif - case FUNC_ADJUST_GVAR: -#if defined(GVARS) - return modelFunctions; -#else - return false; -#endif -#if !defined(HAPTIC) - case FUNC_HAPTIC: -#endif - case FUNC_RESERVE4: -#if !defined(DANGEROUS_MODULE_FUNCTIONS) - case FUNC_RANGECHECK: - case FUNC_BIND: -#endif -#if !defined(LUA) - case FUNC_PLAY_SCRIPT: -#endif - case FUNC_RESERVE5: - return false; - - default: - return true; - } -} - -bool isSourceAvailableInGlobalResetSpecialFunction(int index) -{ - if (index >= FUNC_RESET_PARAM_FIRST_TELEM) - return false; - else - return isSourceAvailableInResetSpecialFunction(index); -} - -bool isSourceAvailableInResetSpecialFunction(int index) -{ - if (index >= FUNC_RESET_PARAM_FIRST_TELEM) { - TelemetrySensor & telemetrySensor = g_model.telemetrySensors[index-FUNC_RESET_PARAM_FIRST_TELEM]; - return telemetrySensor.isAvailable(); - } -#if TIMERS < 3 - else if (index == FUNC_RESET_TIMER3) { - return false; - } -#endif -#if TIMERS < 2 - else if (index == FUNC_RESET_TIMER2) { - return false; - } -#endif - else { - return true; - } -} - -bool isModuleAvailable(int module) -{ - return true; -} - -bool modelHasNotes() -{ - char filename[sizeof(MODELS_PATH)+1+sizeof(g_model.header.name)+sizeof(TEXT_EXT)] = MODELS_PATH "/"; - char *buf = strcat_modelname(&filename[sizeof(MODELS_PATH)], g_eeGeneral.currModel); - strcpy(buf, TEXT_EXT); - return isFileAvailable(filename); -} - -int getFirstAvailable(int min, int max, bool (*func)(int)) -{ - int retval = 0; - for (int i = min; i <= max; i++) { - if ((*func)(i)) { - retval = i; - break; - } - } - return retval; -} diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index fdef225f1..1ebc7dc71 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -37,7 +37,7 @@ #include "../../opentx.h" #if defined(SIMU) -display_t displayBuf[DISPLAY_BUF_SIZE]; +display_t displayBuf[DISPLAY_BUFFER_SIZE]; #endif uint16_t lcdColorTable[LCD_COLOR_COUNT]; @@ -123,7 +123,7 @@ void lcdPutFontPattern(coord_t x, coord_t y, const uint8_t * font, const uint16_ lcdNextPos = x + width; } -void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) +void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) { int fontindex = FONTSIZE(flags) >> 8; const pm_uchar * font = fontsTable[fontindex]; @@ -131,11 +131,6 @@ void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) lcdPutFontPattern(x, y, font, fontspecs, getMappedChar(c), flags); } -void lcd_putc(coord_t x, coord_t y, const unsigned char c) -{ - lcd_putcAtt(x, y, c, 0); -} - uint8_t getStringInfo(const char *s) { uint8_t result = 0; @@ -180,11 +175,11 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags += TEXT_INVERTED_COLOR - TEXT_COLOR; } if (FONTSIZE(flags) == TINSIZE) - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); else if (FONTSIZE(flags) == SMLSIZE) - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); else - lcdDrawFilledRect(x-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } char str[256]; @@ -330,13 +325,8 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, int len, lcd_putsAtt(x, y, s, flags); } -void lcd_hline(coord_t x, coord_t y, coord_t w, LcdFlags att) -{ - lcd_hlineStip(x, y, w, 0xff, att); -} - #if !defined(BOOT) -void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFlags att) +void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFlags att) { int dx = x2-x1; /* the horizontal distance of the line */ int dy = y2-y1; /* the vertical distance of the line */ @@ -380,44 +370,39 @@ void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFl } #endif -void lcd_vline(coord_t x, scoord_t y, scoord_t h) -{ - lcd_vlineStip(x, y, h, SOLID); -} - -void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) +void lcdDrawRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) { if (att & ROUND) { - lcd_vlineStip(x, y+1, h-2, pat, att); - lcd_vlineStip(x+w-1, y+1, h-2, pat, att); - lcd_hlineStip(x+1, y+h-1, w-2, pat, att); - lcd_hlineStip(x+1, y, w-2, pat, att); + lcdDrawVerticalLine(x, y+1, h-2, pat, att); + lcdDrawVerticalLine(x+w-1, y+1, h-2, pat, att); + lcdDrawHorizontalLine(x+1, y+h-1, w-2, pat, att); + lcdDrawHorizontalLine(x+1, y, w-2, pat, att); } else { - lcd_vlineStip(x, y, h, pat, att); - lcd_vlineStip(x+w-1, y, h, pat, att); - lcd_hlineStip(x+1, y+h-1, w-2, pat, att); - lcd_hlineStip(x+1, y, w-2, pat, att); + lcdDrawVerticalLine(x, y, h, pat, att); + lcdDrawVerticalLine(x+w-1, y, h, pat, att); + lcdDrawHorizontalLine(x+1, y+h-1, w-2, pat, att); + lcdDrawHorizontalLine(x+1, y, w-2, pat, att); } } #if defined(SIMU) -void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) +void lcdDrawSolidFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) { for (scoord_t i=y; i> 1) + ((pat & 1) << 7); } } @@ -433,7 +418,7 @@ void getTimerString(char * str, putstime_t tme, LcdFlags att) div_t qr; if (tme < 0) { - // TODO lcd_putcAtt(x - ((att & DBLSIZE) ? FW+2 : ((att & MIDSIZE) ? FW+0 : FWNUM)), y, '-', att); + // TODO lcdDrawChar(x - ((att & DBLSIZE) ? FW+2 : ((att & MIDSIZE) ? FW+0 : FWNUM)), y, '-', att); tme = -tme; *str++ = '-'; } @@ -504,15 +489,15 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) div_t qr = div(idx-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); #if defined(LUA_MODEL_SCRIPTS) if (qr.quot < MAX_SCRIPTS && qr.rem < scriptInputsOutputs[qr.quot].outputsCount) { - lcd_putcAtt(x+2, y+1, '1'+qr.quot, TINSIZE); - lcdDrawFilledRect(x, y, 7, 7); + lcdDrawChar(x+2, y+1, '1'+qr.quot, TINSIZE); + lcdDrawSolidFilledRect(x, y, 7, 7); lcd_putsnAtt(x+8, y, scriptInputsOutputs[qr.quot].outputs[qr.rem].name, att & STREXPANDED ? 9 : 4, att); } else #endif { putsStrIdx(x, y, "LUA", qr.quot+1, att); - lcd_putcAtt(x+20, y, 'a'+qr.rem, att); + lcdDrawChar(x+20, y, 'a'+qr.rem, att); } } @@ -540,7 +525,7 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) putsStrIdx(x, y, STR_CH, idx-MIXSRC_CH1+1, att); #if 0 if (ZEXIST(g_model.limitData[idx-MIXSRC_CH1].name) && (att & STREXPANDED)) { - lcd_putcAtt(lcdNextPos, y, ' ', att); + lcdDrawChar(lcdNextPos, y, ' ', att); lcd_putsnAtt(lcdNextPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att); } #endif @@ -682,7 +667,7 @@ void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att) if (neg) { if ((att&INVERS) && ((~att&BLINK) || BLINK_ON_PHASE)) att &= ~(INVERS|BLINK); - lcd_putcAtt(x-3, y, '!', att); + lcdDrawChar(x-3, y, '!', att); } } @@ -810,6 +795,12 @@ void lcdDrawPixel(display_t * p, display_t value) *p = value; } +void lcdDrawPixel(coord_t x, coord_t y, display_t value) +{ + display_t * p = PIXEL_PTR(x, y); + lcdDrawPixel(p, value); +} + void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color) { display_t * p = PIXEL_PTR(x, y); @@ -843,7 +834,7 @@ void lcdDrawPoint(coord_t x, coord_t y, LcdFlags att) } } -void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) +void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) { if (y >= LCD_H) return; if (x+w > LCD_W) { w = LCD_W - x; } @@ -864,7 +855,7 @@ void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) } } -void lcd_vlineStip(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att) +void lcdDrawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att) { if (x >= LCD_W) return; if (y >= LCD_H) return; @@ -914,7 +905,218 @@ void lcdDrawBitmap(coord_t x, coord_t y, const uint16_t * img, coord_t offset, c } #endif -void drawBlackOverlay() +void lcdDrawBlackOverlay() { - // TODO drawFilledRect(0, 0, LCD_W, LCD_H, SOLID, OPAQUE(10, GREY(3))); + lcdDrawFilledRect(0, 0, LCD_W, LCD_H, SOLID, TEXT_COLOR | (8<<24)); +} + +void lcdDrawCircle(int x0, int y0, int radius) +{ + int x = radius; + int y = 0; + int decisionOver2 = 1 - x; + + while (y <= x) { + lcdDrawPixel(x+x0, y+y0, WHITE); + lcdDrawPixel(y+x0, x+y0, WHITE); + lcdDrawPixel(-x+x0, y+y0, WHITE); + lcdDrawPixel(-y+x0, x+y0, WHITE); + lcdDrawPixel(-x+x0, -y+y0, WHITE); + lcdDrawPixel(-y+x0, -x+y0, WHITE); + lcdDrawPixel(x+x0, -y+y0, WHITE); + lcdDrawPixel(y+x0, -x+y0, WHITE); + y++; + if (decisionOver2 <= 0) { + decisionOver2 += 2*y + 1; + } + else { + x--; + decisionOver2 += 2 * (y-x) + 1; + } + } +} + +#define PI 3.14159265 + +void lcdDrawPie(int x0, int y0, int radius, int startAngle, int endAngle) +{ + int slope1s, slope1e, slope2s, slope2e; + + if (startAngle >= 360 || endAngle <= 0) + return; + + float angle1 = float(startAngle) * PI / 180; + float angle2 = float(endAngle) * PI / 180; + + if (startAngle == 0) { + slope1e = 100000; + slope2s = -100000; + } + else if (startAngle >= 180) { + slope1e = -100000; + slope2s = cos(angle1)*100/sin(angle1); + } + else { + slope1e = cos(angle1)*100/sin(angle1); + slope2s = -100000; + } + + if (endAngle == 360) { + slope1s = -100000; + slope2e = 100000; + } + else if (endAngle >= 180) { + slope1s = -100000; + slope2e = -cos(angle2)*100/sin(angle2); + } + else { + slope1s = cos(angle2)*100/sin(angle2); + slope2e = -100000; + } + + for (int y=0; y<=radius; y++) { + for (int x=0; x<=radius; x++) { + if (x*x+y*y <= radius*radius) { + int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); + if (slope >= slope1s && slope < slope1e) { + lcdDrawPixel(x0+x, y0-y, WHITE); + } + if (-slope >= slope1s && -slope < slope1e) { + lcdDrawPixel(x0+x, y0+y, WHITE); + } + if (slope >= slope2s && slope < slope2e) { + lcdDrawPixel(x0-x, y0-y, WHITE); + } + if (-slope >= slope2s && -slope < slope2e) { + lcdDrawPixel(x0-x, y0+y, WHITE); + } + } + } + } +} + +void lcdDrawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle) +{ + const uint16_t * q = img; + coord_t width = *q++; + coord_t height = *q++; + + int slope1s, slope1e, slope2s, slope2e; + + if (startAngle >= 360 || endAngle <= 0) + return; + + float angle1 = float(startAngle) * PI / 180; + float angle2 = float(endAngle) * PI / 180; + + if (startAngle == 0) { + slope1e = 100000; + slope2s = -100000; + } + else if (startAngle >= 180) { + slope1e = -100000; + slope2s = cos(angle1)*100/sin(angle1); + } + else { + slope1e = cos(angle1)*100/sin(angle1); + slope2s = -100000; + } + + if (endAngle == 360) { + slope1s = -100000; + slope2e = 100000; + } + else if (endAngle >= 180) { + slope1s = -100000; + slope2e = -cos(angle2)*100/sin(angle2); + } + else { + slope1s = cos(angle2)*100/sin(angle2); + slope2e = -100000; + } + + int w2 = width/2; + int h2 = height/2; + + for (int y=h2-1; y>=0; y--) { + for (int x=w2-1; x>=0; x--) { + int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); + if (slope >= slope1s && slope < slope1e) { + displayBuf[(y0+h2-y)*LCD_W + x0+w2+x] = q[(h2-y)*width + w2+x]; + } + if (-slope >= slope1s && -slope < slope1e) { + displayBuf[(y0+h2+y)*LCD_W + x0+w2+x] = q[(h2+y)*width + w2+x]; + } + if (slope >= slope2s && slope < slope2e) { + displayBuf[(y0+h2-y)*LCD_W + x0+w2-x] = q[(h2-y)*width + w2-x]; + } + if (-slope >= slope2s && -slope < slope2e) { + displayBuf[(y0+h2+y)*LCD_W + x0+w2-x] = q[(h2+y)*width + w2-x]; + } + } + } +} + +void lcdDrawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFlags flags, int startAngle, int endAngle) +{ + coord_t width = *((uint16_t *)img); + coord_t height = *(((uint16_t *)img)+1); + const uint8_t * q = img+4; + + int slope1s, slope1e, slope2s, slope2e; + + if (startAngle >= 360 || endAngle <= 0) + return; + + display_t color = lcdColorTable[COLOR_IDX(flags)]; + + float angle1 = float(startAngle) * PI / 180; + float angle2 = float(endAngle) * PI / 180; + + if (startAngle == 0) { + slope1e = 100000; + slope2s = -100000; + } + else if (startAngle >= 180) { + slope1e = -100000; + slope2s = cos(angle1)*100/sin(angle1); + } + else { + slope1e = cos(angle1)*100/sin(angle1); + slope2s = -100000; + } + + if (endAngle == 360) { + slope1s = -100000; + slope2e = 100000; + } + else if (endAngle >= 180) { + slope1s = -100000; + slope2e = -cos(angle2)*100/sin(angle2); + } + else { + slope1s = cos(angle2)*100/sin(angle2); + slope2e = -100000; + } + + int w2 = width/2; + int h2 = height/2; + + for (int y=h2-1; y>=0; y--) { + for (int x=w2-1; x>=0; x--) { + int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); + if (slope >= slope1s && slope < slope1e) { + lcdDrawTransparentPixel(x0+w2+x, y0+h2-y, q[(h2-y)*width + w2+x], color); + } + if (-slope >= slope1s && -slope < slope1e) { + lcdDrawTransparentPixel(x0+w2+x, y0+h2+y, q[(h2+y)*width + w2+x], color); + } + if (slope >= slope2s && slope < slope2e) { + lcdDrawTransparentPixel(x0+w2-x, y0+h2-y, q[(h2-y)*width + w2-x], color); + } + if (-slope >= slope2s && -slope < slope2e) { + lcdDrawTransparentPixel(x0+w2-x, y0+h2+y, q[(h2+y)*width + w2-x], color); + } + } + } } diff --git a/radio/src/gui/Horus/lcd.h b/radio/src/gui/Horus/lcd.h index 89d18b258..e53092fd0 100644 --- a/radio/src/gui/Horus/lcd.h +++ b/radio/src/gui/Horus/lcd.h @@ -139,6 +139,7 @@ extern uint16_t lcdColorTable[LCD_COLOR_COUNT]; void lcdColorsInit(); #define OPACITY_MAX 0x0F +#define OPACITY(x) ((x)<<24) #define COLOR(index) ((index) << 16) #define TEXT_COLOR COLOR(TEXT_COLOR_INDEX) @@ -170,27 +171,26 @@ void lcdColorsInit(); #define COLOR_JOIN(r, g, b) \ (((r) << 11) + ((g) << 5) + (b)) -#define display_t uint16_t -#define DISPLAY_BUF_SIZE (LCD_W*LCD_H) +#define display_t uint16_t +#define DISPLAY_PIXELS_COUNT (LCD_W*LCD_H) +#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_PIXELS_COUNT) #if defined(SIMU) -extern display_t displayBuf[DISPLAY_BUF_SIZE]; +extern display_t displayBuf[DISPLAY_BUFFER_SIZE]; #else -extern uint32_t CurrentFrameBuffer; -#define displayBuf ((uint16_t *)CurrentFrameBuffer) +extern uint32_t CurrentFrameBuffer; +#define displayBuf ((uint16_t *)CurrentFrameBuffer) #endif #define lcdRefreshWait() -#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) -#define DISPLAY_END (displayBuf + DISPLAY_BUF_SIZE) -#define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) + +#define DISPLAY_END (displayBuf + DISPLAY_BUFFER_SIZE) +#define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) extern coord_t lcdNextPos; -void lcdStartScreen(); -void lcd_putc(coord_t x, coord_t y, const unsigned char c); -void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags attr=TEXT_COLOR); +void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags attr=TEXT_COLOR); void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags attr=TEXT_COLOR); void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s, uint8_t idx, LcdFlags attr=TEXT_COLOR); void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, unsigned char len, LcdFlags attr=TEXT_COLOR); @@ -235,54 +235,56 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att=0); void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color); void lcdDrawTransparentPixel(display_t * p, uint8_t opacity, uint16_t color); void lcdDrawPoint(coord_t x, coord_t y, LcdFlags att=0); -void lcd_mask(uint8_t *p, uint8_t mask, LcdFlags att=0); -void lcd_hline(coord_t x, coord_t y, coord_t w, LcdFlags att=0); -void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); -void lcd_vline(coord_t x, scoord_t y, scoord_t h); -void lcd_vlineStip(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); -void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); +void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); +void lcdDrawVerticalLine(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); +void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); #if !defined(SIMU) -inline void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) +inline void lcdDrawSolidFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) { - lcdDrawFilledRectDMA(x, y, w, h, lcdColorTable[COLOR_IDX(att)]); + lcdDrawSolidFilledRectDMA(x, y, w, h, lcdColorTable[COLOR_IDX(att)]); } #else -void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att); +void lcdDrawSolidFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att); #endif -inline void lcdDrawHorizontalLine(coord_t x, scoord_t y, coord_t w, LcdFlags att) +inline void lcdClear() { - lcdDrawFilledRect(x, y, w, 1, att); + lcdDrawSolidFilledRect(0, 0, LCD_W, LCD_H, 0); } -inline void lcdDrawVerticalLine(coord_t x, scoord_t y, coord_t h, LcdFlags att) +inline void lcdDrawSolidHorizontalLine(coord_t x, scoord_t y, coord_t w, LcdFlags att) { - lcdDrawFilledRect(x, y, 1, h, att); + lcdDrawSolidFilledRect(x, y, w, 1, att); } -inline void lcdDrawRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) +inline void lcdDrawSolidVerticalLine(coord_t x, scoord_t y, coord_t h, LcdFlags att) { - lcdDrawVerticalLine(x, y, h, att); - lcdDrawVerticalLine(x+w-1, y, h, att); - lcdDrawHorizontalLine(x, y, w, att); - lcdDrawHorizontalLine(x, y+h-1, w, att); + if (h<0) { y+=h; h=-h; } + lcdDrawSolidFilledRect(x, y, 1, h, att); } -void lcdDrawFilledRectWithAttributes(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att); -void drawBlackOverlay(); -void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); +inline void lcdDrawSolidRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) +{ + lcdDrawSolidVerticalLine(x, y, h, att); + lcdDrawSolidVerticalLine(x+w-1, y, h, att); + lcdDrawSolidHorizontalLine(x, y, w, att); + lcdDrawSolidHorizontalLine(x, y+h-1, w, att); +} + +void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att); +void lcdDrawBlackOverlay(); +void lcdDrawRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); +void lcdDrawCircle(int x0, int y0, int radius); +void lcdDrawPie(int x0, int y0, int radius, int angle1=0, int angle2=360); +void lcdDrawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle); +void lcdDrawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFlags flags=0, int startAngle=0, int endAngle=360); inline void lcdDrawSquare(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { - lcdDrawRect(x, y, w, w, att); + lcdDrawSolidRect(x, y, w, w, att); } -#define V_BAR(xx, yy, ll) \ - lcd_vline(xx-1,yy-ll,ll); \ - lcd_vline(xx ,yy-ll,ll); \ - lcd_vline(xx+1,yy-ll,ll); - void lcd_img(coord_t x, coord_t y, const pm_uchar * img, uint8_t idx, LcdFlags att=0); int getTextWidth(const pm_char *s, int len=0, LcdFlags flags=0); @@ -293,12 +295,6 @@ void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags fl void lcdSetContrast(); #define lcdOff(...) -void lcdSendByte(uint8_t data); -void lcdSend(const uint8_t * data, uint32_t size); -void lcdSendString(const char * s); -uint32_t lcdReceive(uint8_t * data, uint32_t len); -void lcdClearRxBuffer(); - const pm_char * bmpLoad(uint8_t *dest, const char *filename, const unsigned int width, const unsigned int height); #if defined(BOOT) @@ -309,7 +305,7 @@ const pm_char * bmpLoad(uint8_t *dest, const char *filename, const unsigned int #ifdef SIMU extern bool lcd_refresh; - extern display_t lcd_buf[DISPLAY_BUF_SIZE]; + extern display_t lcd_buf[DISPLAY_BUFFER_SIZE]; #endif char *strAppend(char * dest, const char * source, int len=0); @@ -317,15 +313,4 @@ char *strSetCursor(char *dest, int position); char *strAppendDate(char * str, bool time=false); char *strAppendFilename(char * dest, const char * filename, const int size); -#define BITMAP_IDX_SPLASH 0x00 -#define BITMAP_IDX_ALERT 0x01 -#define BITMAP_IDX_WARNING 0x02 -#define BITMAP_IDX_MESSAGE 0x03 -#define BITMAP_IDX_USB 0x04 -#define BITMAP_IDX_STICKS_FIRST 0x05 // 4 bitmaps -#define BITMAP_IDX_BATTERY_FIRST 0x09 // 5 bitmaps -#define BITMAP_IDX_RSSI_FIRST 0x0E // 6 bitmaps -#define BITMAP_IDX_CHECKBOX_FIRST 0x14 // 4 bitmaps -#define BITMAP_IDX_MODEL_FIRST 0x80 - #endif // _LCD_H_ diff --git a/radio/src/gui/Horus/menu_general_calib.cpp b/radio/src/gui/Horus/menu_general_calib.cpp index f3d988c3a..ebc3a901e 100644 --- a/radio/src/gui/Horus/menu_general_calib.cpp +++ b/radio/src/gui/Horus/menu_general_calib.cpp @@ -163,7 +163,7 @@ void menuCommonCalib(evt_t event) } #endif g_eeGeneral.chkSum = evalChkSum(); - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); reusableBuffer.calib.state = 4; break; diff --git a/radio/src/gui/Horus/menu_general_sdmanager.cpp b/radio/src/gui/Horus/menu_general_sdmanager.cpp index c9bb5e189..550e77425 100644 --- a/radio/src/gui/Horus/menu_general_sdmanager.cpp +++ b/radio/src/gui/Horus/menu_general_sdmanager.cpp @@ -92,7 +92,7 @@ void onSdManagerMenu(const char *result) } else if (result == STR_PASTE) { f_getcwd(lfn, _MAX_LFN); - POPUP_WARNING(fileCopy(clipboard.data.sd.filename, clipboard.data.sd.directory, lfn)); + POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, lfn)); REFRESH_FILES(); } else if (result == STR_RENAME_FILE) { @@ -132,7 +132,7 @@ void onSdManagerMenu(const char *result) else if (result == STR_ASSIGN_BITMAP) { strAppendFilename(g_model.header.bitmap, line, sizeof(g_model.header.bitmap)); memcpy(modelHeaders[g_eeGeneral.currModel].bitmap, g_model.header.bitmap, sizeof(g_model.header.bitmap)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } #endif else if (result == STR_VIEW_TEXT) { diff --git a/radio/src/gui/Horus/menu_general_setup.cpp b/radio/src/gui/Horus/menu_general_setup.cpp index 3a3981b43..1e7046ff6 100644 --- a/radio/src/gui/Horus/menu_general_setup.cpp +++ b/radio/src/gui/Horus/menu_general_setup.cpp @@ -122,11 +122,11 @@ void menuGeneralSetup(evt_t event) if (s_warning_result) { s_warning_result = 0; g_eeGeneral.fai = true; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); } #endif - MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 2|NAVIGATION_LINE_BY_LINE, 2|NAVIGATION_LINE_BY_LINE, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO_CPUARM(LABEL(VARIO)) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) LABEL(ALARMS), 0, 0, 0, CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, CASE_MAVLINK(0) 0, 0, LABEL(TX_MODE), 1/*to force edit mode*/ }); + MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 2|NAVIGATION_LINE_BY_LINE, 2|NAVIGATION_LINE_BY_LINE, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO_CPUARM(LABEL(VARIO)) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) LABEL(ALARMS), 0, 0, 0, CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, CASE_MAVLINK(0) 0, 0, 0, 0, 1/*to force edit mode*/ }); int sub = m_posVert; @@ -143,7 +143,7 @@ void menuGeneralSetup(evt_t event) LcdFlags flags = 0; if (attr && m_posHorz < 0) { flags |= INVERS; - lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+YEAR_SEPARATOR_OFFSET, y, "-", flags); lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+MONTH_SEPARATOR_OFFSET, y, "-", flags); @@ -182,7 +182,7 @@ void menuGeneralSetup(evt_t event) LcdFlags flags = 0; if (attr && m_posHorz < 0) { flags |= INVERS; - lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+HOUR_SEPARATOR_OFFSET, y, ":", flags); lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+MINUTE_SEPARATOR_OFFSET, y, ":", flags); @@ -215,7 +215,7 @@ void menuGeneralSetup(evt_t event) LcdFlags color = WHITE; if (attr && m_posHorz < 0) { color = BLACK; - lcdDrawFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 90, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 90, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } putsVolts(RADIO_SETUP_2ND_COLUMN, y, 90+g_eeGeneral.vBatMin, color|(m_posHorz==0 ? attr : 0)|LEFT|NO_UNIT); lcd_puts(RADIO_SETUP_2ND_COLUMN+20, y, "-"); @@ -498,6 +498,7 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_STICK_MODE: { + lcd_putsLeft(y, NO_INDENT(STR_MODE)); char s[2] = " "; s[0] = '1'+g_eeGeneral.stickMode; lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, s, attr); diff --git a/radio/src/gui/Horus/menu_general_trainer.cpp b/radio/src/gui/Horus/menu_general_trainer.cpp index 26c3df358..a399e9dc8 100644 --- a/radio/src/gui/Horus/menu_general_trainer.cpp +++ b/radio/src/gui/Horus/menu_general_trainer.cpp @@ -115,7 +115,7 @@ void menuGeneralTrainer(evt_t event) if (attr) { if (event==EVT_KEY_LONG(KEY_ENTER)){ memcpy(g_eeGeneral.trainer.calib, ppmInput, sizeof(g_eeGeneral.trainer.calib)); - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); AUDIO_WARNING1(); } } diff --git a/radio/src/gui/Horus/menu_model.cpp b/radio/src/gui/Horus/menu_model.cpp index 651074dfc..2cf16ada2 100644 --- a/radio/src/gui/Horus/menu_model.cpp +++ b/radio/src/gui/Horus/menu_model.cpp @@ -173,13 +173,13 @@ void editName(coord_t x, coord_t y, char * name, uint8_t size, evt_t event, uint if (c != v) { name[cur] = v; - eeDirty(g_menuPos[0] == 0 ? EE_MODEL : EE_GENERAL); + storageDirty(g_menuPos[0] == 0 ? EE_MODEL : EE_GENERAL); } lcd_putsnAtt(x, y, name, size, ZCHAR | mode); coord_t w = (editNameCursorPos == 0 ? 0 : getTextWidth(name, editNameCursorPos, ZCHAR)); char s[] = { idx2char(v), '\0' }; - lcdDrawFilledRect(x+w, y-INVERT_VERT_MARGIN, getTextWidth(s, 1)-1, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x+w-1, y-INVERT_VERT_MARGIN, getTextWidth(s, 1)+1, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); lcd_putsAtt(x+w, y, s, TEXT_INVERTED_COLOR); } else { diff --git a/radio/src/gui/Horus/menu_model_curves.cpp b/radio/src/gui/Horus/menu_model_curves.cpp index 79f55bd06..90ec83179 100644 --- a/radio/src/gui/Horus/menu_model_curves.cpp +++ b/radio/src/gui/Horus/menu_model_curves.cpp @@ -71,7 +71,7 @@ void DrawCurve(int offset=0) do { point_t point = getPoint(i++); if (point.x == 0) break; - lcdDrawFilledRect(point.x-offset, point.y-1, 3, 3, TEXT_COLOR); // do markup square + lcdDrawSolidFilledRect(point.x-offset, point.y-1, 3, 3, TEXT_COLOR); // do markup square } while(1); } @@ -90,7 +90,7 @@ bool moveCurve(uint8_t index, int8_t shift) curveEnd[index++] += shift; } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); return true; } @@ -212,8 +212,8 @@ void menuModelCurveOne(evt_t event) DrawCurve(6); } - lcd_putsAtt(115, MENU_FOOTER_TOP, "X", HEADER_COLOR); - lcd_putsAtt(145, MENU_FOOTER_TOP, "Y", HEADER_COLOR); + // lcd_putsAtt(115, MENU_FOOTER_TOP, "X", HEADER_COLOR); + // lcd_putsAtt(145, MENU_FOOTER_TOP, "Y", HEADER_COLOR); coord_t posY = MENU_CONTENT_TOP; attr = (s_editMode > 0 ? INVERS|BLINK : INVERS); @@ -233,15 +233,15 @@ void menuModelCurveOne(evt_t event) if (i>=pointsOfs && i0 && i<5+crv.points-1) x = points[5+crv.points+i-1]; - lcd_outdezAtt(110, posY, i+1, LEFT); - lcd_outdezAtt(130, posY, x, LEFT|(selectionMode==1?attr:0)); - lcd_outdezAtt(160, posY, points[i], LEFT|(selectionMode==2?attr:0)); + lcd_outdezAtt(120, posY, i+1, LEFT|TEXT_DISABLE_COLOR); + lcd_outdezAtt(150, posY, x, LEFT|(selectionMode==1?attr:0)); + lcd_outdezAtt(200, posY, points[i], LEFT|(selectionMode==2?attr:0)); posY += FH; } if (selectionMode > 0) { // do selection square - lcdDrawFilledRect(point.x-7, point.y-2, 5, 5, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(point.x-7, point.y-2, 5, 5, TEXT_INVERTED_BGCOLOR); if (s_editMode > 0) { if (selectionMode == 1) CHECK_INCDEC_MODELVAR(event, points[5+crv.points+i-1], i==1 ? -100 : points[5+crv.points+i-2], i==5+crv.points-2 ? 100 : points[5+crv.points+i]); // edit X @@ -256,7 +256,7 @@ void menuModelCurveOne(evt_t event) } if (5+crv.points > NUM_BODY_LINES) { - lcdDrawScrollbar(167, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); + lcdDrawScrollbar(250, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); } } diff --git a/radio/src/gui/Horus/menu_model_custom_functions.cpp b/radio/src/gui/Horus/menu_model_custom_functions.cpp index f39cf865d..4f7af3ce8 100644 --- a/radio/src/gui/Horus/menu_model_custom_functions.cpp +++ b/radio/src/gui/Horus/menu_model_custom_functions.cpp @@ -57,7 +57,7 @@ void onCustomFunctionsFileSelectionMenu(const char *result) strcpy(directory, SOUNDS_PATH); strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); } - if (!listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cf->play.name), NULL)) { + if (!sdListFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cf->play.name), NULL)) { POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); s_menu_flags = 0; } @@ -65,7 +65,7 @@ void onCustomFunctionsFileSelectionMenu(const char *result) else { // The user choosed a file in the list memcpy(cf->play.name, result, sizeof(cf->play.name)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); if (func == FUNC_PLAY_SCRIPT) { LUA_LOAD_MODEL_SCRIPTS(); } @@ -93,21 +93,21 @@ void onCustomFunctionsMenu(const char *result) } else if (result == STR_PASTE) { *cfn = clipboard.data.cfn; - eeDirty(eeFlags); + storageDirty(eeFlags); } else if (result == STR_CLEAR) { memset(cfn, 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); + storageDirty(eeFlags); } else if (result == STR_INSERT) { memmove(cfn+1, cfn, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); memset(cfn, 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); + storageDirty(eeFlags); } else if (result == STR_DELETE) { memmove(cfn, cfn+1, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); memset(&g_model.customFn[NUM_CFN-1], 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); + storageDirty(eeFlags); } } @@ -257,7 +257,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc strcpy(directory, SOUNDS_PATH); strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); } - if (listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { + if (sdListFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { menuHandler = onCustomFunctionsFileSelectionMenu; } else { diff --git a/radio/src/gui/Horus/menu_model_gvars.cpp b/radio/src/gui/Horus/menu_model_gvars.cpp index 651e26555..f8f6ce39f 100644 --- a/radio/src/gui/Horus/menu_model_gvars.cpp +++ b/radio/src/gui/Horus/menu_model_gvars.cpp @@ -41,17 +41,17 @@ void onGVARSMenu(const char *result) if (result == STR_ENABLE_POPUP) { g_model.gvars[sub].popup = true; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_DISABLE_POPUP) { g_model.gvars[sub].popup = false; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_CLEAR) { for (int i=0; i GVAR_MAX ? 0 : GVAR_MAX+1); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (s_editMode>0) { v = checkIncDec(event, v, vmin, vmax, EE_MODEL); diff --git a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp index cf4e9ebfe..6b9dd58f4 100644 --- a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp @@ -63,7 +63,7 @@ FlightModesType editFlightModes(coord_t x, coord_t y, evt_t event, FlightModesTy if (s_editMode && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; value ^= (1<weight = 100; } resumeMixerCalculations(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } void copyExpoMix(uint8_t expo, uint8_t idx) @@ -225,7 +225,7 @@ void copyExpoMix(uint8_t expo, uint8_t idx) memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); } resumeMixerCalculations(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } void memswap(void *a, void *b, uint8_t size) @@ -369,8 +369,8 @@ void menuModelExpoOne(evt_t event) x = divRound(x*CURVE_SIDE_WIDTH, RESX); y = getYCoord(expoFn, x); - lcd_vlineStip(CURVE_CENTER_X+x, y-3, 7, SOLID, CURVE_CURSOR_COLOR); - lcd_hlineStip(CURVE_CENTER_X+x-3, y, 7, SOLID, CURVE_CURSOR_COLOR); + lcdDrawVerticalLine(CURVE_CENTER_X+x, y-3, 7, SOLID, CURVE_CURSOR_COLOR); + lcdDrawHorizontalLine(CURVE_CENTER_X+x-3, y, 7, SOLID, CURVE_CURSOR_COLOR); } for (unsigned int k=0; k 101) barMax = 101; - lcd_hlineStip(x-2, y, GAUGE_WIDTH+2, DOTTED); - lcd_hlineStip(x-2, y+GAUGE_HEIGHT, GAUGE_WIDTH+2, DOTTED); - lcd_vline(x-2, y+1, GAUGE_HEIGHT-1); - lcd_vline(x+GAUGE_WIDTH-1, y+1, GAUGE_HEIGHT-1); + lcdDrawHorizontalLine(x-2, y, GAUGE_WIDTH+2, DOTTED); + lcdDrawHorizontalLine(x-2, y+GAUGE_HEIGHT, GAUGE_WIDTH+2, DOTTED); + // lcdDrawSolidVerticalLine(x-2, y+1, GAUGE_HEIGHT-1); + // lcdDrawSolidVerticalLine(x+GAUGE_WIDTH-1, y+1, GAUGE_HEIGHT-1); if (barMin <= barMax) { - // TODO - // int8_t right = (barMax * GAUGE_WIDTH) / 200; - // int8_t left = ((barMin * GAUGE_WIDTH) / 200)-1; - // lcdDrawFilledRect(x+GAUGE_WIDTH/2+left, y+2, right-left, GAUGE_HEIGHT-3); + int8_t right = (barMax * GAUGE_WIDTH) / 200; + int8_t left = ((barMin * GAUGE_WIDTH) / 200)-1; + // lcdDrawSolidFilledRect(x+GAUGE_WIDTH/2+left, y+2, right-left, GAUGE_HEIGHT-3); } - lcd_vline(x+GAUGE_WIDTH/2-1, y, GAUGE_HEIGHT+1); + // lcdDrawSolidVerticalLine(x+GAUGE_WIDTH/2-1, y, GAUGE_HEIGHT+1); #if 0 // TODO if (barMin == -101) { for (uint8_t i=0; i<3; ++i) { @@ -526,7 +525,7 @@ void menuModelMixOne(evt_t event) putsChn(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, md2->destCh+1, HEADER_COLOR); // The separation line between 2 columns - lcd_vlineStip(MENU_COLUMN2_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, SOLID, HEADER_COLOR); + lcdDrawVerticalLine(MENU_COLUMN2_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, SOLID, HEADER_COLOR); int8_t sub = m_posVert; int8_t editMode = s_editMode; @@ -648,7 +647,7 @@ static uint8_t s_copySrcCh; void lineSurround(bool expo, coord_t y, LcdFlags flags=CURVE_AXIS_COLOR) { - lcd_rect(expo ? EXPO_LINE_SELECT_POS : MIX_LINE_SELECT_POS, y-INVERT_VERT_MARGIN, expo ? EXPO_LINE_SELECT_WIDTH : MIX_LINE_SELECT_WIDTH, INVERT_LINE_HEIGHT, (s_copyMode == COPY_MODE ? SOLID : DOTTED), flags); + lcdDrawRect(expo ? EXPO_LINE_SELECT_POS : MIX_LINE_SELECT_POS, y-INVERT_VERT_MARGIN, expo ? EXPO_LINE_SELECT_WIDTH : MIX_LINE_SELECT_WIDTH, INVERT_LINE_HEIGHT, (s_copyMode == COPY_MODE ? SOLID : DOTTED), flags); } void onExpoMixMenu(const char *result) @@ -764,7 +763,7 @@ void menuModelExpoMix(uint8_t expo, evt_t event) swapExpoMix(expo, s_currIdx, s_copyTgtOfs > 0); s_copyTgtOfs += (s_copyTgtOfs < 0 ? +1 : -1); } while (s_copyTgtOfs != 0); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } m_posVert = s_copySrcRow; s_copyTgtOfs = 0; @@ -853,7 +852,7 @@ void menuModelExpoMix(uint8_t expo, evt_t event) else { // only swap the mix with its neighbor if (!swapExpoMix(expo, s_currIdx, IS_ROTARY_UP(event) || key==KEY_MOVE_UP)) break; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } s_copyTgtOfs = next_ofs; diff --git a/radio/src/gui/Horus/menu_model_limits.cpp b/radio/src/gui/Horus/menu_model_limits.cpp index 0afe8631f..77df8b0fc 100644 --- a/radio/src/gui/Horus/menu_model_limits.cpp +++ b/radio/src/gui/Horus/menu_model_limits.cpp @@ -144,7 +144,7 @@ void menuModelLimits(evt_t event) s_warning_result = 0; LimitData *ld = limitAddress(sub); ld->revert = !ld->revert; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } for (int i=0; iv2 = calcRESXto100(x); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } break; case LS_FIELD_V3: diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp index fa3968546..62cfb8f1e 100644 --- a/radio/src/gui/Horus/menu_model_select.cpp +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -56,24 +56,24 @@ void onModelSelectMenu(const char *result) s_copySrcRow = -1; } else if (result == STR_BACKUP_MODEL) { - eeCheck(true); // force writing of current model data before this is changed + storageCheck(true); // force writing of current model data before this is changed POPUP_WARNING(eeBackupModel(sub)); } else if (result == STR_RESTORE_MODEL || result == STR_UPDATE_LIST) { - if (!listSdFiles(MODELS_PATH, MODELS_EXT, MENU_LINE_LENGTH-1, NULL)) { + if (!sdListFiles(MODELS_PATH, MODELS_EXT, MENU_LINE_LENGTH-1, NULL)) { POPUP_WARNING(STR_NO_MODELS_ON_SD); s_menu_flags = 0; } } else if (result == STR_DELETE_MODEL) { POPUP_CONFIRMATION(STR_DELETEMODEL); - SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); + // SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); } else { // The user choosed a file on SD to restore POPUP_WARNING(eeRestoreModel(sub, (char *)result)); - if (!s_warning && g_eeGeneral.currModel == sub) - eeLoadModel(sub); + // if (!s_warning && g_eeGeneral.currModel == sub) + // eeLoadModel(sub); } } @@ -115,12 +115,12 @@ void menuModelSelect(evt_t event) if (sub >= NUM_BODY_LINES) s_pgOfs = sub-(NUM_BODY_LINES-1); s_copyMode = 0; s_editMode = EDIT_MODE_INIT; - eeCheck(true); + storageCheck(true); break; case EVT_KEY_LONG(KEY_EXIT): if (s_copyMode && s_copyTgtOfs == 0 && g_eeGeneral.currModel != sub && eeModelExists(sub)) { POPUP_CONFIRMATION(STR_DELETEMODEL); - SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); + // SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); killEvents(_event_); break; } @@ -149,7 +149,7 @@ void menuModelSelect(evt_t event) } } else if (s_copyMode && (s_copyTgtOfs || s_copySrcRow>=0)) { - eeCheck(true); // force writing of current model data before this is changed + storageCheck(true); // force writing of current model data before this is changed uint8_t cur = (MAX_MODELS + sub + s_copyTgtOfs) % MAX_MODELS; if (s_copyMode == COPY_MODE) { if (!eeCopyModel(cur, s_copySrcRow)) { @@ -170,7 +170,7 @@ void menuModelSelect(evt_t event) if (s_copySrcRow != g_eeGeneral.currModel) { g_eeGeneral.currModel = s_copySrcRow; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); } s_copyMode = 0; @@ -248,7 +248,7 @@ void menuModelSelect(evt_t event) LcdFlags flags = 0; if (sub==k && s_copyMode == 0) { flags |= TEXT_INVERTED_COLOR; - lcdDrawFilledRect(MENUS_MARGIN_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(MENUS_MARGIN_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } char str[20]; @@ -259,7 +259,7 @@ void menuModelSelect(evt_t event) if (k == sub) { if (s_copyMode == COPY_MODE) { k = s_copySrcRow; - // lcd_rect(MENUS_MARGIN_LEFT-5, y-3, 170, 16, SOLID, YELLOW); + // lcdDrawRect(MENUS_MARGIN_LEFT-5, y-3, 170, 16, SOLID, YELLOW); } else { k = sub + s_copyTgtOfs; @@ -274,7 +274,7 @@ void menuModelSelect(evt_t event) k %= MAX_MODELS; if (eeModelExists(k)) { - strcat_modelname(str, k); + // TODO strcat_modelname(str, k); lcd_putsAtt(POS_MODEL_NAME, y, str, flags); if (k==g_eeGeneral.currModel && (s_copyMode!=COPY_MODE || s_copySrcRow<0 || i+s_pgOfs!=(vertpos_t)sub)) { lcd_puts(1, y, "*"); @@ -285,7 +285,7 @@ void menuModelSelect(evt_t event) } if (s_copyMode && (vertpos_t)sub==i+s_pgOfs) { - lcd_rect(MENUS_MARGIN_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, s_copyMode == COPY_MODE ? SOLID : DOTTED, WARNING_COLOR); + lcdDrawRect(MENUS_MARGIN_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, s_copyMode == COPY_MODE ? SOLID : DOTTED, WARNING_COLOR); } } diff --git a/radio/src/gui/Horus/menu_model_setup.cpp b/radio/src/gui/Horus/menu_model_setup.cpp index f04ca9935..cb3ec8269 100644 --- a/radio/src/gui/Horus/menu_model_setup.cpp +++ b/radio/src/gui/Horus/menu_model_setup.cpp @@ -106,7 +106,7 @@ void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, evt_t event) { TimerData * timer = &g_model.timers[timerIdx]; if (attr && m_posHorz < 0) { - lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 80+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 80+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } putsStrIdx(MENUS_MARGIN_LEFT, y, STR_TIMER, timerIdx+1); putsTimerMode(MODEL_SETUP_2ND_COLUMN, y, timer->mode, (m_posHorz<=0 ? attr : 0)); @@ -126,7 +126,7 @@ void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, evt_t event) int8_t switchVal = checkIncDecMovedSwitch(val); if (val != switchVal) { timer->mode = switchVal + (TMRMODE_COUNT-1); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } } #endif @@ -204,7 +204,6 @@ void menuModelSetup(evt_t event) switch(k) { case ITEM_MODEL_NAME: editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_MODELNAME, g_model.header.name, sizeof(g_model.header.name), event, attr); - memcpy(modelHeaders[g_eeGeneral.currModel].name, g_model.header.name, sizeof(g_model.header.name)); break; case ITEM_MODEL_BITMAP: @@ -291,7 +290,7 @@ void menuModelSetup(evt_t event) for (uint8_t i=0; i> (3*i)) & 0x07); LcdFlags color = (state > 0 ? TEXT_COLOR : TEXT_DISABLE_COLOR); @@ -414,12 +413,12 @@ void menuModelSetup(evt_t event) if (g_model.potsWarnMode == POTS_WARN_MANUAL) { SAVE_POT_POSITION(m_posHorz-1); AUDIO_WARNING1(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } break; case EVT_KEY_BREAK(KEY_ENTER): g_model.potsWarnEnabled ^= (1 << (m_posHorz-1)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); break; } } @@ -437,7 +436,7 @@ void menuModelSetup(evt_t event) if (attr && (m_posHorz == 0)) { CHECK_INCDEC_MODELVAR(event, g_model.potsWarnMode, POTS_WARN_OFF, POTS_WARN_AUTO); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } break; } @@ -445,11 +444,10 @@ void menuModelSetup(evt_t event) case ITEM_MODEL_BEEP_CENTER: lcd_putsLeft(y, STR_BEEPCTR); if (attr && m_posHorz < 0) { - lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, (NUM_STICKS+NUM_POTS)*11+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, (NUM_STICKS+NUM_POTS)*13+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } for (int i=0; i0) { - CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], IS_MODULE_DSM2(moduleIdx) ? 20 : 63); - if (checkIncDec_Ret) { - modelHeaders[g_eeGeneral.currModel].modelId[moduleIdx] = g_model.header.modelId[moduleIdx]; - } - } - if (s_editMode==0 && event==EVT_KEY_BREAK(KEY_ENTER)) { - checkModelIdUnique(g_eeGeneral.currModel, moduleIdx); - } + if (attr && l_posHorz==0 && s_editMode>0) { + CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], IS_MODULE_DSM2(moduleIdx) ? 20 : 63); } lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); @@ -635,7 +625,7 @@ void menuModelSetup(evt_t event) } } else { - lcdDrawFilledRect(MODEL_SETUP_2ND_COLUMN, y, LCD_W-MODEL_SETUP_2ND_COLUMN-MENUS_SCROLLBAR_WIDTH, 8, TEXT_COLOR); + lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN, y, LCD_W-MODEL_SETUP_2ND_COLUMN-MENUS_SCROLLBAR_WIDTH, 8, TEXT_COLOR); } } } @@ -659,7 +649,7 @@ void menuModelFailsafe(evt_t event) if (event == EVT_KEY_LONG(KEY_ENTER) && s_editMode) { s_noHi = NO_HI_LEN; g_model.moduleData[g_moduleIdx].failsafeChannels[m_posVert] = channelOutputs[m_posVert]; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); AUDIO_WARNING1(); SEND_FAILSAFE_NOW(g_moduleIdx); } @@ -669,7 +659,7 @@ void menuModelFailsafe(evt_t event) #define COL_W (LCD_W/2) const uint8_t SLIDER_W = 64; // Column separator - lcd_vline(LCD_W/2, FH, LCD_H-FH); + // TODO lcd_vline(LCD_W/2, FH, LCD_H-FH); if (m_posVert >= 16) { ch = 16; @@ -724,15 +714,11 @@ void menuModelFailsafe(evt_t event) #endif // Gauge - lcd_rect(x+COL_W-3-wbar-ofs, y, wbar+1, 6); + lcdDrawRect(x+COL_W-3-wbar-ofs, y, wbar+1, 6); uint16_t lim = g_model.extendedLimits ? 640*2 : 512*2; uint8_t len = limit((uint8_t)1, uint8_t((abs(val) * wbar/2 + lim/2) / lim), uint8_t(wbar/2)); coord_t x0 = (val>0) ? x+COL_W-ofs-3-wbar/2 : x+COL_W-ofs-2-wbar/2-len; - lcd_hline(x0, y+1, len); - lcd_hline(x0, y+2, len); - lcd_hline(x0, y+3, len); - lcd_hline(x0, y+4, len); - + lcdDrawSolidFilledRect(x0, y+1, len, 4, LINE_COLOR); ch++; } } diff --git a/radio/src/gui/Horus/menu_model_telemetry.cpp b/radio/src/gui/Horus/menu_model_telemetry.cpp index 0ea07919f..e1e2c4504 100644 --- a/radio/src/gui/Horus/menu_model_telemetry.cpp +++ b/radio/src/gui/Horus/menu_model_telemetry.cpp @@ -428,7 +428,7 @@ void onTelemetryScriptFileSelectionMenu(const char *result) uint8_t screenIndex = TELEMETRY_CURRENT_EDIT_SCREEN(sub); if (result == STR_UPDATE_LIST) { - if (!listSdFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), NULL)) { + if (!sdListFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), NULL)) { POPUP_WARNING(STR_NO_SCRIPTS_ON_SD); s_menu_flags = 0; } @@ -436,7 +436,7 @@ void onTelemetryScriptFileSelectionMenu(const char *result) else { // The user choosed a file in the list memcpy(g_model.frsky.screens[screenIndex].script.file, result, sizeof(g_model.frsky.screens[screenIndex].script.file)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); LUA_LOAD_MODEL_SCRIPTS(); } } @@ -610,7 +610,7 @@ void menuModelTelemetry(evt_t event) if (m_posHorz==1 && attr && event==EVT_KEY_BREAK(KEY_ENTER) && READ_ONLY_UNLOCKED()) { s_editMode = 0; - if (listSdFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), g_model.frsky.screens[screenIndex].script.file)) { + if (sdListFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), g_model.frsky.screens[screenIndex].script.file)) { menuHandler = onTelemetryScriptFileSelectionMenu; } else { diff --git a/radio/src/gui/Horus/menus.cpp b/radio/src/gui/Horus/menus.cpp index ecffd63b1..1ba534a53 100644 --- a/radio/src/gui/Horus/menus.cpp +++ b/radio/src/gui/Horus/menus.cpp @@ -61,7 +61,7 @@ void pushMenu(MenuFuncP newMenu) if (g_menuStackPtr == 0) { if (newMenu == menuGeneralSetup) g_menuPos[0] = 1; - if (newMenu == menuModelSelect) + if (newMenu == menuModelSetup) g_menuPos[0] = 0; } else { @@ -80,7 +80,7 @@ void menuModelNotes(evt_t event) { if (event == EVT_ENTRY) { strcpy(s_text_file, MODELS_PATH "/"); - char *buf = strcat_modelname(&s_text_file[sizeof(MODELS_PATH)], g_eeGeneral.currModel); + char *buf = strcat_currentmodelname(&s_text_file[sizeof(MODELS_PATH)]); strcpy(buf, TEXT_EXT); } diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h index 92f9ce2f0..e7941d80e 100644 --- a/radio/src/gui/Horus/menus.h +++ b/radio/src/gui/Horus/menus.h @@ -105,6 +105,7 @@ void menuGeneralCalib(evt_t event); void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext); void menuModelSelect(evt_t event); +void menuModelSetup(evt_t event); void menuModelCustomFunctions(evt_t event); void menuStatisticsView(evt_t event); void menuStatisticsDebug(evt_t event); @@ -192,26 +193,12 @@ int8_t checkIncDecMovedSwitch(int8_t val); #define CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, var, max, check) \ var = checkIncDec(event, var, 0, max, EE_MODEL, check) -bool isThrottleSourceAvailable(int source); -bool isLogicalSwitchFunctionAvailable(int function); -bool isAssignableFunctionAvailable(int function); -bool isSourceAvailableInResetSpecialFunction(int index); -bool isSourceAvailableInGlobalResetSpecialFunction(int index); -bool isSwitchAvailableInLogicalSwitches(int swtch); -bool isSwitchAvailableInCustomFunctions(int swtch); -bool isSwitchAvailableInMixes(int swtch); -bool isSwitchAvailableInTimers(int swtch); -bool isModuleAvailable(int module); #define AUTOSWITCH_ENTER_LONG() (attr && event==EVT_KEY_LONG(KEY_ENTER)) #define CHECK_INCDEC_SWITCH(event, var, min, max, flags, available) \ var = checkIncDec(event, var, min, max, (flags)|INCDEC_SWITCH, available) #define CHECK_INCDEC_MODELSWITCH(event, var, min, max, available) \ CHECK_INCDEC_SWITCH(event, var, min, max, EE_MODEL, available) -bool isInputAvailable(int input); -bool isSourceAvailable(int source); -bool isSourceAvailableInCustomSwitches(int source); -bool isInputSourceAvailable(int source); #define CHECK_INCDEC_MODELSOURCE(event, var, min, max) \ var = checkIncDec(event, var,min,max,EE_MODEL|INCDEC_SOURCE|NO_INCDEC_MARKS, isSourceAvailable) diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp index bb58ac257..916c5eaf9 100644 --- a/radio/src/gui/Horus/navigation.cpp +++ b/radio/src/gui/Horus/navigation.cpp @@ -200,7 +200,7 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags AUDIO_KEYPAD_DOWN(); } } - eeDirty(i_flags & (EE_GENERAL|EE_MODEL)); + storageDirty(i_flags & (EE_GENERAL|EE_MODEL)); checkIncDec_Ret = (newval > val ? 1 : -1); } else { diff --git a/radio/src/gui/Horus/popups.cpp b/radio/src/gui/Horus/popups.cpp index dd8402cd9..daa9fc83d 100644 --- a/radio/src/gui/Horus/popups.cpp +++ b/radio/src/gui/Horus/popups.cpp @@ -48,28 +48,28 @@ int16_t s_warning_input_max; void displayAlertBox() { - drawBlackOverlay(); - lcdDrawFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); - lcd_rect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); - lcd_rect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); + lcdDrawBlackOverlay(); + lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); + lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_ALERT); } void displayWarningBox() { - drawBlackOverlay(); - lcdDrawFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); - lcd_rect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); - lcd_rect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); + lcdDrawBlackOverlay(); + lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); + lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_WARNING); } void displayMessageBox() { - drawBlackOverlay(); - lcdDrawFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); - lcd_rect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, WARNING_COLOR); - lcd_rect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, WARNING_COLOR); + lcdDrawBlackOverlay(); + lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, WARNING_COLOR); + lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, WARNING_COLOR); // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_MESSAGE); } @@ -152,17 +152,16 @@ void (*menuHandler)(const char *result); const char * displayMenu(evt_t event) { const char * result = NULL; - uint8_t display_count = min(s_menu_count, (uint16_t)MENU_MAX_LINES); - int y = (LCD_H - (display_count*(FH+1))) / 2; - // drawBlackOverlay(); - lcdDrawFilledRect(MENU_X, y, MENU_W, display_count * (FH+1), TEXT_BGCOLOR); - lcdDrawRect(MENU_X, y, MENU_W, display_count * (FH+1) + 1, ALARM_COLOR); + + lcdDrawBlackOverlay(); + lcdDrawSolidFilledRect(MENU_X, y, MENU_W, display_count * (FH+1), TEXT_BGCOLOR); + lcdDrawSolidRect(MENU_X, y, MENU_W, display_count * (FH+1) + 1, ALARM_COLOR); for (uint8_t i=0; i skipCount) { // The black background - lcdDrawFilledRect(x, y, CHANNEL_WIDTH, CHANNEL_HEIGHT, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(x, y, CHANNEL_WIDTH, CHANNEL_HEIGHT, TEXT_BGCOLOR); // The label unsigned int lenLabel = ZLEN(g_model.limitData[ch].name); @@ -85,11 +85,11 @@ void menuChannelsView(evt_t event) int32_t val = channelOutputs[ch]; // The bar - lcdDrawFilledRect(x+CHANNEL_PADDING_HORZ, y+15, BAR_WIDTH, 6, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x+CHANNEL_PADDING_HORZ, y+15, BAR_WIDTH, 6, TEXT_INVERTED_BGCOLOR); unsigned int lim = g_model.extendedLimits ? 640*2 : 512*2; unsigned int len = limit(1, (abs(val) * BAR_WIDTH/2 + lim/2) / lim, BAR_WIDTH/2); unsigned int x0 = (val>0) ? x+CHANNEL_PADDING_HORZ-1+BAR_WIDTH/2 : x+CHANNEL_PADDING_HORZ+BAR_WIDTH/2+1-len; - lcdDrawFilledRect(x0, y+16, len, 4, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(x0, y+16, len, 4, TEXT_BGCOLOR); y += CHANNEL_HEIGHT + CHANNEL_MARGIN; if (y >= 4*(CHANNEL_HEIGHT + CHANNEL_MARGIN)) { diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index 3b147eefe..8ba758b20 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -59,7 +59,7 @@ void drawPotsBars() if (IS_POT_AVAILABLE(i)) { len = ((calibratedStick[i]+RESX)*BAR_HEIGHT/(RESX*2))+1l; // calculate once per loop // TODO 220 constant - lcdDrawFilledRect(x, 220-FH-len, 5, len, TEXT_COLOR); + lcdDrawSolidFilledRect(x, 220-FH-len, 5, len, TEXT_COLOR); } } } @@ -79,7 +79,7 @@ void doMainScreenGraphics() void drawTrimSquare(coord_t x, coord_t y) { - lcdDrawFilledRect(x-2, y, 15, 15, TITLE_BGCOLOR); + lcdDrawSolidFilledRect(x-2, y, 15, 15, TITLE_BGCOLOR); lcdDrawBitmapPattern(x-2, y, LBM_TRIM_SHADOW, TEXT_COLOR); } @@ -87,13 +87,13 @@ void drawHorizontalTrimPosition(coord_t x, coord_t y, int16_t dir) { drawTrimSquare(x, y); if (dir >= 0) { - lcdDrawVerticalLine(x+8, y+3, 9, TEXT_INVERTED_COLOR); + lcdDrawSolidVerticalLine(x+8, y+3, 9, TEXT_INVERTED_COLOR); } if (dir <= 0) { - lcdDrawVerticalLine(x+2, y+3, 9, TEXT_INVERTED_COLOR); + lcdDrawSolidVerticalLine(x+2, y+3, 9, TEXT_INVERTED_COLOR); } // if (exttrim) { - // lcdDrawVerticalLine(xm, ym, 9, TEXT_INVERTED_COLOR); + // lcdDrawSolidVerticalLine(xm, ym, 9, TEXT_INVERTED_COLOR); // } } @@ -101,13 +101,13 @@ void drawVerticalTrimPosition(coord_t x, coord_t y, int16_t dir) { drawTrimSquare(x, y); if (dir >= 0) { - lcdDrawHorizontalLine(x+1, y+4, 9, TEXT_INVERTED_COLOR); + lcdDrawSolidHorizontalLine(x+1, y+4, 9, TEXT_INVERTED_COLOR); } if (dir <= 0) { - lcdDrawHorizontalLine(x+1, y+10, 9, TEXT_INVERTED_COLOR); + lcdDrawSolidHorizontalLine(x+1, y+10, 9, TEXT_INVERTED_COLOR); } // if (exttrim) { - // lcdDrawHorizontalLine(xm-1, ym, 3, TEXT_INVERTED_COLOR); + // lcdDrawSolidHorizontalLine(xm-1, ym, 3, TEXT_INVERTED_COLOR); // } } @@ -115,20 +115,20 @@ void drawHorizontalStick(coord_t x, int val) { for (int i=0; i<=160; i+=4) { if (i==0 || i==80 || i==160) - lcdDrawVerticalLine(x+i, 250, 13, TEXT_COLOR); + lcdDrawSolidVerticalLine(x+i, 250, 13, TEXT_COLOR); else - lcdDrawVerticalLine(x+i, 252, 9, TEXT_COLOR); + lcdDrawSolidVerticalLine(x+i, 252, 9, TEXT_COLOR); } - drawHorizontalTrimPosition(x+TRIM_LEN+val*TRIM_LEN/RESX-4, TRIM_H_Y+16, val); + drawHorizontalTrimPosition(x+TRIM_LEN+val*TRIM_LEN/RESX-5, TRIM_H_Y+16, val); } void drawVerticalStick(coord_t x, int val) { for (int i=0; i<=160; i+=4) { if (i==0 || i==80 || i==160) - lcdDrawHorizontalLine(x, 56+i, 13, TEXT_COLOR); + lcdDrawSolidHorizontalLine(x, 56+i, 13, TEXT_COLOR); else - lcdDrawHorizontalLine(x+2, 56+i, 9, TEXT_COLOR); + lcdDrawSolidHorizontalLine(x+2, 56+i, 9, TEXT_COLOR); } drawVerticalTrimPosition(x, TRIM_V_Y+val*TRIM_LEN/RESX-6, val); } @@ -208,7 +208,7 @@ void displayTimers() if (timerState.val < 0) { color = ALARM_COLOR; } - // TODO lcdDrawFilledRect(TIMERS_MARGIN, y, TIMERS_W, TIMERS_H, SOLID, bgColor); + // TODO lcdDrawSolidFilledRect(TIMERS_MARGIN, y, TIMERS_W, TIMERS_H, SOLID, bgColor); putsTimer(TIMERS_MARGIN+TIMERS_PADDING, y+12, abs(timerState.val), color|DBLSIZE|LEFT); if (len > 0) lcd_putsnAtt(TIMERS_MARGIN+TIMERS_PADDING, y+2, timerData.name, LEN_TIMER_NAME, color|ZCHAR); @@ -241,7 +241,7 @@ void displayMainTelemetryFields() att |= PREC1; } att |= (item.isOld() ? ALARM_COLOR : TEXT_COLOR); - lcdDrawFilledRect(ALTITUDE_X, VOLTS_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(ALTITUDE_X, VOLTS_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); lcd_putsAtt(ALTITUDE_X+PADDING, VOLTS_Y+2, "Voltage", att); putsValueWithUnit(ALTITUDE_X+PADDING, VOLTS_Y+12, value, UNIT_VOLTS, DBLSIZE|LEFT|att); } @@ -254,7 +254,7 @@ void displayMainTelemetryFields() TelemetrySensor & sensor = g_model.telemetrySensors[g_model.frsky.altitudeSource-1]; if (sensor.prec) value /= sensor.prec == 2 ? 100 : 10; LcdFlags att = (item.isOld() ? ALARM_COLOR : TEXT_COLOR); - lcdDrawFilledRect(ALTITUDE_X, ALTITUDE_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(ALTITUDE_X, ALTITUDE_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); lcd_putsAtt(ALTITUDE_X+PADDING, ALTITUDE_Y+2, "Alt", att); putsValueWithUnit(ALTITUDE_X+PADDING, ALTITUDE_Y+12, value, UNIT_METERS, DBLSIZE|LEFT|att); } @@ -276,10 +276,10 @@ void displayMainViewIndex() for (int i=VIEW_COUNT; i>=0; --i) { if (isViewAvailable(i)) { if (g_eeGeneral.view == i) { - lcdDrawFilledRect(x, MENU_FOOTER_TOP+17, 9, 9, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x, MENU_FOOTER_TOP+17, 9, 9, TEXT_INVERTED_BGCOLOR); } else { - lcdDrawRect(x+1, MENU_FOOTER_TOP+18, 7, 7, TEXT_COLOR); + lcdDrawSolidRect(x+1, MENU_FOOTER_TOP+18, 7, 7, TEXT_COLOR); } x -= 11; } @@ -343,11 +343,18 @@ const uint16_t LBM_CORSAIR[] = { #include "../../bitmaps/Horus/corsair.lbm" }; +const uint8_t LBM_TIMER_BACKGROUND[] = { +#include "../../bitmaps/Horus/mask_timer_bg.lbm" +}; + +const uint8_t LBM_RSCALE[] = { +#include "../../bitmaps/Horus/mask_rscale.lbm" +}; void menuMainView(evt_t event) { // clear the screen - lcdDrawFilledRect(0, 0, LCD_W, LCD_H, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(0, 0, LCD_W, LCD_H, TEXT_BGCOLOR); switch (event) { case EVT_ENTRY: @@ -377,13 +384,13 @@ void menuMainView(evt_t event) break; case EVT_KEY_BREAK(KEY_DOWN): - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); g_eeGeneral.view = circularIncDec(g_eeGeneral.view, +1, 0, VIEW_COUNT-1, isViewAvailable); break; case EVT_KEY_BREAK(KEY_UP): killEvents(event); - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); g_eeGeneral.view = circularIncDec(g_eeGeneral.view, -1, 0, VIEW_COUNT-1, isViewAvailable); break; @@ -401,14 +408,19 @@ void menuMainView(evt_t event) // lcdDrawBitmap(0, 0, LBM_MAINVIEW_FLAT); // Header - lcdDrawFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); + lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); lcdDrawTopmenuDatetime(); // Flight Mode Name int mode = mixerCurrentFlightMode; - lcd_putsnAtt(212, 237, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|SMLSIZE); + for (int i=0; i=MAXTRACE) traceRd = 0; if (traceRd==s_traceWr) break; @@ -103,7 +103,7 @@ void menuStatisticsDebug(evt_t event) case EVT_KEY_LONG(KEY_ENTER): g_eeGeneral.mAhUsed = 0; g_eeGeneral.globalTimer = 0; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); sessionTimer = 0; killEvents(event); AUDIO_KEYPAD_UP(); @@ -181,7 +181,7 @@ void menuTraceBuffer(evt_t event) uint8_t k = 0; int8_t sub = m_posVert; - lcd_putc(0, FH, '#'); + lcdDrawChar(0, FH, '#', TEXT_COLOR); lcd_puts(4*10, FH, "Time"); lcd_puts(14*10, FH, "Event"); lcd_puts(20*10, FH, "Data"); diff --git a/radio/src/gui/Horus/view_telemetry.cpp b/radio/src/gui/Horus/view_telemetry.cpp index 2e76051b0..d4ed7eab7 100644 --- a/radio/src/gui/Horus/view_telemetry.cpp +++ b/radio/src/gui/Horus/view_telemetry.cpp @@ -81,7 +81,7 @@ void displayGaugesTelemetryScreen(FrSkyScreenData & screen) int y = GAUGE_MARGIN + i*(GAUGE_HEIGHT+GAUGE_MARGIN); // The black background - lcdDrawFilledRect(GAUGE_MARGIN, y, GAUGE_WIDTH, GAUGE_HEIGHT, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(GAUGE_MARGIN, y, GAUGE_WIDTH, GAUGE_HEIGHT, TEXT_BGCOLOR); getvalue_t value = getValue(source); LcdFlags color = TEXT_COLOR; @@ -98,7 +98,7 @@ void displayGaugesTelemetryScreen(FrSkyScreenData & screen) if (value < 0) { putsMixerSource(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+GAUGE_PADDING_VERT, source, ALARM_COLOR); putsTimer(GAUGE_MARGIN+GAUGE_WIDTH-VAL_WIDTH, y+GAUGE_PADDING_VERT+3, -value, LEFT|DBLSIZE|ALARM_COLOR); - // TODO lcdDrawFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+15, BAR_WIDTH, 6, ALARM_BGCOLOR); + // TODO lcdDrawSolidFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+15, BAR_WIDTH, 6, ALARM_BGCOLOR); continue; } } @@ -110,19 +110,19 @@ void displayGaugesTelemetryScreen(FrSkyScreenData & screen) putsChannel(GAUGE_MARGIN+GAUGE_WIDTH-VAL_WIDTH, y+GAUGE_PADDING_VERT+3, source, LEFT|DBLSIZE|NO_UNIT|color); // The bar - // TODO lcdDrawFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+15, BAR_WIDTH, 6, SOLID, color); + // TODO lcdDrawSolidFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ, y+15, BAR_WIDTH, 6, SOLID, color); //uint8_t thresholdX = 0; int width = barCoord(value, barMin, barMax, BAR_WIDTH-2); //uint8_t barShade = SOLID; - lcdDrawFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ+1, y+16, width, 4, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(GAUGE_MARGIN+GAUGE_PADDING_HORZ+1, y+16, width, 4, TEXT_BGCOLOR); /* for (uint8_t j=24; j<99; j+=25) { if (j>thresholdX || j>width) { lcd_vline(j*BAR_WIDTH/100+BAR_LEFT+1, y+1, barHeight); } } if (thresholdX) { - lcd_vlineStip(BAR_LEFT+1+thresholdX, y-2, barHeight+3, DOTTED); - lcd_hline(BAR_LEFT+thresholdX, y-2, 3); + lcdDrawVerticalLine(BAR_LEFT+1+thresholdX, y-2, barHeight+3, DOTTED); + lcdDrawSolidHorizontalLine(BAR_LEFT+thresholdX, y-2, 3); } */ } @@ -144,7 +144,7 @@ void displayNumbersTelemetryScreen(FrSkyScreenData & screen) coord_t pos[] = { NUMBERS_MARGIN, (LCD_W-NUMBERS_W)/2, LCD_W-NUMBERS_MARGIN-NUMBERS_W }; int x = pos[j]; int y = NUMBERS_MARGIN + i*(NUMBERS_H+NUMBERS_MARGIN); - lcdDrawFilledRect(x, y, NUMBERS_W, NUMBERS_H, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(x, y, NUMBERS_W, NUMBERS_H, TEXT_BGCOLOR); if (field >= MIXSRC_FIRST_TELEM) { TelemetryItem & telemetryItem = telemetryItems[(field-MIXSRC_FIRST_TELEM)/3]; // TODO macro to convert a source to a telemetry index if (!telemetryItem.isAvailable()) { diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 79c40423f..7928d145c 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -58,7 +58,7 @@ const char * STR_MONTHS[] = { "Jan", "Fev", "Mar", "Apr", "May", "Jun", "Jul", " void lcdDrawTopmenuDatetime() { - lcdDrawVerticalLine(DATETIME_SEPARATOR_X, 7, 31, TEXT_INVERTED_COLOR); + lcdDrawSolidVerticalLine(DATETIME_SEPARATOR_X, 7, 31, TEXT_INVERTED_COLOR); struct gtm t; gettime(&t); @@ -76,8 +76,8 @@ void drawStick(coord_t centrex, int16_t xval, int16_t yval) #define MARKER_WIDTH 5 lcdDrawSquare(centrex-BOX_WIDTH/2, BOX_CENTERY-BOX_WIDTH/2, BOX_WIDTH, TEXT_COLOR); - lcd_vlineStip(centrex, BOX_CENTERY-1, 3, SOLID, TEXT_COLOR); - lcd_hlineStip(centrex-1, BOX_CENTERY, 3, SOLID, TEXT_COLOR); + lcdDrawVerticalLine(centrex, BOX_CENTERY-1, 3, SOLID, TEXT_COLOR); + lcdDrawHorizontalLine(centrex-1, BOX_CENTERY, 3, SOLID, TEXT_COLOR); lcdDrawSquare(centrex + (xval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, BOX_CENTERY - (yval/((2*RESX)/(BOX_WIDTH-MARKER_WIDTH))) - MARKER_WIDTH/2, MARKER_WIDTH, ROUND|TEXT_COLOR); #undef BOX_CENTERY @@ -87,37 +87,37 @@ void drawStick(coord_t centrex, int16_t xval, int16_t yval) void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) { if (attr) { - lcdDrawFilledRect(x-1, y+2, 13, 13, TEXT_INVERTED_BGCOLOR); - lcdDrawFilledRect(x+1, y+4, 9, 9, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(x-1, y+2, 13, 13, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x+1, y+4, 9, 9, TEXT_BGCOLOR); if (value) { - lcdDrawFilledRect(x+2, y+5, 7, 7, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x+2, y+5, 7, 7, TEXT_INVERTED_BGCOLOR); } } else { if (value) { - lcdDrawFilledRect(x+2, y+5, 7, 7, SCROLLBOX_COLOR); - lcdDrawRect(x, y+3, 11, 11, LINE_COLOR); + lcdDrawSolidFilledRect(x+2, y+5, 7, 7, SCROLLBOX_COLOR); + lcdDrawSolidRect(x, y+3, 11, 11, LINE_COLOR); } else { - lcdDrawRect(x, y+3, 11, 11, LINE_COLOR); + lcdDrawSolidRect(x, y+3, 11, 11, LINE_COLOR); } } } void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { - lcdDrawVerticalLine(x, y, h, LINE_COLOR); + lcdDrawSolidVerticalLine(x, y, h, LINE_COLOR); coord_t yofs = (h*offset + count/2) / count; coord_t yhgt = (h*visible + count/2) / count; if (yhgt + yofs > h) yhgt = h - yofs; - lcdDrawFilledRect(x-1, y + yofs, 3, yhgt, SCROLLBOX_COLOR); + lcdDrawSolidFilledRect(x-1, y + yofs, 3, yhgt, SCROLLBOX_COLOR); } void displayProgressBar(const char *label) { lcd_putsLeft(4*FH, label); - lcd_rect(3, 6*FH+4, 204, 7); + lcdDrawRect(3, 6*FH+4, 204, 7); lcdRefresh(); } @@ -125,9 +125,7 @@ void updateProgressBar(int num, int den) { if (num > 0 && den > 0) { int width = (200*num)/den; - lcd_hline(5, 6*FH+6, width); - lcd_hline(5, 6*FH+7, width); - lcd_hline(5, 6*FH+8, width); + lcdDrawSolidFilledRect(5, 6*FH+6, width, 3, LINE_COLOR); lcdRefresh(); } } @@ -137,11 +135,11 @@ void updateProgressBar(int num, int den) void drawMenuTemplate(const char * name, evt_t event) { // clear the screen - lcdDrawFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); - lcdDrawFilledRect(0, MENU_HEADER_HEIGHT, LCD_W, MENU_TITLE_TOP-MENU_HEADER_HEIGHT, TEXT_BGCOLOR); - lcdDrawFilledRect(0, MENU_TITLE_TOP, LCD_W, MENU_TITLE_HEIGHT, TITLE_BGCOLOR); - lcdDrawFilledRect(0, MENU_BODY_TOP, LCD_W, MENU_BODY_HEIGHT, TEXT_BGCOLOR); - lcdDrawFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); + lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); + lcdDrawSolidFilledRect(0, MENU_HEADER_HEIGHT, LCD_W, MENU_TITLE_TOP-MENU_HEADER_HEIGHT, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(0, MENU_TITLE_TOP, LCD_W, MENU_TITLE_HEIGHT, TITLE_BGCOLOR); + lcdDrawSolidFilledRect(0, MENU_BODY_TOP, LCD_W, MENU_BODY_HEIGHT, TEXT_BGCOLOR); + lcdDrawSolidFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); @@ -149,7 +147,7 @@ void drawMenuTemplate(const char * name, evt_t event) lcdDrawBitmapPattern(58+menuPageIndex*MENU_ICONS_SPACING-10, 0, LBM_CURRENT_BG, TITLE_BGCOLOR); } else { - lcdDrawFilledRect(58+menuPageIndex*MENU_ICONS_SPACING-9, 0, 32, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); + lcdDrawSolidFilledRect(58+menuPageIndex*MENU_ICONS_SPACING-9, 0, 32, MENU_HEADER_HEIGHT, TITLE_BGCOLOR); lcdDrawBitmapPattern(58+menuPageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_DOT, MENU_TITLE_COLOR); } @@ -199,12 +197,12 @@ int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, evt_t e void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) { const int width = 50; - lcd_hlineStip(x, y+5, width, SOLID, TEXT_COLOR); + lcdDrawHorizontalLine(x, y+5, width, SOLID, TEXT_COLOR); if (attr && (!(attr & BLINK) || !BLINK_ON_PHASE)) { - lcdDrawFilledRect(x+value*(width-5)/max, y, 5, 11, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x+value*(width-5)/max, y, 5, 11, TEXT_INVERTED_BGCOLOR); } else { - lcdDrawFilledRect(x+value*(width-5)/max, y, 5, 11, LINE_COLOR); + lcdDrawSolidFilledRect(x+value*(width-5)/max, y, 5, 11, LINE_COLOR); } } @@ -227,7 +225,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode)*10 : delta); else value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode) : delta); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } if (GV_IS_GV_VALUE(value, min, max)) { diff --git a/radio/src/gui/Taranis/helpers.cpp b/radio/src/gui/Taranis/helpers.cpp index f63f00db2..58de6d51d 100644 --- a/radio/src/gui/Taranis/helpers.cpp +++ b/radio/src/gui/Taranis/helpers.cpp @@ -41,391 +41,3 @@ uint8_t switchToMix(uint8_t source) div_t qr = div(source-1, 3); return qr.quot+MIXSRC_FIRST_SWITCH; } - -int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable) -{ - do { - current += inc; - if (current < min) - current = max; - else if (current > max) - current = min; - if (!isValueAvailable || isValueAvailable(current)) - return current; - } while(1); - return 0; -} - -bool isInputAvailable(int input) -{ - for (int i=0; ichn == input) - return true; - } - return false; -} - -bool isChannelUsed(int channel) -{ - for (int i=0; isrcRaw == 0) return false; - if (md->destCh == channel) return true; - if (md->destCh > channel) return false; - } - return false; -} - -int getChannelsUsed() -{ - int result = 0; - int lastCh = -1; - for (int i=0; isrcRaw == 0) return result; - if (md->destCh != lastCh) { ++result; lastCh = md->destCh; } - } - return result; -} - -bool isSourceAvailable(int source) -{ - if (source>=MIXSRC_FIRST_INPUT && source<=MIXSRC_LAST_INPUT) { - return isInputAvailable(source - MIXSRC_FIRST_INPUT); - } - -#if defined(LUA_MODEL_SCRIPTS) - if (source>=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) { - div_t qr = div(source-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); - return (qr.rem=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) - return false; -#endif - - if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { - return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); - } - - if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) { - return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); - } - -#if !defined(HELI) - if (source>=MIXSRC_CYC1 && source<=MIXSRC_CYC3) - return false; -#endif - - if (source>=MIXSRC_CH1 && source<=MIXSRC_LAST_CH) { - return isChannelUsed(source-MIXSRC_CH1); - } - - if (source>=MIXSRC_FIRST_LOGICAL_SWITCH && source<=MIXSRC_LAST_LOGICAL_SWITCH) { - LogicalSwitchData * cs = lswAddress(source-MIXSRC_FIRST_LOGICAL_SWITCH); - return (cs->func != LS_FUNC_NONE); - } - -#if !defined(GVARS) - if (source>=MIXSRC_GVAR1 && source<=MIXSRC_LAST_GVAR) - return false; -#endif - - if (source>=MIXSRC_RESERVE1 && source<=MIXSRC_RESERVE5) - return false; - - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - if (qr.rem == 0) - return isTelemetryFieldAvailable(qr.quot); - else - return isTelemetryFieldComparisonAvailable(qr.quot); - } - - return true; -} - -bool isSourceAvailableInGlobalFunctions(int source) -{ - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - return false; - } - return isSourceAvailable(source); -} - -bool isSourceAvailableInCustomSwitches(int source) -{ - bool result = isSourceAvailable(source); - -#if defined(FRSKY) - if (result && source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - result = isTelemetryFieldComparisonAvailable(qr.quot); - } -#endif - - return result; -} - -bool isInputSourceAvailable(int source) -{ - if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { - return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); - } - - if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) - return true; - - if (source>=MIXSRC_FIRST_TRIM && source<=MIXSRC_LAST_TRIM) - return true; - - if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) - return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); - - if (source>=MIXSRC_FIRST_CH && source<=MIXSRC_LAST_CH) - return true; - - if (source>=MIXSRC_FIRST_TRAINER && source<=MIXSRC_LAST_TRAINER) - return true; - - if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { - div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); - return isTelemetryFieldAvailable(qr.quot) && isTelemetryFieldComparisonAvailable(qr.quot); - } - - return false; -} - -enum SwitchContext -{ - LogicalSwitchesContext, - ModelCustomFunctionsContext, - GeneralCustomFunctionsContext, - TimersContext, - MixesContext -}; - -bool isLogicalSwitchAvailable(int index) -{ - LogicalSwitchData * lsw = lswAddress(index); - return (lsw->func != LS_FUNC_NONE); -} - -bool isSwitchAvailable(int swtch, SwitchContext context) -{ - bool negative = false; - - if (swtch < 0) { - negative = true; - if (swtch == -SWSRC_ON || swtch == -SWSRC_ONE) { - return false; - } - swtch = -swtch; - } - - if (swtch >= SWSRC_SA0 && swtch <= SWSRC_LAST_SWITCH) { - div_t swinfo = switchInfo(swtch); - if (!SWITCH_EXISTS(swinfo.quot)) { - return false; - } - if (!IS_3POS(swinfo.quot)) { - if (negative) { - return false; - } - if (IS_3POS_MIDDLE(swinfo.rem)) { - return false; - } - } - return true; - } - -#if NUM_XPOTS > 0 - if (swtch >= SWSRC_FIRST_MULTIPOS_SWITCH && swtch <= SWSRC_LAST_MULTIPOS_SWITCH) { - int index = (swtch - SWSRC_FIRST_MULTIPOS_SWITCH) / XPOTS_MULTIPOS_COUNT; - if (IS_POT_MULTIPOS(POT1+index)) { - StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[POT1+index]; - return (calib->count >= ((swtch - SWSRC_FIRST_MULTIPOS_SWITCH) % XPOTS_MULTIPOS_COUNT)); - } - else { - return false; - } - } -#endif - - if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH && swtch <= SWSRC_LAST_LOGICAL_SWITCH) { - if (context == GeneralCustomFunctionsContext) { - return false; - } - else if (context != LogicalSwitchesContext) { - return isLogicalSwitchAvailable(swtch - SWSRC_FIRST_LOGICAL_SWITCH); - } - } - - if (context != ModelCustomFunctionsContext && context != GeneralCustomFunctionsContext && (swtch == SWSRC_ON || swtch == SWSRC_ONE)) { - return false; - } - - if (swtch >= SWSRC_FIRST_FLIGHT_MODE && swtch <= SWSRC_LAST_FLIGHT_MODE) { - if (context == MixesContext || context == GeneralCustomFunctionsContext) { - return false; - } - else { - swtch -= SWSRC_FIRST_FLIGHT_MODE; - if (swtch == 0) { - return true; - } - FlightModeData * fm = flightModeAddress(swtch); - return (fm->swtch != SWSRC_NONE); - } - } - - if (swtch >= SWSRC_FIRST_SENSOR && swtch <= SWSRC_LAST_SENSOR) { - return isTelemetryFieldAvailable(swtch - SWSRC_FIRST_SENSOR); - } - - return true; -} - -bool isSwitchAvailableInLogicalSwitches(int swtch) -{ - return isSwitchAvailable(swtch, LogicalSwitchesContext); -} - -bool isSwitchAvailableInCustomFunctions(int swtch) -{ - if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) - return isSwitchAvailable(swtch, ModelCustomFunctionsContext); - else - return isSwitchAvailable(swtch, GeneralCustomFunctionsContext); -} - -bool isSwitchAvailableInMixes(int swtch) -{ - return isSwitchAvailable(swtch, MixesContext); -} - -bool isSwitchAvailableInTimers(int swtch) -{ - if (swtch >= 0) { - if (swtch < TMRMODE_COUNT) - return true; - else - swtch -= TMRMODE_COUNT-1; - } - else { - if (swtch > -TMRMODE_COUNT) - return false; - else - swtch += TMRMODE_COUNT-1; - } - - return isSwitchAvailable(swtch, TimersContext); -} - -bool isThrottleSourceAvailable(int source) -{ - if (source >= THROTTLE_SOURCE_FIRST_POT && source < THROTTLE_SOURCE_FIRST_POT+NUM_POTS && !IS_POT_AVAILABLE(POT1+source-THROTTLE_SOURCE_FIRST_POT)) - return false; - else - return true; -} - -bool isLogicalSwitchFunctionAvailable(int function) -{ - return function != LS_FUNC_RANGE; -} - -bool isAssignableFunctionAvailable(int function) -{ -#if defined(OVERRIDE_CHANNEL_FUNCTION) || defined(GVARS) - bool modelFunctions = (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions); -#endif - - switch (function) { - case FUNC_OVERRIDE_CHANNEL: -#if defined(OVERRIDE_CHANNEL_FUNCTION) - return modelFunctions; -#else - return false; -#endif - case FUNC_ADJUST_GVAR: -#if defined(GVARS) - return modelFunctions; -#else - return false; -#endif -#if !defined(HAPTIC) - case FUNC_HAPTIC: -#endif - case FUNC_RESERVE4: -#if !defined(DANGEROUS_MODULE_FUNCTIONS) - case FUNC_RANGECHECK: - case FUNC_BIND: -#endif -#if !defined(LUA) - case FUNC_PLAY_SCRIPT: -#endif - case FUNC_RESERVE5: - return false; - - default: - return true; - } -} - -bool isSourceAvailableInGlobalResetSpecialFunction(int index) -{ - if (index >= FUNC_RESET_PARAM_FIRST_TELEM) - return false; - else - return isSourceAvailableInResetSpecialFunction(index); -} - -bool isSourceAvailableInResetSpecialFunction(int index) -{ - if (index >= FUNC_RESET_PARAM_FIRST_TELEM) { - TelemetrySensor & telemetrySensor = g_model.telemetrySensors[index-FUNC_RESET_PARAM_FIRST_TELEM]; - return telemetrySensor.isAvailable(); - } -#if TIMERS < 3 - else if (index == FUNC_RESET_TIMER3) { - return false; - } -#endif -#if TIMERS < 2 - else if (index == FUNC_RESET_TIMER2) { - return false; - } -#endif - else { - return true; - } -} - -bool isModuleAvailable(int module) -{ - return true; -} - -bool modelHasNotes() -{ - char filename[sizeof(MODELS_PATH)+1+sizeof(g_model.header.name)+sizeof(TEXT_EXT)] = MODELS_PATH "/"; - char *buf = strcat_modelname(&filename[sizeof(MODELS_PATH)], g_eeGeneral.currModel); - strcpy(buf, TEXT_EXT); - return isFileAvailable(filename); -} - -int getFirstAvailable(int min, int max, bool (*func)(int)) -{ - int retval = 0; - for (int i = min; i <= max; i++) { - if ((*func)(i)) { - retval = i; - break; - } - } - return retval; -} diff --git a/radio/src/gui/Taranis/lcd.cpp b/radio/src/gui/Taranis/lcd.cpp index 18f17e8cf..23326517e 100644 --- a/radio/src/gui/Taranis/lcd.cpp +++ b/radio/src/gui/Taranis/lcd.cpp @@ -38,11 +38,11 @@ #include "../../timers.h" #if defined(REVPLUS) && defined(LCD_DUAL_BUFFER) - display_t displayBuf1[DISPLAY_BUF_SIZE] __DMA; - display_t displayBuf2[DISPLAY_BUF_SIZE] __DMA; + display_t displayBuf1[DISPLAY_BUFFER_SIZE] __DMA; + display_t displayBuf2[DISPLAY_BUFFER_SIZE] __DMA; display_t * displayBuf = displayBuf1; #else - display_t displayBuf[DISPLAY_BUF_SIZE] __DMA; + display_t displayBuf[DISPLAY_BUFFER_SIZE] __DMA; #endif inline bool lcdIsPointOutside(coord_t x, coord_t y) @@ -143,7 +143,7 @@ void lcdPutPattern(coord_t x, coord_t y, const uint8_t * pattern, uint8_t width, } } -void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) +void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) { const pm_uchar * q; @@ -215,7 +215,7 @@ void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags flags) void lcd_putc(coord_t x, coord_t y, const unsigned char c) { - lcd_putcAtt(x, y, c, 0); + lcdDrawChar(x, y, c, 0); } void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) @@ -248,7 +248,7 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags break; } else if (c >= 0x20) { - lcd_putcAtt(x, y, c, flags); + lcdDrawChar(x, y, c, flags); x = lcdNextPos; } else if (c == 0x1F) { //X-coord prefix @@ -315,7 +315,7 @@ void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags flags) x -= FWNUM; char c = val & 0xf; c = c>9 ? c+'A'-10 : c+'0'; - lcd_putcAtt(x, y, c, flags|(c>='A' ? CONDENSED : 0)); + lcdDrawChar(x, y, c, flags|(c>='A' ? CONDENSED : 0)); val >>= 4; } } @@ -403,7 +403,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t div_t qr = div((lcduint_t)val, 10); char c = qr.rem + '0'; LcdFlags f = flags; - lcd_putcAtt(x, y, c, f); + lcdDrawChar(x, y, c, f); if (mode == i) { flags &= ~PREC2; // TODO not needed but removes 20bytes, could be improved for sure, check asm if (dblsize) { @@ -422,7 +422,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t } else if (xxlsize) { x -= 17; - lcd_putcAtt(x+2, y, '.', f); + lcdDrawChar(x+2, y, '.', f); } else if (midsize) { x -= 3; @@ -445,7 +445,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t } else { x -= 2; - lcd_putcAtt(x, y, '.', f); + lcdDrawChar(x, y, '.', f); } } val = qr.quot; @@ -469,17 +469,17 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t drawFilledRect(xn, y+2*FH-3, ln, 2); } } - if (neg) lcd_putcAtt(x, y, '-', flags); + if (neg) lcdDrawChar(x, y, '-', flags); } #endif void lcd_hline(coord_t x, coord_t y, coord_t w, LcdFlags att) { - lcd_hlineStip(x, y, w, 0xff, att); + lcdDrawHorizontalLine(x, y, w, 0xff, att); } #if !defined(BOOT) -void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFlags att) +void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFlags att) { if (lcdIsPointOutside(x1, y1) || lcdIsPointOutside(x2, y2)) return; @@ -526,17 +526,17 @@ void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat, LcdFl void lcd_vline(coord_t x, scoord_t y, scoord_t h) { - lcd_vlineStip(x, y, h, SOLID); + lcdDrawVerticalLine(x, y, h, SOLID); } #endif -void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) +void lcdDrawRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) { - lcd_vlineStip(x, y, h, pat, att); - lcd_vlineStip(x+w-1, y, h, pat, att); + lcdDrawVerticalLine(x, y, h, pat, att); + lcdDrawVerticalLine(x+w-1, y, h, pat, att); if (~att & ROUND) { x+=1; w-=2; } - lcd_hlineStip(x, y+h-1, w, pat, att); - lcd_hlineStip(x, y, w, pat, att); + lcdDrawHorizontalLine(x, y+h-1, w, pat, att); + lcdDrawHorizontalLine(x, y, w, pat, att); } #if !defined(BOOT) @@ -544,9 +544,9 @@ void drawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat, Lc { for (scoord_t i=y; i> 1) + ((pat & 1) << 7); } } @@ -588,7 +588,7 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 } if (tme < 0) { - lcd_putcAtt(x - ((att & DBLSIZE) ? FW+2 : ((att & MIDSIZE) ? FW+0 : FWNUM)), y, '-', att); + lcdDrawChar(x - ((att & DBLSIZE) ? FW+2 : ((att & MIDSIZE) ? FW+0 : FWNUM)), y, '-', att); tme = -tme; } @@ -598,7 +598,7 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 if (att & TIMEHOUR) { div_t qr2 = div(qr.quot, 60); lcd_outdezNAtt(x, y, qr2.quot, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, separator, att&att2); + lcdDrawChar(lcdLastPos, y, separator, att&att2); qr.quot = qr2.rem; if (att & MIDSIZE) x += 17; @@ -607,9 +607,9 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 } lcd_outdezNAtt(x, y, qr.quot, att|LEADING0|LEFT, 2); if (att&TIMEBLINK) - lcd_putcAtt(lcdLastPos, y, separator, BLINK); + lcdDrawChar(lcdLastPos, y, separator, BLINK); else - lcd_putcAtt(lcdLastPos, y, separator, att&att2); + lcdDrawChar(lcdLastPos, y, separator, att&att2); lcd_outdezNAtt(lcdNextPos, y, qr.rem, att2|LEADING0|LEFT, 2); } @@ -617,7 +617,7 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 void putsVolts(coord_t x, coord_t y, uint16_t volts, LcdFlags att) { lcd_outdezAtt(x, y, (int16_t)volts, (~NO_UNIT) & (att | ((att&PREC2)==PREC2 ? 0 : PREC1))); - if (~att & NO_UNIT) lcd_putcAtt(lcdLastPos, y, 'V', att); + if (~att & NO_UNIT) lcdDrawChar(lcdLastPos, y, 'V', att); } void putsVBat(coord_t x, coord_t y, LcdFlags att) @@ -643,7 +643,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) lcd_putsiAtt(x, y, STR_VSRCRAW, 0, att); // TODO macro } else if (idx <= MIXSRC_LAST_INPUT) { - lcd_putcAtt(x+2, y+1, CHR_INPUT, TINSIZE); + lcdDrawChar(x+2, y+1, CHR_INPUT, TINSIZE); drawFilledRect(x, y, 7, 7); if (ZEXIST(g_model.inputNames[idx-MIXSRC_FIRST_INPUT])) lcd_putsnAtt(x+8, y, g_model.inputNames[idx-MIXSRC_FIRST_INPUT], LEN_INPUT_NAME, ZCHAR|att); @@ -655,7 +655,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) div_t qr = div(idx-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); #if defined(LUA_MODEL_SCRIPTS) if (qr.quot < MAX_SCRIPTS && qr.rem < scriptInputsOutputs[qr.quot].outputsCount) { - lcd_putcAtt(x+2, y+1, '1'+qr.quot, TINSIZE); + lcdDrawChar(x+2, y+1, '1'+qr.quot, TINSIZE); drawFilledRect(x, y, 7, 7); lcd_putsnAtt(x+8, y, scriptInputsOutputs[qr.quot].outputs[qr.rem].name, att & STREXPANDED ? 9 : 4, att); } @@ -663,7 +663,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) #endif { putsStrIdx(x, y, "LUA", qr.quot+1, att); - lcd_putcAtt(lcdLastPos, y, 'a'+qr.rem, att); + lcdDrawChar(lcdLastPos, y, 'a'+qr.rem, att); } } @@ -671,11 +671,11 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) idx = idx-MIXSRC_Rud; if (ZEXIST(g_eeGeneral.anaNames[idx])) { if (idx < MIXSRC_FIRST_POT-MIXSRC_Rud ) - lcd_putcAtt(x, y, '\307', att); //stick symbol + lcdDrawChar(x, y, '\307', att); //stick symbol else if (idx < MIXSRC_FIRST_SLIDER-MIXSRC_Rud ) - lcd_putcAtt(x, y, '\310', att); //pot symbol + lcdDrawChar(x, y, '\310', att); //pot symbol else - lcd_putcAtt(x, y, '\311', att); //slider symbol + lcdDrawChar(x, y, '\311', att); //slider symbol lcd_putsnAtt(lcdNextPos, y, g_eeGeneral.anaNames[idx], LEN_ANA_NAME, ZCHAR|att); } else @@ -684,7 +684,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) else if (idx >= MIXSRC_FIRST_SWITCH && idx <= MIXSRC_LAST_SWITCH) { idx = idx-MIXSRC_FIRST_SWITCH; if (ZEXIST(g_eeGeneral.switchNames[idx])) { - lcd_putcAtt(x, y, '\312', att); //switch symbol + lcdDrawChar(x, y, '\312', att); //switch symbol lcd_putsnAtt(lcdNextPos, y, g_eeGeneral.switchNames[idx], LEN_SWITCH_NAME, ZCHAR|att); } else @@ -699,7 +699,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) else if (idx <= MIXSRC_LAST_CH) { putsStrIdx(x, y, STR_CH, idx-MIXSRC_CH1+1, att); if (ZEXIST(g_model.limitData[idx-MIXSRC_CH1].name) && (att & STREXPANDED)) { - lcd_putcAtt(lcdLastPos, y, ' ', att|SMLSIZE); + lcdDrawChar(lcdLastPos, y, ' ', att|SMLSIZE); lcd_putsnAtt(lcdLastPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att|SMLSIZE); } } @@ -713,7 +713,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) idx -= MIXSRC_FIRST_TELEM; div_t qr = div(idx, 3); lcd_putsnAtt(x, y, g_model.telemetrySensors[qr.quot].label, TELEM_LABEL_LEN, ZCHAR|att); - if (qr.rem) lcd_putcAtt(lcdLastPos, y, qr.rem==2 ? '+' : '-', att); + if (qr.rem) lcdDrawChar(lcdLastPos, y, qr.rem==2 ? '+' : '-', att); } } @@ -744,7 +744,7 @@ void putsSwitches(coord_t x, coord_t y, int32_t idx, LcdFlags att) } if (idx < 0) { - lcd_putcAtt(x-2, y, '!', att); + lcdDrawChar(x-2, y, '!', att); idx = -idx; } @@ -754,11 +754,11 @@ void putsSwitches(coord_t x, coord_t y, int32_t idx, LcdFlags att) lcd_putsnAtt(x, y, g_eeGeneral.switchNames[swinfo.quot], LEN_SWITCH_NAME, ZCHAR|att); } else { - lcd_putcAtt(x, y, 'S', att); - lcd_putcAtt(lcdNextPos, y, 'A'+swinfo.quot, att); + lcdDrawChar(x, y, 'S', att); + lcdDrawChar(lcdNextPos, y, 'A'+swinfo.quot, att); } char c = "\300-\301"[swinfo.rem]; - lcd_putcAtt(lcdNextPos, y, c, att); + lcdDrawChar(lcdNextPos, y, c, att); } else if (idx <= SWSRC_LAST_MULTIPOS_SWITCH) { div_t swinfo = div(idx - SWSRC_FIRST_MULTIPOS_SWITCH, XPOTS_MULTIPOS_COUNT); @@ -788,7 +788,7 @@ void putsSwitches(coord_t x, coord_t y, int32_t idx, LcdFlags att) void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx==0) { lcd_putsiAtt(x, y, STR_MMMINV, 0, att); return; } - if (idx < 0) { lcd_putcAtt(x-2, y, '!', att); idx = -idx; } + if (idx < 0) { lcdDrawChar(x-2, y, '!', att); idx = -idx; } if (att & CONDENSED) lcd_outdezNAtt(x+FW*1, y, idx-1, (att & ~CONDENSED), 1); else @@ -801,12 +801,12 @@ void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att) if (curve.value != 0) { switch (curve.type) { case CURVE_REF_DIFF: - lcd_putcAtt(x, y, 'D', att); + lcdDrawChar(x, y, 'D', att); GVAR_MENU_ITEM(x+FW, y, curve.value, -100, 100, LEFT|att, 0, 0); break; case CURVE_REF_EXPO: - lcd_putcAtt(x, y, 'E', att); + lcdDrawChar(x, y, 'E', att); GVAR_MENU_ITEM(x+FW, y, curve.value, -100, 100, LEFT|att, 0, 0); break; @@ -828,7 +828,7 @@ void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att) } if (idx < 0) { - lcd_putcAtt(x-3, y, '!', att); + lcdDrawChar(x-3, y, '!', att); idx = -idx; } @@ -857,10 +857,10 @@ void putsTrimMode(coord_t x, coord_t y, uint8_t phase, uint8_t idx, LcdFlags att } else { if (mode % 2 == 0) - lcd_putcAtt(x, y, ':', att|FIXEDWIDTH); + lcdDrawChar(x, y, ':', att|FIXEDWIDTH); else - lcd_putcAtt(x, y, '+', att|FIXEDWIDTH); - lcd_putcAtt(lcdNextPos, y, '0'+p, att); + lcdDrawChar(x, y, '+', att|FIXEDWIDTH); + lcdDrawChar(lcdNextPos, y, '0'+p, att); } } @@ -888,7 +888,7 @@ void displayGpsCoord(coord_t x, coord_t y, char direction, int16_t bp, int16_t a { if (!direction) direction = '-'; lcd_outdezAtt(x, y, bp / 100, att); // ddd before '.' - lcd_putcAtt(lcdLastPos, y, '@', att); + lcdDrawChar(lcdLastPos, y, '@', att); uint8_t mn = bp % 100; // TODO div_t if (g_eeGeneral.gpsFormat == 0) { lcd_outdezNAtt(lcdNextPos, y, mn, att|LEFT|LEADING0, 2); // mm before '.' @@ -917,27 +917,27 @@ void displayDate(coord_t x, coord_t y, TelemetryItem & telemetryItem, LcdFlags a x -= 42; att &= ~0x0F00; // TODO constant lcd_outdezNAtt(x, y, telemetryItem.datetime.day, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos-1, y, '-', att); + lcdDrawChar(lcdLastPos-1, y, '-', att); lcd_outdezNAtt(lcdNextPos-1, y, telemetryItem.datetime.month, att|LEFT, 2); - lcd_putcAtt(lcdLastPos-1, y, '-', att); + lcdDrawChar(lcdLastPos-1, y, '-', att); lcd_outdezAtt(lcdNextPos-1, y, telemetryItem.datetime.year, att|LEFT); y += FH; lcd_outdezNAtt(x, y, telemetryItem.datetime.hour, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.min, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.sec, att|LEADING0|LEFT, 2); } else { lcd_outdezNAtt(x, y, telemetryItem.datetime.day, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos-1, y, '-', att); + lcdDrawChar(lcdLastPos-1, y, '-', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.month, att|LEFT, 2); - lcd_putcAtt(lcdLastPos-1, y, '-', att); + lcdDrawChar(lcdLastPos-1, y, '-', att); lcd_outdezAtt(lcdNextPos, y, telemetryItem.datetime.year, att|LEFT); lcd_outdezNAtt(lcdNextPos+FW+1, y, telemetryItem.datetime.hour, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.min, att|LEADING0|LEFT, 2); - lcd_putcAtt(lcdLastPos, y, ':', att); + lcdDrawChar(lcdLastPos, y, ':', att); lcd_outdezNAtt(lcdNextPos, y, telemetryItem.datetime.sec, att|LEADING0|LEFT, 2); } } @@ -1048,7 +1048,7 @@ void lcd_plot(coord_t x, coord_t y, LcdFlags att) lcd_mask(p, mask, att); } -void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) +void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) { if (y < 0 || y >= LCD_H) return; if (x+w > LCD_W) { @@ -1070,7 +1070,7 @@ void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) } } -void lcd_vlineStip(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att) +void lcdDrawVerticalLine(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att) { if (x >= LCD_W) return; if (y >= LCD_H) return; diff --git a/radio/src/gui/Taranis/lcd.h b/radio/src/gui/Taranis/lcd.h index a0813c0ec..6278e7849 100644 --- a/radio/src/gui/Taranis/lcd.h +++ b/radio/src/gui/Taranis/lcd.h @@ -119,14 +119,14 @@ #define LcdFlags uint32_t #define display_t uint8_t -#define DISPLAY_BUF_SIZE (LCD_W*LCD_H*4/8) +#define DISPLAY_BUFFER_SIZE (LCD_W*LCD_H*4/8) #if defined(REVPLUS) && defined(LCD_DUAL_BUFFER) - extern display_t displayBuf1[DISPLAY_BUF_SIZE]; - extern display_t displayBuf2[DISPLAY_BUF_SIZE]; + extern display_t displayBuf1[DISPLAY_BUFFER_SIZE]; + extern display_t displayBuf2[DISPLAY_BUFFER_SIZE]; extern display_t * displayBuf; #else - extern display_t displayBuf[DISPLAY_BUF_SIZE]; + extern display_t displayBuf[DISPLAY_BUFFER_SIZE]; #endif #if defined(REVPLUS) && !defined(LCD_DUAL_BUFFER) && !defined(SIMU) @@ -138,8 +138,7 @@ extern coord_t lcdLastPos; extern coord_t lcdNextPos; -#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_BUF_SIZE) -#define DISPLAY_END (displayBuf + DISPLAY_BUF_SIZE) +#define DISPLAY_END (displayBuf + DISPLAY_BUFFER_SIZE) #define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) #if defined(BOOT) @@ -149,7 +148,7 @@ typedef const char pm_char; #endif void lcd_putc(coord_t x, coord_t y, const unsigned char c); -void lcd_putcAtt(coord_t x, coord_t y, const unsigned char c, LcdFlags mode); +void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags mode); void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags mode); void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags mode); void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s,unsigned char len, LcdFlags mode); @@ -201,17 +200,17 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att, LcdFlags att2 void lcd_plot(coord_t x, coord_t y, LcdFlags att=0); void lcd_mask(uint8_t *p, uint8_t mask, LcdFlags att=0); void lcd_hline(coord_t x, coord_t y, coord_t w, LcdFlags att=0); -void lcd_hlineStip(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); +void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); void lcd_vline(coord_t x, scoord_t y, scoord_t h); -void lcd_vlineStip(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); -void lcd_line(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); +void lcdDrawVerticalLine(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); +void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); void drawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); -void lcd_rect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); +void lcdDrawRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat=SOLID, LcdFlags att=0); void lcd_invert_line(int8_t line); #define lcd_status_line() lcd_invert_line(LCD_LINES-1) -inline void lcd_square(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { lcd_rect(x, y, w, w, SOLID, att); } +inline void lcd_square(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { lcdDrawRect(x, y, w, w, SOLID, att); } void displaySleepBitmap(); @@ -248,7 +247,7 @@ const char *writeScreenshot(); #if defined(SIMU) extern bool lcd_refresh; - extern display_t lcd_buf[DISPLAY_BUF_SIZE]; + extern display_t lcd_buf[DISPLAY_BUFFER_SIZE]; #endif char *strAppend(char * dest, const char * source, int len=0); diff --git a/radio/src/gui/Taranis/menu_general_calib.cpp b/radio/src/gui/Taranis/menu_general_calib.cpp index 2dfb41718..52b8876bd 100644 --- a/radio/src/gui/Taranis/menu_general_calib.cpp +++ b/radio/src/gui/Taranis/menu_general_calib.cpp @@ -175,7 +175,7 @@ void menuCommonCalib(uint8_t event) } } g_eeGeneral.chkSum = evalChkSum(); - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); reusableBuffer.calib.state = 4; break; diff --git a/radio/src/gui/Taranis/menu_general_diagkeys.cpp b/radio/src/gui/Taranis/menu_general_diagkeys.cpp index 9a2ee4425..ff1ae4e62 100644 --- a/radio/src/gui/Taranis/menu_general_diagkeys.cpp +++ b/radio/src/gui/Taranis/menu_general_diagkeys.cpp @@ -39,7 +39,7 @@ void displayKeyState(uint8_t x, uint8_t y, EnumKeys key) { uint8_t t = switchState(key); - lcd_putcAtt(x, y, t+'0', t ? INVERS : 0); + lcdDrawChar(x, y, t+'0', t ? INVERS : 0); } void menuGeneralDiagKeys(uint8_t event) diff --git a/radio/src/gui/Taranis/menu_general_sdmanager.cpp b/radio/src/gui/Taranis/menu_general_sdmanager.cpp index 4dfc3f4db..330bf4895 100644 --- a/radio/src/gui/Taranis/menu_general_sdmanager.cpp +++ b/radio/src/gui/Taranis/menu_general_sdmanager.cpp @@ -181,7 +181,7 @@ void onSdManagerMenu(const char *result) strcat(lfn, line); } if (strcmp(clipboard.data.sd.directory, lfn)) { // prevent copying to the same directory - POPUP_WARNING(fileCopy(clipboard.data.sd.filename, clipboard.data.sd.directory, lfn)); + POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, lfn)); REFRESH_FILES(); } } @@ -217,7 +217,7 @@ void onSdManagerMenu(const char *result) else if (result == STR_ASSIGN_BITMAP) { strAppendFilename(g_model.header.bitmap, line, sizeof(g_model.header.bitmap)); memcpy(modelHeaders[g_eeGeneral.currModel].bitmap, g_model.header.bitmap, sizeof(g_model.header.bitmap)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_VIEW_TEXT) { getSelectionFullPath(lfn); @@ -448,7 +448,7 @@ void menuGeneralSdManager(uint8_t _event) lcdNextPos = 0; LcdFlags attr = (index == i ? BSS|INVERS : BSS); if (reusableBuffer.sdmanager.lines[i][0]) { - if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[i])) { lcd_putcAtt(0, y, '[', s_editMode == EDIT_MODIFY_STRING ? 0 : attr); } + if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[i])) { lcdDrawChar(0, y, '[', s_editMode == EDIT_MODIFY_STRING ? 0 : attr); } if (s_editMode == EDIT_MODIFY_STRING && attr) { editName(lcdNextPos, y, reusableBuffer.sdmanager.lines[i], SD_SCREEN_FILE_LENGTH-4, _event, attr, 0); if (s_editMode == 0) { @@ -467,7 +467,7 @@ void menuGeneralSdManager(uint8_t _event) else { lcd_putsAtt(lcdNextPos, y, reusableBuffer.sdmanager.lines[i], attr); } - if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[i])) { lcd_putcAtt(lcdNextPos, y, ']', s_editMode == EDIT_MODIFY_STRING ? 0 : attr); } + if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[i])) { lcdDrawChar(lcdNextPos, y, ']', s_editMode == EDIT_MODIFY_STRING ? 0 : attr); } } } diff --git a/radio/src/gui/Taranis/menu_general_setup.cpp b/radio/src/gui/Taranis/menu_general_setup.cpp index f3b7864a6..85b07064f 100644 --- a/radio/src/gui/Taranis/menu_general_setup.cpp +++ b/radio/src/gui/Taranis/menu_general_setup.cpp @@ -124,7 +124,7 @@ void menuGeneralSetup(uint8_t event) if (s_warning_result) { s_warning_result = 0; g_eeGeneral.fai = true; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); } #endif @@ -251,7 +251,7 @@ void menuGeneralSetup(uint8_t event) case ITEM_SETUP_SPEAKER_PITCH: lcd_putsLeft( y, STR_SPKRPITCH); - lcd_putcAtt(RADIO_SETUP_2ND_COLUMN, y, '+', attr); + lcdDrawChar(RADIO_SETUP_2ND_COLUMN, y, '+', attr); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN+FW, y, g_eeGeneral.speakerPitch*15, attr|LEFT); lcd_putsAtt(lcdLastPos, y, "Hz", attr); if (attr) { @@ -486,13 +486,13 @@ void menuGeneralSetup(uint8_t event) if (attr) { s_editMode = 0; CHECK_INCDEC_GENVAR(event, g_eeGeneral.stickReverse, 0, 15); - lcd_rect(6*FW-1, y-1, 15*FW+2, 9); + lcdDrawRect(6*FW-1, y-1, 15*FW+2, 9); } #endif break; case ITEM_SETUP_STICK_MODE: - lcd_putcAtt(2*FW, y, '1'+g_eeGeneral.stickMode, attr); + lcdDrawChar(2*FW, y, '1'+g_eeGeneral.stickMode, attr); for (uint8_t i=0; i<4; i++) { putsStickName((6+4*i)*FW, y, pgm_read_byte(modn12x3 + 4*g_eeGeneral.stickMode + i), 0); } diff --git a/radio/src/gui/Taranis/menu_general_trainer.cpp b/radio/src/gui/Taranis/menu_general_trainer.cpp index aed419c09..12984b398 100644 --- a/radio/src/gui/Taranis/menu_general_trainer.cpp +++ b/radio/src/gui/Taranis/menu_general_trainer.cpp @@ -108,7 +108,7 @@ void menuGeneralTrainer(uint8_t event) if (attr) { if (event==EVT_KEY_LONG(KEY_ENTER)){ memcpy(g_eeGeneral.trainer.calib, ppmInput, sizeof(g_eeGeneral.trainer.calib)); - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); AUDIO_WARNING1(); } } diff --git a/radio/src/gui/Taranis/menu_general_version.cpp b/radio/src/gui/Taranis/menu_general_version.cpp index ca1154d88..545267343 100644 --- a/radio/src/gui/Taranis/menu_general_version.cpp +++ b/radio/src/gui/Taranis/menu_general_version.cpp @@ -47,19 +47,14 @@ void backupEeprom() // reset unexpectedShutdown to prevent warning when user restores EEPROM backup g_eeGeneral.unexpectedShutdown = 0; - eeDirty(EE_GENERAL); - eeCheck(true); + storageDirty(EE_GENERAL); + storageCheck(true); // create the directory if needed... - DIR folder; - FRESULT result = f_opendir(&folder, EEPROMS_PATH); - if (result != FR_OK) { - if (result == FR_NO_PATH) - result = f_mkdir(EEPROMS_PATH); - if (result != FR_OK) { - POPUP_WARNING(SDCARD_ERROR(result)); - return; - } + const char * error = sdCheckAndCreateDirectory(EEPROMS_PATH); + if (error) { + POPUP_WARNING(error); + return; } // prepare the filename... @@ -82,8 +77,8 @@ void backupEeprom() //set back unexpectedShutdown g_eeGeneral.unexpectedShutdown = 1; - eeDirty(EE_GENERAL); - eeCheck(true); + storageDirty(EE_GENERAL); + storageCheck(true); } void menuGeneralVersion(uint8_t event) @@ -91,7 +86,7 @@ void menuGeneralVersion(uint8_t event) if (s_warning_result) { s_warning_result = 0; displayPopup(STR_EEPROMFORMATTING); - eeErase(false); + storageEraseAll(false); #if !defined(SIMU) NVIC_SystemReset(); #else diff --git a/radio/src/gui/Taranis/menu_model.cpp b/radio/src/gui/Taranis/menu_model.cpp index 733651119..ee095d5ce 100644 --- a/radio/src/gui/Taranis/menu_model.cpp +++ b/radio/src/gui/Taranis/menu_model.cpp @@ -209,14 +209,14 @@ void editName(coord_t x, coord_t y, char *name, uint8_t size, uint8_t event, uin if (c != v) { name[cur] = v; - eeDirty(g_menuPos[0] == 0 ? EE_MODEL : EE_GENERAL); + storageDirty(g_menuPos[0] == 0 ? EE_MODEL : EE_GENERAL); } if (attr == ZCHAR) { - lcd_putcAtt(x+editNameCursorPos*FW, y, idx2char(v), ERASEBG|INVERS|FIXEDWIDTH); + lcdDrawChar(x+editNameCursorPos*FW, y, idx2char(v), ERASEBG|INVERS|FIXEDWIDTH); } else { - lcd_putcAtt(x+editNameCursorPos*FW, y, v, ERASEBG|INVERS|FIXEDWIDTH); + lcdDrawChar(x+editNameCursorPos*FW, y, v, ERASEBG|INVERS|FIXEDWIDTH); } } else { diff --git a/radio/src/gui/Taranis/menu_model_curves.cpp b/radio/src/gui/Taranis/menu_model_curves.cpp index bad8fc69d..89e5cf5d2 100644 --- a/radio/src/gui/Taranis/menu_model_curves.cpp +++ b/radio/src/gui/Taranis/menu_model_curves.cpp @@ -91,7 +91,7 @@ bool moveCurve(uint8_t index, int8_t shift) curveEnd[index++] += shift; } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); return true; } @@ -99,7 +99,7 @@ void displayPresetChoice(uint8_t event) { displayWarning(event); lcd_outdezAtt(WARNING_LINE_X+FW*7, WARNING_LINE_Y, 45*s_warning_input_value/4, LEFT|INVERS); - lcd_putcAtt(lcdLastPos, WARNING_LINE_Y, '@', INVERS); + lcdDrawChar(lcdLastPos, WARNING_LINE_Y, '@', INVERS); if (s_warning_result) { s_warning_result = 0; diff --git a/radio/src/gui/Taranis/menu_model_custom_functions.cpp b/radio/src/gui/Taranis/menu_model_custom_functions.cpp index 117ed9745..a7f3ef762 100644 --- a/radio/src/gui/Taranis/menu_model_custom_functions.cpp +++ b/radio/src/gui/Taranis/menu_model_custom_functions.cpp @@ -67,7 +67,7 @@ void onCustomFunctionsFileSelectionMenu(const char *result) strcpy(directory, SOUNDS_PATH); strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); } - if (!listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), NULL)) { + if (!sdListFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), NULL)) { POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); s_menu_flags = 0; } @@ -75,7 +75,7 @@ void onCustomFunctionsFileSelectionMenu(const char *result) else { // The user choosed a file in the list memcpy(cfn->play.name, result, sizeof(cfn->play.name)); - eeDirty(eeFlags); + storageDirty(eeFlags); if (func == FUNC_PLAY_SCRIPT) { LUA_LOAD_MODEL_SCRIPTS(); } @@ -103,21 +103,21 @@ void onCustomFunctionsMenu(const char *result) } else if (result == STR_PASTE) { *cfn = clipboard.data.cfn; - eeDirty(eeFlags); + storageDirty(eeFlags); } else if (result == STR_CLEAR) { memset(cfn, 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); + storageDirty(eeFlags); } else if (result == STR_INSERT) { memmove(cfn+1, cfn, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); memset(cfn, 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); + storageDirty(eeFlags); } else if (result == STR_DELETE) { memmove(cfn, cfn+1, (NUM_CFN-sub-1)*sizeof(CustomFunctionData)); memset(&g_model.customFn[NUM_CFN-1], 0, sizeof(CustomFunctionData)); - eeDirty(eeFlags); + storageDirty(eeFlags); } } @@ -128,22 +128,22 @@ void onAdjustGvarSourceLongEnterPress(const char * result) if (result == STR_CONSTANT) { CFN_GVAR_MODE(cfn) = FUNC_ADJUST_GVAR_CONSTANT; CFN_PARAM(cfn) = 0; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_MIXSOURCE) { CFN_GVAR_MODE(cfn) = FUNC_ADJUST_GVAR_SOURCE; CFN_PARAM(cfn) = 0; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_GLOBALVAR) { CFN_GVAR_MODE(cfn) = FUNC_ADJUST_GVAR_GVAR; CFN_PARAM(cfn) = 0; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_INCDEC) { CFN_GVAR_MODE(cfn) = FUNC_ADJUST_GVAR_INC; CFN_PARAM(cfn) = 0; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else { onSourceLongEnterPress(result); @@ -303,7 +303,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu strcpy(directory, SOUNDS_PATH); strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); } - if (listSdFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { + if (sdListFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { menuHandler = onCustomFunctionsFileSelectionMenu; } else { @@ -415,12 +415,12 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { - lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, '!', attr); + lcdDrawChar(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, '!', attr); lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); } else { lcd_outdezAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr); - lcd_putcAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, 's', attr); + lcdDrawChar(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, 's', attr); } if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); } diff --git a/radio/src/gui/Taranis/menu_model_custom_scripts.cpp b/radio/src/gui/Taranis/menu_model_custom_scripts.cpp index e72f48fde..216d84b9b 100644 --- a/radio/src/gui/Taranis/menu_model_custom_scripts.cpp +++ b/radio/src/gui/Taranis/menu_model_custom_scripts.cpp @@ -40,7 +40,7 @@ void onModelCustomScriptMenu(const char *result) ScriptData &sd = g_model.scriptsData[s_currIdx]; if (result == STR_UPDATE_LIST) { - if (!listSdFiles(SCRIPTS_MIXES_PATH, SCRIPTS_EXT, sizeof(sd.file), NULL)) { + if (!sdListFiles(SCRIPTS_MIXES_PATH, SCRIPTS_EXT, sizeof(sd.file), NULL)) { POPUP_WARNING(STR_NO_SCRIPTS_ON_SD); s_menu_flags = 0; } @@ -49,7 +49,7 @@ void onModelCustomScriptMenu(const char *result) // The user choosed a lua file in the list copySelection(sd.file, result, sizeof(sd.file)); memset(sd.inputs, 0, sizeof(sd.inputs)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); LUA_LOAD_MODEL_SCRIPT(s_currIdx); } } @@ -88,7 +88,7 @@ void menuModelCustomScriptOne(uint8_t event) lcd_putsiAtt(SCRIPT_ONE_2ND_COLUMN_POS, y, STR_VCSWFUNC, 0, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER) && !READ_ONLY()) { s_editMode = 0; - if (listSdFiles(SCRIPTS_MIXES_PATH, SCRIPTS_EXT, sizeof(sd.file), sd.file, LIST_NONE_SD_FILE)) { + if (sdListFiles(SCRIPTS_MIXES_PATH, SCRIPTS_EXT, sizeof(sd.file), sd.file, LIST_NONE_SD_FILE)) { menuHandler = onModelCustomScriptMenu; } else { diff --git a/radio/src/gui/Taranis/menu_model_gvars.cpp b/radio/src/gui/Taranis/menu_model_gvars.cpp index fc4aff11c..f37743cbb 100644 --- a/radio/src/gui/Taranis/menu_model_gvars.cpp +++ b/radio/src/gui/Taranis/menu_model_gvars.cpp @@ -41,17 +41,17 @@ void onGVARSMenu(const char *result) if (result == STR_ENABLE_POPUP) { g_model.gvars[sub].popup = true; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_DISABLE_POPUP) { g_model.gvars[sub].popup = false; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_CLEAR) { for (int i=0; i GVAR_MAX ? 0 : GVAR_MAX+1); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (s_editMode>0) { v = checkIncDec(event, v, vmin, vmax, EE_MODEL); diff --git a/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp b/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp index 38eb02c88..16584f3d0 100644 --- a/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp @@ -53,9 +53,9 @@ FlightModesType editFlightModes(coord_t x, coord_t y, uint8_t event, FlightModes if (posHorz==p) flags |= BLINK; } if (value & (1<weight = 100; } resumeMixerCalculations(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } void copyExpoMix(uint8_t expo, uint8_t idx) @@ -193,7 +193,7 @@ void copyExpoMix(uint8_t expo, uint8_t idx) memmove(mix+1, mix, (MAX_MIXERS-(idx+1))*sizeof(MixData)); } resumeMixerCalculations(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } void memswap(void *a, void *b, uint8_t size) @@ -460,8 +460,8 @@ void drawOffsetBar(uint8_t x, uint8_t y, MixData * md) barMin = -101; if (barMax > 101) barMax = 101; - lcd_hlineStip(x-2, y, GAUGE_WIDTH+2, DOTTED); - lcd_hlineStip(x-2, y+GAUGE_HEIGHT, GAUGE_WIDTH+2, DOTTED); + lcdDrawHorizontalLine(x-2, y, GAUGE_WIDTH+2, DOTTED); + lcdDrawHorizontalLine(x-2, y+GAUGE_HEIGHT, GAUGE_WIDTH+2, DOTTED); lcd_vline(x-2, y+1, GAUGE_HEIGHT-1); lcd_vline(x+GAUGE_WIDTH-1, y+1, GAUGE_HEIGHT-1); if (barMin <= barMax) { @@ -734,7 +734,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) swapExpoMix(expo, s_currIdx, s_copyTgtOfs > 0); s_copyTgtOfs += (s_copyTgtOfs < 0 ? +1 : -1); } while (s_copyTgtOfs != 0); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } m_posVert = s_copySrcRow; s_copyTgtOfs = 0; @@ -822,7 +822,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) else { // only swap the mix with its neighbor if (!swapExpoMix(expo, s_currIdx, key==KEY_MOVE_UP)) break; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } s_copyTgtOfs = next_ofs; @@ -887,7 +887,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) do { if (s_copyMode) { if (s_copyMode == MOVE_MODE && cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES && s_copySrcCh == ch && s_copyTgtOfs != 0 && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { - lcd_rect(expo ? 18 : 22, y-1, expo ? LCD_W-18 : LCD_W-22, 9, DOTTED); + lcdDrawRect(expo ? 18 : 22, y-1, expo ? LCD_W-18 : LCD_W-22, 9, DOTTED); cur++; y+=FH; } if (s_currIdx == i) { @@ -926,7 +926,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) if (s_copyMode) { if ((s_copyMode==COPY_MODE || s_copyTgtOfs == 0) && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { /* draw a border around the raw on selection mode (copy/move) */ - lcd_rect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? (LCD_W-EXPO_LINE_SELECT_POS) : (LCD_W-22), 9, s_copyMode == COPY_MODE ? SOLID : DOTTED); + lcdDrawRect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? (LCD_W-EXPO_LINE_SELECT_POS) : (LCD_W-22), 9, s_copyMode == COPY_MODE ? SOLID : DOTTED); } if (cur == sub) { /* invert the raw when it's the current one */ @@ -937,7 +937,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) cur++; y+=FH; mixCnt++; i++; if (expo) ed++; else md++; } while (expo ? (ichn+1 == ch && EXPO_VALID(ed)) : (isrcRaw && md->destCh+1 == ch)); if (s_copyMode == MOVE_MODE && cur-s_pgOfs >= 0 && cur-s_pgOfs < NUM_BODY_LINES && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { - lcd_rect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? LCD_W-EXPO_LINE_SELECT_POS : LCD_W-22, 9, DOTTED); + lcdDrawRect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? LCD_W-EXPO_LINE_SELECT_POS : LCD_W-22, 9, DOTTED); cur++; y+=FH; } } @@ -958,7 +958,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) putsChn(0, y, ch, attr); // show CHx } if (s_copyMode == MOVE_MODE && s_copySrcCh == ch) { - lcd_rect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? (LCD_W-EXPO_LINE_SELECT_POS) : (LCD_W-22), 9, DOTTED); + lcdDrawRect(expo ? EXPO_LINE_SELECT_POS : 22, y-1, expo ? (LCD_W-EXPO_LINE_SELECT_POS) : (LCD_W-22), 9, DOTTED); } } cur++; y+=FH; diff --git a/radio/src/gui/Taranis/menu_model_limits.cpp b/radio/src/gui/Taranis/menu_model_limits.cpp index d5a653660..f132b8ad0 100644 --- a/radio/src/gui/Taranis/menu_model_limits.cpp +++ b/radio/src/gui/Taranis/menu_model_limits.cpp @@ -136,7 +136,7 @@ void menuModelLimits(uint8_t event) s_warning_result = 0; LimitData *ld = limitAddress(sub); ld->revert = !ld->revert; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } for (int i=0; irevert; #if defined(PPM_CENTER_ADJUSTABLE) - lcd_putcAtt(LIMITS_REVERT_POS, y, revert ? 127 : 126, attr); + lcdDrawChar(LIMITS_REVERT_POS, y, revert ? 127 : 126, attr); #else lcd_putsiAtt(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); #endif @@ -269,7 +269,7 @@ void menuModelLimits(uint8_t event) #if defined(PPM_LIMITS_SYMETRICAL) case ITEM_LIMITS_SYMETRICAL: - lcd_putcAtt(LCD_W-FW-MENUS_SCROLLBAR_WIDTH, y, ld->symetrical ? '=' : '\306', attr); + lcdDrawChar(LCD_W-FW-MENUS_SCROLLBAR_WIDTH, y, ld->symetrical ? '=' : '\306', attr); if (active) { CHECK_INCDEC_MODELVAR_ZERO(event, ld->symetrical, 1); } diff --git a/radio/src/gui/Taranis/menu_model_logical_switches.cpp b/radio/src/gui/Taranis/menu_model_logical_switches.cpp index 420b341f1..996255ee2 100644 --- a/radio/src/gui/Taranis/menu_model_logical_switches.cpp +++ b/radio/src/gui/Taranis/menu_model_logical_switches.cpp @@ -84,11 +84,11 @@ void onLogicalSwitchesMenu(const char *result) } else if (result == STR_PASTE) { *cs = clipboard.data.csw; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (result == STR_CLEAR) { memset(cs, 0, sizeof(LogicalSwitchData)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } } @@ -258,7 +258,7 @@ void menuModelLogicalSwitches(uint8_t event) if (v1_val <= MIXSRC_LAST_CH) { cs->v2 = calcRESXto100(x); } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } break; case LS_FIELD_V3: diff --git a/radio/src/gui/Taranis/menu_model_select.cpp b/radio/src/gui/Taranis/menu_model_select.cpp index 0c3882a02..86091ee97 100644 --- a/radio/src/gui/Taranis/menu_model_select.cpp +++ b/radio/src/gui/Taranis/menu_model_select.cpp @@ -56,11 +56,11 @@ void onModelSelectMenu(const char *result) s_copySrcRow = -1; } else if (result == STR_BACKUP_MODEL) { - eeCheck(true); // force writing of current model data before this is changed + storageCheck(true); // force writing of current model data before this is changed POPUP_WARNING(eeBackupModel(sub)); } else if (result == STR_RESTORE_MODEL || result == STR_UPDATE_LIST) { - if (!listSdFiles(MODELS_PATH, MODELS_EXT, MENU_LINE_LENGTH-1, NULL)) { + if (!sdListFiles(MODELS_PATH, MODELS_EXT, MENU_LINE_LENGTH-1, NULL)) { POPUP_WARNING(STR_NO_MODELS_ON_SD); s_menu_flags = 0; } @@ -71,7 +71,7 @@ void onModelSelectMenu(const char *result) } else { // The user choosed a file on SD to restore - eeCheck(true); + storageCheck(true); POPUP_WARNING(eeRestoreModel(sub, (char *)result)); if (!s_warning && g_eeGeneral.currModel == sub) { eeLoadModel(sub); @@ -83,7 +83,7 @@ void menuModelSelect(uint8_t event) { if (s_warning_result) { s_warning_result = 0; - eeCheck(true); + storageCheck(true); eeDeleteModel(m_posVert); // delete file s_copyMode = 0; event = EVT_ENTRY_UP; @@ -145,7 +145,7 @@ void menuModelSelect(uint8_t event) } else if (s_copyMode && (s_copyTgtOfs || s_copySrcRow>=0)) { displayPopup(s_copyMode==COPY_MODE ? STR_COPYINGMODEL : STR_MOVINGMODEL); - eeCheck(true); // force writing of current model data before this is changed + storageCheck(true); // force writing of current model data before this is changed uint8_t cur = (MAX_MODELS + sub + s_copyTgtOfs) % MAX_MODELS; @@ -167,7 +167,7 @@ void menuModelSelect(uint8_t event) if (s_copySrcRow != g_eeGeneral.currModel) { g_eeGeneral.currModel = s_copySrcRow; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); } s_copyMode = 0; @@ -278,7 +278,7 @@ void menuModelSelect(uint8_t event) if (s_copyMode && (vertpos_t)sub==i+s_pgOfs) { drawFilledRect(9, y, MODELSEL_W-1-9, 7); - lcd_rect(8, y-1, MODELSEL_W-1-7, 9, s_copyMode == COPY_MODE ? SOLID : DOTTED); + lcdDrawRect(8, y-1, MODELSEL_W-1-7, 9, s_copyMode == COPY_MODE ? SOLID : DOTTED); } } diff --git a/radio/src/gui/Taranis/menu_model_setup.cpp b/radio/src/gui/Taranis/menu_model_setup.cpp index c2c541460..4db7ed90a 100644 --- a/radio/src/gui/Taranis/menu_model_setup.cpp +++ b/radio/src/gui/Taranis/menu_model_setup.cpp @@ -120,7 +120,7 @@ void copySelection(char *dst, const char *src, uint8_t size) void onModelSetupBitmapMenu(const char *result) { if (result == STR_UPDATE_LIST) { - if (!listSdFiles(BITMAPS_PATH, BITMAPS_EXT, sizeof(g_model.header.bitmap), NULL)) { + if (!sdListFiles(BITMAPS_PATH, BITMAPS_EXT, sizeof(g_model.header.bitmap), NULL)) { POPUP_WARNING(STR_NO_BITMAPS_ON_SD); s_menu_flags = 0; } @@ -129,7 +129,7 @@ void onModelSetupBitmapMenu(const char *result) // The user choosed a bmp file in the list copySelection(g_model.header.bitmap, result, sizeof(g_model.header.bitmap)); memcpy(modelHeaders[g_eeGeneral.currModel].bitmap, g_model.header.bitmap, sizeof(g_model.header.bitmap)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } } @@ -157,7 +157,7 @@ void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, uint8_t event) swsrc_t switchVal = checkIncDecMovedSwitch(val); if (val != switchVal) { timer->mode = switchVal + (TMRMODE_COUNT-1); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } } #endif @@ -304,7 +304,7 @@ void menuModelSetup(uint8_t event) lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VCSWFUNC, 0, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER) && READ_ONLY_UNLOCKED()) { s_editMode = 0; - if (listSdFiles(BITMAPS_PATH, BITMAPS_EXT, sizeof(g_model.header.bitmap), g_model.header.bitmap, LIST_NONE_SD_FILE)) { + if (sdListFiles(BITMAPS_PATH, BITMAPS_EXT, sizeof(g_model.header.bitmap), g_model.header.bitmap, LIST_NONE_SD_FILE)) { menuHandler = onModelSetupBitmapMenu; } else { @@ -402,7 +402,7 @@ void menuModelSetup(uint8_t event) for (uint8_t i=0; i0) { switch (m_posHorz) { @@ -878,7 +878,7 @@ void menuModelFailsafe(uint8_t event) event = 0; if (s_editMode) { g_model.moduleData[g_moduleIdx].failsafeChannels[m_posVert] = channelOutputs[m_posVert]; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); AUDIO_WARNING1(); SEND_FAILSAFE_NOW(g_moduleIdx); } @@ -890,7 +890,7 @@ void menuModelFailsafe(uint8_t event) failsafe = FAILSAFE_CHANNEL_NOPULSE; else failsafe = channelOutputs[m_posVert]; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); AUDIO_WARNING1(); SEND_FAILSAFE_NOW(g_moduleIdx); } @@ -981,13 +981,13 @@ void menuModelFailsafe(uint8_t event) } // Gauge - lcd_rect(x+COL_W-3-wbar-ofs, y, wbar+1, 6); + lcdDrawRect(x+COL_W-3-wbar-ofs, y, wbar+1, 6); unsigned int lenChannel = limit((uint8_t)1, uint8_t((abs(channelValue) * wbar/2 + lim/2) / lim), uint8_t(wbar/2)); unsigned int lenFailsafe = limit((uint8_t)1, uint8_t((abs(failsafeValue) * wbar/2 + lim/2) / lim), uint8_t(wbar/2)); coord_t xChannel = (channelValue>0) ? x+COL_W-ofs-3-wbar/2 : x+COL_W-ofs-2-wbar/2-lenChannel; coord_t xFailsafe = (failsafeValue>0) ? x+COL_W-ofs-3-wbar/2 : x+COL_W-ofs-2-wbar/2-lenFailsafe; - lcd_hlineStip(xChannel, y+1, lenChannel, DOTTED, 0); - lcd_hlineStip(xChannel, y+2, lenChannel, DOTTED, 0); + lcdDrawHorizontalLine(xChannel, y+1, lenChannel, DOTTED, 0); + lcdDrawHorizontalLine(xChannel, y+2, lenChannel, DOTTED, 0); lcd_hline(xFailsafe, y+3, lenFailsafe); lcd_hline(xFailsafe, y+4, lenFailsafe); diff --git a/radio/src/gui/Taranis/menu_model_telemetry.cpp b/radio/src/gui/Taranis/menu_model_telemetry.cpp index cf5de4cbd..51c7092cf 100644 --- a/radio/src/gui/Taranis/menu_model_telemetry.cpp +++ b/radio/src/gui/Taranis/menu_model_telemetry.cpp @@ -367,7 +367,7 @@ void menuModelSensor(uint8_t event) lcd_putsLeft(y, STR_RATIO); if (attr) sensor->custom.ratio = checkIncDec(event, sensor->custom.ratio, 0, 30000, EE_MODEL|NO_INCDEC_MARKS|INCDEC_REP10); if (sensor->custom.ratio == 0) - lcd_putcAtt(SENSOR_2ND_COLUMN, y, '-', attr); + lcdDrawChar(SENSOR_2ND_COLUMN, y, '-', attr); else lcd_outdezAtt(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); break; @@ -416,7 +416,7 @@ void menuModelSensor(uint8_t event) source = checkIncDec(event, source, -MAX_SENSORS, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); } if (source < 0) { - lcd_putcAtt(SENSOR_2ND_COLUMN, y, '-', attr); + lcdDrawChar(SENSOR_2ND_COLUMN, y, '-', attr); putsMixerSource(lcdNextPos, y, MIXSRC_FIRST_TELEM+3*(-1-source), attr); } else { @@ -478,7 +478,7 @@ void onSensorMenu(const char *result) TelemetryItem & sourceItem = telemetryItems[index]; TelemetryItem & newItem = telemetryItems[newIndex]; newItem = sourceItem; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else { POPUP_WARNING(STR_TELEMETRYFULL); @@ -494,7 +494,7 @@ void onTelemetryScriptFileSelectionMenu(const char *result) int screenIndex = TELEMETRY_CURRENT_SCREEN(sub); if (result == STR_UPDATE_LIST) { - if (!listSdFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), NULL)) { + if (!sdListFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), NULL)) { POPUP_WARNING(STR_NO_SCRIPTS_ON_SD); s_menu_flags = 0; } @@ -502,7 +502,7 @@ void onTelemetryScriptFileSelectionMenu(const char *result) else { // The user choosed a file in the list memcpy(g_model.frsky.screens[screenIndex].script.file, result, sizeof(g_model.frsky.screens[screenIndex].script.file)); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); LUA_LOAD_MODEL_SCRIPTS(); } } @@ -535,7 +535,7 @@ void menuModelTelemetry(uint8_t event) if (k>=ITEM_TELEMETRY_SENSOR1 && k val ? 1 : -1); } else { diff --git a/radio/src/gui/Taranis/popups.cpp b/radio/src/gui/Taranis/popups.cpp index 1ac52edb6..37cd02783 100644 --- a/radio/src/gui/Taranis/popups.cpp +++ b/radio/src/gui/Taranis/popups.cpp @@ -57,7 +57,7 @@ void (*menuHandler)(const char *result); void displayBox(const char *title) { drawFilledRect(10, 16, LCD_W-20, 40, SOLID, ERASE); - lcd_rect(10, 16, LCD_W-20, 40); + lcdDrawRect(10, 16, LCD_W-20, 40); lcd_putsn(WARNING_LINE_X, WARNING_LINE_Y, title, WARNING_LINE_LEN); // could be a place for a s_warning_info } @@ -131,7 +131,7 @@ const char * displayMenu(uint8_t event) uint8_t display_count = min(s_menu_count, MENU_MAX_DISPLAY_LINES); uint8_t y = (display_count >= 5 ? MENU_Y - FH - 1 : MENU_Y); drawFilledRect(MENU_X, y, MENU_W, display_count * (FH+1) + 2, SOLID, ERASE); - lcd_rect(MENU_X, y, MENU_W, display_count * (FH+1) + 2); + lcdDrawRect(MENU_X, y, MENU_W, display_count * (FH+1) + 2); for (uint8_t i=0; i= VIEW_COUNT) { g_eeGeneral.view = 0; @@ -604,7 +604,7 @@ void menuMainView(uint8_t event) drawFilledRect(x, y, 4, 8); } else { - lcd_rect(x, y, 4, 8); + lcdDrawRect(x, y, 4, 8); } } } @@ -613,7 +613,7 @@ void menuMainView(uint8_t event) if (s_gvar_timer > 0) { s_gvar_timer--; drawFilledRect(BITMAP_X, BITMAP_Y, 64, 32, SOLID, ERASE); - lcd_rect(BITMAP_X, BITMAP_Y, 64, 32); + lcdDrawRect(BITMAP_X, BITMAP_Y, 64, 32); putsStrIdx(BITMAP_X+FW, BITMAP_Y+FH-1, STR_GV, s_gvar_last+1); lcd_putsnAtt(BITMAP_X+4*FW+FW/2, BITMAP_Y+FH-1, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, ZCHAR); lcd_putsAtt(BITMAP_X+FW, BITMAP_Y+2*FH+3, PSTR("[\010]"), BOLD); diff --git a/radio/src/gui/Taranis/view_statistics.cpp b/radio/src/gui/Taranis/view_statistics.cpp index 575a45f0b..5aa740c22 100644 --- a/radio/src/gui/Taranis/view_statistics.cpp +++ b/radio/src/gui/Taranis/view_statistics.cpp @@ -49,7 +49,7 @@ void menuStatisticsView(uint8_t event) case EVT_KEY_LONG(KEY_MENU): g_eeGeneral.globalTimer = 0; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); sessionTimer = 0; break; @@ -125,7 +125,7 @@ void menuStatisticsDebug(uint8_t event) case EVT_KEY_LONG(KEY_ENTER): g_eeGeneral.mAhUsed = 0; g_eeGeneral.globalTimer = 0; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); sessionTimer = 0; killEvents(event); AUDIO_KEYPAD_UP(); diff --git a/radio/src/gui/Taranis/view_telemetry.cpp b/radio/src/gui/Taranis/view_telemetry.cpp index d35a04a91..eed7f0b6b 100644 --- a/radio/src/gui/Taranis/view_telemetry.cpp +++ b/radio/src/gui/Taranis/view_telemetry.cpp @@ -54,7 +54,7 @@ void displayRssiLine() lcd_hline(0, 55, 212, 0); // separator uint8_t rssi = min((uint8_t)99, TELEMETRY_RSSI()); lcd_putsn(0, STATUS_BAR_Y, STR_RX, 2); lcd_outdezNAtt(4*FW, STATUS_BAR_Y, rssi, LEADING0, 2); - lcd_rect(BAR_LEFT, 57, 78, 7); + lcdDrawRect(BAR_LEFT, 57, 78, 7); drawFilledRect(BAR_LEFT+1, 58, 19*rssi/25, 5, (rssi < getRssiAlarmValue(0)) ? DOTTED : SOLID); } else { @@ -94,7 +94,7 @@ void displayGaugesTelemetryScreen(FrSkyScreenData & screen) if (source && barMax > barMin) { int y = barHeight+6+i*(barHeight+6); putsMixerSource(0, y+barHeight-5, source, 0); - lcd_rect(BAR_LEFT, y, BAR_WIDTH+1, barHeight+2); + lcdDrawRect(BAR_LEFT, y, BAR_WIDTH+1, barHeight+2); getvalue_t value = getValue(source); putsChannel(BAR_LEFT+2+BAR_WIDTH, y+barHeight-5, source, LEFT); uint8_t thresholdX = 0; @@ -107,7 +107,7 @@ void displayGaugesTelemetryScreen(FrSkyScreenData & screen) } } if (thresholdX) { - lcd_vlineStip(BAR_LEFT+1+thresholdX, y-2, barHeight+3, DOTTED); + lcdDrawVerticalLine(BAR_LEFT+1+thresholdX, y-2, barHeight+3, DOTTED); lcd_hline(BAR_LEFT+thresholdX, y-2, 3); } } diff --git a/radio/src/gui/Taranis/widgets.cpp b/radio/src/gui/Taranis/widgets.cpp index 80a4603dc..65fc53e10 100644 --- a/radio/src/gui/Taranis/widgets.cpp +++ b/radio/src/gui/Taranis/widgets.cpp @@ -78,18 +78,18 @@ void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr) { lcd_outdezAtt(LCD_W, 0, count, attr); coord_t x = 1+LCD_W-FW*(count>9 ? 3 : 2); - lcd_putcAtt(x, 0, '/', attr); + lcdDrawChar(x, 0, '/', attr); lcd_outdezAtt(x, 0, index+1, attr); } void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { - lcd_vlineStip(x, y, h, DOTTED); + lcdDrawVerticalLine(x, y, h, DOTTED); coord_t yofs = (h * offset) / count; coord_t yhgt = (h * visible) / count; if (yhgt + yofs > h) yhgt = h - yofs; - lcd_vlineStip(x, y + yofs, yhgt, SOLID, FORCE); + lcdDrawVerticalLine(x, y + yofs, yhgt, SOLID, FORCE); } const pm_uchar MAINMENU_LBM[] PROGMEM = { @@ -102,14 +102,14 @@ void displayMenuBar(const MenuItem *menu, int index) drawFilledRect(0, 24, LCD_W, 7, SOLID, GREY_DEFAULT); lcd_bmp(1, 0, MAINMENU_LBM); lcd_putsAtt(0, 24, menu[index].name, INVERS); - lcd_rect(index*24, 0, 26, 24, SOLID, FORCE); - lcd_hlineStip(0, 31, LCD_W, SOLID, FORCE); + lcdDrawRect(index*24, 0, 26, 24, SOLID, FORCE); + lcdDrawHorizontalLine(0, 31, LCD_W, SOLID, FORCE); } void displayProgressBar(const char *label) { lcd_putsLeft(4*FH, label); - lcd_rect(3, 6*FH+4, 204, 7); + lcdDrawRect(3, 6*FH+4, 204, 7); lcdRefresh(); } @@ -126,7 +126,7 @@ void updateProgressBar(int num, int den) void drawGauge(coord_t x, coord_t y, coord_t w, coord_t h, int32_t val, int32_t max) { - lcd_rect(x, y, w+1, h); + lcdDrawRect(x, y, w+1, h); drawFilledRect(x+1, y+1, w-1, 4, SOLID, ERASE); coord_t len = limit((uint8_t)1, uint8_t((abs(val) * w/2 + max/2) / max), uint8_t(w/2)); coord_t x0 = (val>0) ? x+w/2 : x+1+w/2-len; @@ -188,7 +188,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode)*10 : delta); else value = (GV_IS_GV_VALUE(value, min, max) ? GET_GVAR(value, min, max, mixerCurrentFlightMode) : delta); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } if (GV_IS_GV_VALUE(value, min, max)) { @@ -208,7 +208,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m if (idx < 0) { value = (int16_t) GV_CALC_VALUE_IDX_NEG(idx, delta); idx = -idx; - lcd_putcAtt(x-6, y, '-', attr); + lcdDrawChar(x-6, y, '-', attr); } else { value = (int16_t) GV_CALC_VALUE_IDX_POS(idx-1, delta); diff --git a/radio/src/gui/gui.h b/radio/src/gui/gui.h index 0d9baa30c..a82ddff6c 100644 --- a/radio/src/gui/gui.h +++ b/radio/src/gui/gui.h @@ -37,6 +37,10 @@ #ifndef _GUI_H_ #define _GUI_H_ +#if defined(CPUARM) +#include "gui_helpers.h" +#endif + #if defined(PCBHORUS) #include "Horus/gui.h" #elif defined(PCBFLAMENCO) diff --git a/radio/src/gui/gui_helpers.cpp b/radio/src/gui/gui_helpers.cpp new file mode 100644 index 000000000..8402c5520 --- /dev/null +++ b/radio/src/gui/gui_helpers.cpp @@ -0,0 +1,436 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../opentx.h" + +int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable) +{ + do { + current += inc; + if (current < min) + current = max; + else if (current > max) + current = min; + if (!isValueAvailable || isValueAvailable(current)) + return current; + } while(1); + return 0; +} + +#if defined(VIRTUALINPUTS) +bool isInputAvailable(int input) +{ + for (int i=0; ichn == input) + return true; + } + return false; +} +#endif + +bool isChannelUsed(int channel) +{ + for (int i=0; isrcRaw == 0) return false; + if (md->destCh == channel) return true; + if (md->destCh > channel) return false; + } + return false; +} + +int getChannelsUsed() +{ + int result = 0; + int lastCh = -1; + for (int i=0; isrcRaw == 0) return result; + if (md->destCh != lastCh) { ++result; lastCh = md->destCh; } + } + return result; +} + +bool isSourceAvailable(int source) +{ +#if defined(VIRTUALINPUTS) + if (source>=MIXSRC_FIRST_INPUT && source<=MIXSRC_LAST_INPUT) { + return isInputAvailable(source - MIXSRC_FIRST_INPUT); + } +#endif + +#if defined(LUA_MODEL_SCRIPTS) + if (source>=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) { + div_t qr = div(source-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); + return (qr.rem=MIXSRC_FIRST_LUA && source<=MIXSRC_LAST_LUA) + return false; +#endif + + if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { + return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); + } + + if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) { + return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); + } + +#if !defined(HELI) + if (source>=MIXSRC_CYC1 && source<=MIXSRC_CYC3) + return false; +#endif + + if (source>=MIXSRC_FIRST_CH && source<=MIXSRC_LAST_CH) { + return isChannelUsed(source-MIXSRC_FIRST_CH); + } + + if (source>=MIXSRC_FIRST_LOGICAL_SWITCH && source<=MIXSRC_LAST_LOGICAL_SWITCH) { + LogicalSwitchData * cs = lswAddress(source-MIXSRC_FIRST_LOGICAL_SWITCH); + return (cs->func != LS_FUNC_NONE); + } + +#if !defined(GVARS) + if (source>=MIXSRC_GVAR1 && source<=MIXSRC_LAST_GVAR) + return false; +#endif + + if (source>=MIXSRC_RESERVE1 && source<=MIXSRC_RESERVE5) + return false; + + if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); + if (qr.rem == 0) + return isTelemetryFieldAvailable(qr.quot); + else + return isTelemetryFieldComparisonAvailable(qr.quot); + } + + return true; +} + +bool isSourceAvailableInGlobalFunctions(int source) +{ + if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + return false; + } + return isSourceAvailable(source); +} + +bool isSourceAvailableInCustomSwitches(int source) +{ + bool result = isSourceAvailable(source); + +#if defined(FRSKY) + if (result && source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); + result = isTelemetryFieldComparisonAvailable(qr.quot); + } +#endif + + return result; +} + +bool isInputSourceAvailable(int source) +{ + if (source>=MIXSRC_FIRST_POT && source<=MIXSRC_LAST_POT) { + return IS_POT_AVAILABLE(POT1+source-MIXSRC_FIRST_POT); + } + + if (source>=MIXSRC_Rud && source<=MIXSRC_MAX) + return true; + + if (source>=MIXSRC_FIRST_TRIM && source<=MIXSRC_LAST_TRIM) + return true; + + if (source>=MIXSRC_FIRST_SWITCH && source<=MIXSRC_LAST_SWITCH) + return SWITCH_EXISTS(source-MIXSRC_FIRST_SWITCH); + + if (source>=MIXSRC_FIRST_CH && source<=MIXSRC_LAST_CH) + return true; + + if (source>=MIXSRC_FIRST_TRAINER && source<=MIXSRC_LAST_TRAINER) + return true; + + if (source>=MIXSRC_FIRST_TELEM && source<=MIXSRC_LAST_TELEM) { + div_t qr = div(source-MIXSRC_FIRST_TELEM, 3); + return isTelemetryFieldAvailable(qr.quot) && isTelemetryFieldComparisonAvailable(qr.quot); + } + + return false; +} + +enum SwitchContext +{ + LogicalSwitchesContext, + ModelCustomFunctionsContext, + GeneralCustomFunctionsContext, + TimersContext, + MixesContext +}; + +bool isLogicalSwitchAvailable(int index) +{ + LogicalSwitchData * lsw = lswAddress(index); + return (lsw->func != LS_FUNC_NONE); +} + +bool isSwitchAvailable(int swtch, SwitchContext context) +{ + bool negative = false; + + if (swtch < 0) { + negative = true; + if (swtch == -SWSRC_ON || swtch == -SWSRC_ONE) { + return false; + } + swtch = -swtch; + } + +#if defined(PCBSKY9X) + if (swtch >= SWSRC_FIRST_SWITCH && swtch <= SWSRC_LAST_SWITCH) { + negative = negative; + return true; + } +#else + if (swtch >= SWSRC_FIRST_SWITCH && swtch <= SWSRC_LAST_SWITCH) { + div_t swinfo = switchInfo(swtch); + if (!SWITCH_EXISTS(swinfo.quot)) { + return false; + } + if (!IS_3POS(swinfo.quot)) { + if (negative) { + return false; + } + if (IS_3POS_MIDDLE(swinfo.rem)) { + return false; + } + } + return true; + } +#endif + +#if NUM_XPOTS > 0 + if (swtch >= SWSRC_FIRST_MULTIPOS_SWITCH && swtch <= SWSRC_LAST_MULTIPOS_SWITCH) { + int index = (swtch - SWSRC_FIRST_MULTIPOS_SWITCH) / XPOTS_MULTIPOS_COUNT; + if (IS_POT_MULTIPOS(POT1+index)) { + StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[POT1+index]; + return (calib->count >= ((swtch - SWSRC_FIRST_MULTIPOS_SWITCH) % XPOTS_MULTIPOS_COUNT)); + } + else { + return false; + } + } +#endif + + if (swtch >= SWSRC_FIRST_LOGICAL_SWITCH && swtch <= SWSRC_LAST_LOGICAL_SWITCH) { + if (context == GeneralCustomFunctionsContext) { + return false; + } + else if (context != LogicalSwitchesContext) { + return isLogicalSwitchAvailable(swtch - SWSRC_FIRST_LOGICAL_SWITCH); + } + } + + if (context != ModelCustomFunctionsContext && context != GeneralCustomFunctionsContext && (swtch == SWSRC_ON || swtch == SWSRC_ONE)) { + return false; + } + + if (swtch >= SWSRC_FIRST_FLIGHT_MODE && swtch <= SWSRC_LAST_FLIGHT_MODE) { + if (context == MixesContext || context == GeneralCustomFunctionsContext) { + return false; + } + else { + swtch -= SWSRC_FIRST_FLIGHT_MODE; + if (swtch == 0) { + return true; + } + FlightModeData * fm = flightModeAddress(swtch); + return (fm->swtch != SWSRC_NONE); + } + } + + if (swtch >= SWSRC_FIRST_SENSOR && swtch <= SWSRC_LAST_SENSOR) { + return isTelemetryFieldAvailable(swtch - SWSRC_FIRST_SENSOR); + } + + return true; +} + +bool isSwitchAvailableInLogicalSwitches(int swtch) +{ + return isSwitchAvailable(swtch, LogicalSwitchesContext); +} + +bool isSwitchAvailableInCustomFunctions(int swtch) +{ + if (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions) + return isSwitchAvailable(swtch, ModelCustomFunctionsContext); + else + return isSwitchAvailable(swtch, GeneralCustomFunctionsContext); +} + +bool isSwitchAvailableInMixes(int swtch) +{ + return isSwitchAvailable(swtch, MixesContext); +} + +bool isSwitchAvailableInTimers(int swtch) +{ + if (swtch >= 0) { + if (swtch < TMRMODE_COUNT) + return true; + else + swtch -= TMRMODE_COUNT-1; + } + else { + if (swtch > -TMRMODE_COUNT) + return false; + else + swtch += TMRMODE_COUNT-1; + } + + return isSwitchAvailable(swtch, TimersContext); +} + +bool isThrottleSourceAvailable(int source) +{ + if (source >= THROTTLE_SOURCE_FIRST_POT && source < THROTTLE_SOURCE_FIRST_POT+NUM_POTS && !IS_POT_AVAILABLE(POT1+source-THROTTLE_SOURCE_FIRST_POT)) + return false; + else + return true; +} + +bool isLogicalSwitchFunctionAvailable(int function) +{ + return function != LS_FUNC_RANGE; +} + +bool isAssignableFunctionAvailable(int function) +{ +#if defined(OVERRIDE_CHANNEL_FUNCTION) || defined(GVARS) + bool modelFunctions = (g_menuStack[g_menuStackPtr] == menuModelCustomFunctions); +#endif + + switch (function) { + case FUNC_OVERRIDE_CHANNEL: +#if defined(OVERRIDE_CHANNEL_FUNCTION) + return modelFunctions; +#else + return false; +#endif + case FUNC_ADJUST_GVAR: +#if defined(GVARS) + return modelFunctions; +#else + return false; +#endif +#if !defined(HAPTIC) + case FUNC_HAPTIC: +#endif + case FUNC_RESERVE4: +#if !defined(DANGEROUS_MODULE_FUNCTIONS) + case FUNC_RANGECHECK: + case FUNC_BIND: +#endif +#if !defined(LUA) + case FUNC_PLAY_SCRIPT: +#endif + case FUNC_RESERVE5: + return false; + + default: + return true; + } +} + +bool isSourceAvailableInGlobalResetSpecialFunction(int index) +{ + if (index >= FUNC_RESET_PARAM_FIRST_TELEM) + return false; + else + return isSourceAvailableInResetSpecialFunction(index); +} + +bool isSourceAvailableInResetSpecialFunction(int index) +{ + if (index >= FUNC_RESET_PARAM_FIRST_TELEM) { + TelemetrySensor & telemetrySensor = g_model.telemetrySensors[index-FUNC_RESET_PARAM_FIRST_TELEM]; + return telemetrySensor.isAvailable(); + } +#if TIMERS < 3 + else if (index == FUNC_RESET_TIMER3) { + return false; + } +#endif +#if TIMERS < 2 + else if (index == FUNC_RESET_TIMER2) { + return false; + } +#endif + else { + return true; + } +} + +bool isModuleAvailable(int module) +{ + return true; +} + +bool modelHasNotes() +{ + char filename[sizeof(MODELS_PATH)+1+sizeof(g_model.header.name)+sizeof(TEXT_EXT)] = MODELS_PATH "/"; + char *buf = strcat_currentmodelname(&filename[sizeof(MODELS_PATH)]); + strcpy(buf, TEXT_EXT); + return isFileAvailable(filename); +} + +int getFirstAvailable(int min, int max, IsValueAvailable isValueAvailable) +{ + int retval = 0; + for (int i = min; i <= max; i++) { + if (isValueAvailable(i)) { + retval = i; + break; + } + } + return retval; +} diff --git a/radio/src/gui/gui_helpers.h b/radio/src/gui/gui_helpers.h new file mode 100644 index 000000000..5a6183005 --- /dev/null +++ b/radio/src/gui/gui_helpers.h @@ -0,0 +1,64 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#ifndef _GUI_HELPERS_H_ +#define _GUI_HELPERS_H_ + +typedef bool (*IsValueAvailable)(int); + +int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable); +int getFirstAvailable(int min, int max, IsValueAvailable isValueAvailable); + +#if defined(VIRTUALINPUTS) +bool isInputAvailable(int input); +#endif +bool isInputSourceAvailable(int source); +bool isThrottleSourceAvailable(int source); +bool isLogicalSwitchFunctionAvailable(int function); +bool isLogicalSwitchAvailable(int index); +bool isAssignableFunctionAvailable(int function); +bool isSourceAvailable(int source); +bool isSourceAvailableInGlobalFunctions(int source); +bool isSourceAvailableInCustomSwitches(int source); +bool isSourceAvailableInResetSpecialFunction(int index); +bool isSourceAvailableInGlobalResetSpecialFunction(int index); +bool isSwitchAvailableInLogicalSwitches(int swtch); +bool isSwitchAvailableInCustomFunctions(int swtch); +bool isSwitchAvailableInMixes(int swtch); +bool isSwitchAvailableInTimers(int swtch); +bool isModuleAvailable(int module); + +#endif // _GUI_HELPERS_H_ diff --git a/radio/src/loadboot.cpp b/radio/src/loadboot.cpp index affa37fd6..7acffce10 100644 --- a/radio/src/loadboot.cpp +++ b/radio/src/loadboot.cpp @@ -52,7 +52,7 @@ #if defined(PCBSKY9X) #include "AT91SAM3S4.h" #else - #include "board_taranis.h" + #include "targets/Taranis/board_taranis.h" #endif #if defined(PCBTARANIS) diff --git a/radio/src/logs.cpp b/radio/src/logs.cpp index 075350856..9971c4132 100644 --- a/radio/src/logs.cpp +++ b/radio/src/logs.cpp @@ -53,7 +53,6 @@ const pm_char * openLogs() { // Determine and set log file filename FRESULT result; - DIR folder; char filename[34]; // /LOGS/modelnamexxx-2013-01-01.log if (!sdMounted()) @@ -64,12 +63,9 @@ const pm_char * openLogs() // check and create folder here strcpy_P(filename, STR_LOGS_PATH); - result = f_opendir(&folder, filename); - if (result != FR_OK) { - if (result == FR_NO_PATH) - result = f_mkdir(filename); - if (result != FR_OK) - return SDCARD_ERROR(result); + const char * error = sdCheckAndCreateDirectory(filename); + if (error) { + return error; } filename[sizeof(LOGS_PATH)-1] = '/'; diff --git a/radio/src/lua_api.cpp b/radio/src/lua_api.cpp index 7cd53d466..b01e3fa0f 100644 --- a/radio/src/lua_api.cpp +++ b/radio/src/lua_api.cpp @@ -439,7 +439,7 @@ static int luaLcdDrawLine(lua_State *L) int y2 = luaL_checkinteger(L, 4); int pat = luaL_checkinteger(L, 5); int flags = luaL_checkinteger(L, 6); - lcd_line(x1, y1, x2, y2, pat, flags); + lcdDrawLine(x1, y1, x2, y2, pat, flags); return 0; } @@ -562,7 +562,7 @@ static int luaLcdDrawRectangle(lua_State *L) int w = luaL_checkinteger(L, 3); int h = luaL_checkinteger(L, 4); unsigned int flags = luaL_optunsigned(L, 5, 0); - lcd_rect(x, y, w, h, 0xff, flags); + lcdDrawRect(x, y, w, h, 0xff, flags); return 0; } @@ -588,7 +588,7 @@ static int luaLcdDrawGauge(lua_State *L) int num = luaL_checkinteger(L, 5); int den = luaL_checkinteger(L, 6); // int flags = luaL_checkinteger(L, 7); - lcd_rect(x, y, w, h); + lcdDrawRect(x, y, w, h); uint8_t len = limit((uint8_t)1, uint8_t(w*num/den), uint8_t(w)); for (int i=1; iduration = luaL_checkinteger(L, -1); } } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } return 0; @@ -1238,7 +1238,7 @@ static int luaModelSetCustomFunction(lua_State *L) CFN_ACTIVE(cfn) = luaL_checkinteger(L, -1); } } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } return 0; @@ -1306,7 +1306,7 @@ static int luaModelSetOutput(lua_State *L) limit->curve = luaL_checkinteger(L, -1) + 1; } } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } return 0; @@ -1330,7 +1330,7 @@ static int luaModelSetGlobalVariable(lua_State *L) int value = luaL_checkinteger(L, 3); if (phase < MAX_FLIGHT_MODES && idx < MAX_GVARS && value >= -GVAR_MAX && value <= GVAR_MAX) { g_model.flightModeData[phase].gvars[idx] = value; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } return 0; } @@ -1996,7 +1996,7 @@ void luaDoOneRunStandalone(uint8_t evt) lcd_outdezAtt(lcdLastPos, 7*FH, luaGetMemUsed(), LEFT); lcd_putc(lcdLastPos, 7*FH, 'b'); lcd_hline(0, 7*FH-2, lcdLastPos+6, FORCE); - lcd_vlineStip(lcdLastPos+6, 7*FH-2, FH+2, SOLID, FORCE); + lcdDrawVerticalLine(lcdLastPos+6, 7*FH-2, FH+2, SOLID, FORCE); } } } diff --git a/radio/src/main_arm.cpp b/radio/src/main_arm.cpp index 82f5a0276..443ee65e8 100644 --- a/radio/src/main_arm.cpp +++ b/radio/src/main_arm.cpp @@ -96,9 +96,15 @@ void checkEeprom() if (eepromIsWriting()) eepromWriteProcess(); else if (TIME_TO_WRITE()) - eeCheck(false); + storageCheck(false); } } +#else +void checkEeprom() +{ + if (TIME_TO_WRITE()) + storageCheck(false); +} #endif #if defined(PCBFLAMENCO) @@ -247,9 +253,7 @@ void perMain() calcConsumption(); #endif checkSpeakerVolume(); -#if defined(EEPROM) checkEeprom(); -#endif sdMountPoll(); writeLogs(); handleUsbConnection(); diff --git a/radio/src/main_avr.cpp b/radio/src/main_avr.cpp index 2d2daa8ad..367b6d928 100644 --- a/radio/src/main_avr.cpp +++ b/radio/src/main_avr.cpp @@ -102,7 +102,7 @@ void perMain() if (theFile.isWriting()) theFile.nextWriteStep(); else if (TIME_TO_WRITE()) - eeCheck(false); + storageCheck(false); } #if defined(SDCARD) diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 5ecf15fbf..346a5f2e3 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -47,7 +47,7 @@ #define LUA_EXPORT(...) #define LUA_EXPORT_MULTIPLE(...) #define LUA_EXPORT_EXTRA(...) -#endif +#endif #define WARN_THR_BIT 0x01 #define WARN_BEP_BIT 0x80 @@ -446,7 +446,8 @@ enum UartModes { uint32_t switchConfig; \ uint8_t potsType; /*two bits for every pot*/\ char switchNames[NUM_SWITCHES][LEN_SWITCH_NAME]; \ - char anaNames[NUM_STICKS+NUM_POTS][LEN_ANA_NAME]; + char anaNames[NUM_STICKS+NUM_POTS][LEN_ANA_NAME]; \ + char currModelFilename[10]; #elif defined(PCBFLAMENCO) #define LEN_SWITCH_NAME 3 #define LEN_ANA_NAME 3 @@ -846,6 +847,9 @@ PACK(typedef struct { #define IS_TOGGLE(x) (SWITCH_CONFIG(x) == SWITCH_TOGGLE) #define IS_3POS_MIDDLE(x) ((x) == 1) #define SWITCH_WARNING_ALLOWED(x) (SWITCH_EXISTS(x) && !IS_TOGGLE(x)) +#else + #define switchInfo(x) ((x) >= 3 ? (x)-2 : 0) + #define SWITCH_EXISTS(x) true #endif #define ALTERNATE_VIEW 0x10 diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 2e0a493d2..80a53dca6 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -367,22 +367,22 @@ void defaultInputs() g_model.inputNames[i][3] = '\0'; #endif } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } #endif #if defined(TEMPLATES) inline void applyDefaultTemplate() { - applyTemplate(TMPL_SIMPLE_4CH); // calls eeDirty internally + applyTemplate(TMPL_SIMPLE_4CH); // calls storageDirty internally } #else void applyDefaultTemplate() { #if defined(VIRTUALINPUTS) - defaultInputs(); // calls eeDirty internally + defaultInputs(); // calls storageDirty internally #else - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); #endif for (int i=0; itrim[idx] = trim; #endif - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } #endif @@ -661,7 +661,7 @@ void incRotaryEncoder(uint8_t idx, int8_t inc) g_rotenc[idx] += inc; int16_t *value = &(flightModeAddress(getRotaryEncoderFlightPhase(idx))->rotaryEncoders[idx]); *value = limit((int16_t)-1024, (int16_t)(*value + (inc * 8)), (int16_t)+1024); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } #endif @@ -669,11 +669,11 @@ void incRotaryEncoder(uint8_t idx, int8_t inc) #if defined(PCBSTD) #define SET_GVAR_VALUE(idx, phase, value) \ - (GVAR_VALUE(idx, phase) = value, eeDirty(EE_MODEL)) + (GVAR_VALUE(idx, phase) = value, storageDirty(EE_MODEL)) #else #define SET_GVAR_VALUE(idx, phase, value) \ GVAR_VALUE(idx, phase) = value; \ - eeDirty(EE_MODEL); \ + storageDirty(EE_MODEL); \ if (g_model.gvars[idx].popup) { \ s_gvar_last = idx; \ s_gvar_timer = GVAR_DISPLAY_TIME; \ @@ -1121,7 +1121,9 @@ void checkAll() #if defined(MODULE_ALWAYS_SEND_PULSES) startupWarningState = STARTUP_WARNING_THROTTLE; #else - checkTHR(); + if (g_eeGeneral.chkSum == evalChkSum()) { + checkTHR(); + } checkSwitches(); checkFailsafe(); #endif @@ -1853,10 +1855,6 @@ void opentxStart() } #endif -#if defined(CPUARM) - eeLoadModel(g_eeGeneral.currModel); -#endif - #if defined(GUI) checkAlarm(); checkAll(); @@ -1867,7 +1865,6 @@ void opentxStart() chainMenu(menuFirstCalib); } #endif - } #if defined(CPUARM) || defined(CPUM2560) @@ -1899,11 +1896,11 @@ void opentxClose() if (sensor.type == TELEM_TYPE_CALCULATED) { if (sensor.persistent && sensor.persistentValue != telemetryItems[i].value) { sensor.persistentValue = telemetryItems[i].value; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } else if (!sensor.persistent) { sensor.persistentValue = 0; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } } } @@ -1923,20 +1920,20 @@ void opentxClose() SAVE_POT_POSITION(i); } } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } #endif #if !defined(PCBTARANIS) - if (s_eeDirtyMsk & EE_MODEL) { + if (s_storageDirtyMsk & EE_MODEL) { displayPopup(STR_SAVEMODEL); } #endif g_eeGeneral.unexpectedShutdown = 0; - eeDirty(EE_GENERAL); - eeCheck(true); + storageDirty(EE_GENERAL); + storageCheck(true); #if defined(CPUARM) while (IS_PLAYING(ID_PLAY_BYE)) { @@ -2265,7 +2262,7 @@ void instantTrim() } } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); AUDIO_WARNING2(); } @@ -2289,7 +2286,7 @@ void copySticksToOffset(uint8_t ch) #endif ld->offset = (ld->revert ? -zero : zero); resumeMixerCalculations(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } void copyTrimsToOffset(uint8_t ch) @@ -2314,7 +2311,7 @@ void copyTrimsToOffset(uint8_t ch) g_model.limitData[ch].offset = limit((int16_t)-1000, (int16_t)v, (int16_t)1000); // make sure the offset doesn't go haywire resumeMixerCalculations(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } void moveTrimsToOffsets() // copy state of 3 primary to subtrim @@ -2362,7 +2359,7 @@ void moveTrimsToOffsets() // copy state of 3 primary to subtrim resumeMixerCalculations(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); AUDIO_WARNING2(); } @@ -2411,9 +2408,7 @@ void opentxInit(OPENTX_INIT_ARGS) { TRACE("opentxInit()"); -#if defined(EEPROM) - eeReadAll(); -#endif + storageReadAll(); #if defined(CPUARM) if (UNEXPECTED_SHUTDOWN()) { @@ -2464,10 +2459,6 @@ void opentxInit(OPENTX_INIT_ARGS) #if !defined(CPUARM) // is done above on ARM unexpectedShutdown = 1; -#endif -#if defined(CPUARM) - TRACE("eeLoadModel(g_eeGeneral.currModel)"); - eeLoadModel(g_eeGeneral.currModel); #endif } else { @@ -2478,7 +2469,7 @@ void opentxInit(OPENTX_INIT_ARGS) #if defined(CPUARM) || defined(CPUM2560) if (!g_eeGeneral.unexpectedShutdown) { g_eeGeneral.unexpectedShutdown = 1; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); } #endif @@ -2554,7 +2545,7 @@ int main(void) displaySplash(); #endif - sei(); // interrupts needed for telemetryInit and eeReadAll. + sei(); // interrupts needed now #if defined(FRSKY) && !defined(DSM2_SERIAL) telemetryInit(); diff --git a/radio/src/opentx.h b/radio/src/opentx.h index 958499164..3495eb65b 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -288,15 +288,15 @@ #elif defined(PCBFLAMENCO) #include "targets/Flamenco/board_flamenco.h" #elif defined(PCBTARANIS) - #include "targets/taranis/board_taranis.h" + #include "targets/Taranis/board_taranis.h" #elif defined(PCBSKY9X) - #include "targets/sky9x/board_sky9x.h" + #include "targets/Sky9x/board_sky9x.h" #elif defined(PCBGRUVIN9X) - #include "targets/gruvin9x/board_gruvin9x.h" + #include "targets/Gruvin9x/board_gruvin9x.h" #elif defined(PCBMEGA2560) - #include "targets/mega2560/board_mega2560.h" + #include "targets/Mega2560/board_mega2560.h" #else - #include "targets/stock/board_stock.h" + #include "targets/9x/board_stock.h" #endif #include "debug.h" @@ -469,14 +469,8 @@ #include "cli.h" #endif -#include "eeprom_common.h" - -#if defined(EEPROM_RLC) - #include "eeprom_rlc.h" -#else - #include "eeprom_raw.h" -#endif - +#include "timers.h" +#include "storage/storage.h" #include "pulses/pulses.h" #if defined(PCBTARANIS) @@ -1530,11 +1524,11 @@ enum AUDIO_SOUNDS { #include "buzzer.h" #if defined(PCBSTD) && defined(VOICE) -#include "targets/stock/voice.h" +#include "targets/9x/voice.h" #endif #if defined(PCBGRUVIN9X) && defined(VOICE) -#include "targets/gruvin9x/somo14d.h" +#include "targets/Gruvin9x/somo14d.h" #endif #include "translations.h" @@ -1630,8 +1624,13 @@ void convertUnit(getvalue_t & val, uint8_t & unit); // TODO check FORCEINLINE on uint8_t zlen(const char *str, uint8_t size); bool zexist(const char *str, uint8_t size); char * strcat_zchar(char *dest, const char *name, uint8_t size, const char *defaultName=NULL, uint8_t defaultNameSize=0, uint8_t defaultIdx=0); -#define strcat_modelname(dest, idx) strcat_zchar(dest, modelHeaders[idx].name, LEN_MODEL_NAME, STR_MODEL, PSIZE(TR_MODEL), idx+1) #define strcat_phasename(dest, idx) strcat_zchar(dest, g_model.flightModeData[idx].name, LEN_FLIGHT_MODE_NAME, STR_FP, PSIZE(TR_FP), idx+1) +#if defined(EEPROM) +#define strcat_modelname(dest, idx) strcat_zchar(dest, modelHeaders[idx].name, LEN_MODEL_NAME, STR_MODEL, PSIZE(TR_MODEL), idx+1) +#define strcat_currentmodelname(dest) strcat_modelname(dest, g_eeGeneral.currModel) +#else +#define strcat_currentmodelname(dest) strcat_zchar(dest, g_model.header.name, LEN_MODEL_NAME) +#endif #define ZLEN(s) zlen(s, sizeof(s)) #define ZEXIST(s) zexist(s, sizeof(s)) #endif diff --git a/radio/src/sdcard.cpp b/radio/src/sdcard.cpp index b67dabbaa..6628415f7 100644 --- a/radio/src/sdcard.cpp +++ b/radio/src/sdcard.cpp @@ -40,7 +40,25 @@ #define LIST_NONE_SD_FILE 1 -bool listSdFiles(const char *path, const char *extension, const uint8_t maxlen, const char *selection, uint8_t flags=0) +const char * sdCheckAndCreateDirectory(const char * path) +{ + DIR archiveFolder; + + FRESULT result = f_opendir(&archiveFolder, path); + if (result != FR_OK) { + if (result == FR_NO_PATH) + result = f_mkdir(path); + if (result != FR_OK) + return SDCARD_ERROR(result); + } + else { + f_closedir(&archiveFolder); + } + + return NULL; +} + +bool sdListFiles(const char *path, const char *extension, const uint8_t maxlen, const char *selection, uint8_t flags=0) { FILINFO fno; DIR dir; @@ -189,7 +207,7 @@ bool listSdFiles(const char *path, const char *extension, const uint8_t maxlen, } #if defined(CPUARM) && defined(SDCARD) -const char *fileCopy(const char *filename, const char *srcDir, const char *destDir) +const char * sdCopyFile(const char * filename, const char * srcDir, const char * destDir) { FIL srcFile; FIL dstFile; diff --git a/radio/src/sdcard.h b/radio/src/sdcard.h index 5f269d7ec..05f959e90 100644 --- a/radio/src/sdcard.h +++ b/radio/src/sdcard.h @@ -41,6 +41,7 @@ #define ROOT_PATH "/" #define MODELS_PATH ROOT_PATH "MODELS" // no trailing slash = important +#define RADIO_PATH ROOT_PATH "RADIO" // no trailing slash = important #define LOGS_PATH ROOT_PATH "LOGS" #define SCREENSHOTS_PATH ROOT_PATH "SCREENSHOTS" #define SOUNDS_PATH ROOT_PATH "SOUNDS/en" @@ -79,9 +80,10 @@ void writeLogs(); uint32_t sdGetNoSectors(); uint32_t sdGetSize(); uint32_t sdGetFreeSectors(); +const char * sdCheckAndCreateDirectory(const char * path); #if !defined(BOOT) -inline const pm_char *SDCARD_ERROR(FRESULT result) +inline const pm_char * SDCARD_ERROR(FRESULT result) { if (result == FR_NOT_READY) return STR_NO_SDCARD; @@ -98,7 +100,7 @@ inline const pm_char *SDCARD_ERROR(FRESULT result) #define O9X_FOURCC 0x3178396F // o9x for gruvin9x/MEGA2560 #endif -const char *fileCopy(const char *filename, const char *srcDir, const char *destDir); +const char *sdCopyFile(const char *filename, const char *srcDir, const char *destDir); #endif diff --git a/radio/src/eeprom_common.cpp b/radio/src/storage/eeprom_common.cpp similarity index 68% rename from radio/src/eeprom_common.cpp rename to radio/src/storage/eeprom_common.cpp index bb5049dcf..d9dc71774 100644 --- a/radio/src/eeprom_common.cpp +++ b/radio/src/storage/eeprom_common.cpp @@ -40,13 +40,35 @@ #include "opentx.h" #include "timers.h" -uint8_t s_eeDirtyMsk; -tmr10ms_t s_eeDirtyTime10ms; - -void eeDirty(uint8_t msk) +void eeLoadModel(uint8_t index) { - s_eeDirtyMsk |= msk; - s_eeDirtyTime10ms = get_tmr10ms() ; + if (index < MAX_MODELS) { + + preModelLoad(); + + uint16_t size = eeLoadModelData(index); + +#if defined(SIMU) && defined(EEPROM_ZONE_SIZE) + if (sizeof(uint16_t) + sizeof(g_model) > EEPROM_ZONE_SIZE) { + TRACE("Model data size can't exceed %d bytes (%d bytes)", int(EEPROM_ZONE_SIZE-sizeof(uint16_t)), (int)sizeof(g_model)); + } +#endif + +#if defined(SIMU) + if (size > 0 && size != sizeof(g_model)) { + TRACE("Model data read=%d bytes vs %d bytes\n", size, (int)sizeof(ModelData)); + } +#endif + + bool newModel = false; + if (size < EEPROM_MIN_MODEL_SIZE) { // if not loaded a fair amount + modelDefault(index) ; + storageCheck(true); + newModel = true; + } + + postModelLoad(newModel); + } } uint8_t eeFindEmptyModel(uint8_t id, bool down) @@ -66,9 +88,9 @@ void selectModel(uint8_t sub) displayPopup(STR_LOADINGMODEL); #endif saveTimers(); - eeCheck(true); // force writing of current model data before this is changed + storageCheck(true); // force writing of current model data before this is changed g_eeGeneral.currModel = sub; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); eeLoadModel(sub); } @@ -82,10 +104,10 @@ void eeLoadModelHeaders() } #endif -void eeReadAll() +void storageReadAll() { if (!eepromOpen() || !eeLoadGeneral()) { - eeErase(true); + storageEraseAll(true); } else { eeLoadModelHeaders(); @@ -102,7 +124,5 @@ void eeReadAll() } #endif -#if !defined(CPUARM) eeLoadModel(g_eeGeneral.currModel); -#endif } diff --git a/radio/src/eeprom_common.h b/radio/src/storage/eeprom_common.h similarity index 70% rename from radio/src/eeprom_common.h rename to radio/src/storage/eeprom_common.h index a54e6a9c8..81690d4a4 100644 --- a/radio/src/eeprom_common.h +++ b/radio/src/storage/eeprom_common.h @@ -34,30 +34,14 @@ * */ -#if defined(SIMU) - #define WRITE_DELAY_10MS 200 -#elif defined(PCBTARANIS) - #define WRITE_DELAY_10MS 500 -#elif defined(PCBSKY9X) && !defined(REV0) - #define WRITE_DELAY_10MS 500 -#elif defined(PCBGRUVIN9X) && !defined(REV0) - #define WRITE_DELAY_10MS 500 -#else - #define WRITE_DELAY_10MS 200 -#endif +#define EEPROM_MIN_MODEL_SIZE 256 -#define TIME_TO_WRITE() (s_eeDirtyMsk && (tmr10ms_t)(get_tmr10ms() - s_eeDirtyTime10ms) >= (tmr10ms_t)WRITE_DELAY_10MS) +uint16_t eeLoadModelData(uint8_t id); +uint16_t eeLoadGeneralSettingsData(); -extern uint8_t s_eeDirtyMsk; -extern tmr10ms_t s_eeDirtyTime10ms; - -void eeDirty(uint8_t msk); -void eeCheck(bool immediately); -void eeReadAll(); bool eeModelExists(uint8_t id); void eeLoadModel(uint8_t id); bool eeConvert(); -void eeErase(bool warn); void ConvertModel(int id, int version); uint8_t eeFindEmptyModel(uint8_t id, bool down); void selectModel(uint8_t sub); diff --git a/radio/src/eeprom_conversions.cpp b/radio/src/storage/eeprom_conversions.cpp similarity index 96% rename from radio/src/eeprom_conversions.cpp rename to radio/src/storage/eeprom_conversions.cpp index 5d7fafbc6..85a9232b5 100644 --- a/radio/src/eeprom_conversions.cpp +++ b/radio/src/storage/eeprom_conversions.cpp @@ -1362,7 +1362,7 @@ void ConvertModel_217_to_218(ModelData & model) void ConvertModel(int id, int version) { - loadModel(id); + eeLoadModelData(id); if (version == 216) { version = 217; @@ -1375,8 +1375,8 @@ void ConvertModel(int id, int version) uint8_t currModel = g_eeGeneral.currModel; g_eeGeneral.currModel = id; - s_eeDirtyMsk = EE_MODEL; - eeCheck(true); + s_storageDirtyMsk = EE_MODEL; + storageCheck(true); g_eeGeneral.currModel = currModel; } @@ -1407,7 +1407,7 @@ bool eeConvert() MESSAGE(STR_EEPROMWARN, STR_EEPROM_CONVERTING, NULL, AU_EEPROM_FORMATTING); // TODO translations // General Settings conversion - loadGeneralSettings(); + eeLoadGeneralSettingsData(); int version = conversionVersionStart; if (version == 215) { version = 216; @@ -1421,14 +1421,14 @@ bool eeConvert() version = 218; ConvertGeneralSettings_217_to_218(g_eeGeneral); } - s_eeDirtyMsk = EE_GENERAL; - eeCheck(true); + s_storageDirtyMsk = EE_GENERAL; + storageCheck(true); #if defined(COLORLCD) #elif LCD_W >= 212 - lcd_rect(60, 6*FH+4, 132, 3); + lcdDrawRect(60, 6*FH+4, 132, 3); #else - lcd_rect(10, 6*FH+4, 102, 3); + lcdDrawRect(10, 6*FH+4, 102, 3); #endif // Models conversion diff --git a/radio/src/eeprom_raw.cpp b/radio/src/storage/eeprom_raw.cpp similarity index 86% rename from radio/src/eeprom_raw.cpp rename to radio/src/storage/eeprom_raw.cpp index cbb792218..03f664507 100644 --- a/radio/src/eeprom_raw.cpp +++ b/radio/src/storage/eeprom_raw.cpp @@ -238,7 +238,7 @@ void writeFile(int index, uint8_t * data, uint32_t size) void eeDeleteModel(uint8_t index) { - eeCheck(true); + storageCheck(true); memclear(&modelHeaders[index], sizeof(ModelHeader)); writeFile(index+1, (uint8_t *)&g_model, 0); eepromWriteWait(); @@ -246,7 +246,7 @@ void eeDeleteModel(uint8_t index) bool eeCopyModel(uint8_t dst, uint8_t src) { - eeCheck(true); + storageCheck(true); uint32_t eepromWriteSourceAddr = eepromHeader.files[src+1].zoneIndex * EEPROM_ZONE_SIZE; uint32_t eepromWriteDestinationAddr = eepromHeader.files[dst+1].zoneIndex * EEPROM_ZONE_SIZE; @@ -274,7 +274,7 @@ bool eeCopyModel(uint8_t dst, uint8_t src) void eeSwapModels(uint8_t id1, uint8_t id2) { - eeCheck(true); + storageCheck(true); { EepromHeaderFile tmp = eepromHeader.files[id1+1]; eepromHeader.files[id1+1] = eepromHeader.files[id2+1]; @@ -292,12 +292,12 @@ void eeSwapModels(uint8_t id1, uint8_t id2) } // For conversions ... -uint32_t loadGeneralSettings() +uint16_t eeLoadGeneralSettingsData() { return readFile(0, (uint8_t *)&g_eeGeneral, sizeof(g_eeGeneral)); } -uint32_t loadModel(uint32_t index) +uint16_t eeLoadModelData(uint8_t index) { return readFile(index+1, (uint8_t *)&g_model, sizeof(g_model)); } @@ -314,7 +314,7 @@ void writeModel(int index) bool eeLoadGeneral() { - loadGeneralSettings(); + eeLoadGeneralSettingsData(); if (g_eeGeneral.version != EEPROM_VER) { TRACE("EEPROM version %d instead of %d", g_eeGeneral.version, EEPROM_VER); @@ -329,75 +329,6 @@ bool eeLoadGeneral() return true; } -void eeLoadModel(uint8_t id) -{ - if (id EEPROM_ZONE_SIZE) - TRACE("Model data size can't exceed %d bytes (%d bytes)", int(EEPROM_ZONE_SIZE-sizeof(uint16_t)), (int)sizeof(g_model)); - if (size > 0 && size != sizeof(g_model)) - TRACE("Model data read=%d bytes vs %d bytes\n", size, (int)sizeof(ModelData)); -#endif - - if (size < EEPROM_BUFFER_SIZE) { // if not loaded a fair amount - modelDefault(id) ; - eeCheck(true); - } - - AUDIO_FLUSH(); - flightReset(); - logicalSwitchesReset(); - - if (pulsesStarted()) { - checkAll(); - resumePulses(); - } - - customFunctionsReset(); - - restoreTimers(); - -#if defined(CPUARM) - for (int i=0; i 0 && sz != sizeof(g_model)) { - printf("Model data read=%d bytes vs %d bytes\n", sz, (int)sizeof(ModelData)); - } -#endif - - bool newModel = false; - - if (sz < 256) { - modelDefault(id); - eeCheck(true); - newModel = true; - } - - AUDIO_FLUSH(); - flightReset(); - logicalSwitchesReset(); - - if (pulsesStarted()) { -#if defined(GUI) - if (!newModel) { - checkAll(); - } -#endif - resumePulses(); - } - - customFunctionsReset(); - - restoreTimers(); - -#if defined(CPUARM) - for (int i=0; i +#ifndef _EEPROM_RLC_H_ +#define _EEPROM_RLC_H_ // TODO duplicated #ifndef PACK @@ -116,7 +114,6 @@ extern EeFs eeFs; #define BLOCKS_OFFSET (RESV-BS) #endif -void eepromFormat(); uint16_t EeFsGetFree(); #if !defined(CPUARM) diff --git a/radio/src/storage/sdcard_raw.cpp b/radio/src/storage/sdcard_raw.cpp new file mode 100644 index 000000000..062ccc7b5 --- /dev/null +++ b/radio/src/storage/sdcard_raw.cpp @@ -0,0 +1,215 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../opentx.h" + +const char * writeFile(const char * filename, const uint8_t * data, uint16_t size) +{ + FIL file; + char buf[8]; + UINT written; + + FRESULT result = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + + *(uint32_t*)&buf[0] = O9X_FOURCC; + buf[4] = EEPROM_VER; + buf[5] = 'M'; + *(uint16_t*)&buf[6] = size; + + result = f_write(&file, buf, 8, &written); + if (result != FR_OK || written != 8) { + f_close(&file); + return SDCARD_ERROR(result); + } + + result = f_write(&file, data, size, &written); + if (result != FR_OK || written != size) { + f_close(&file); + return SDCARD_ERROR(result); + } + + f_close(&file); + return NULL; +} + +const char * writeModel() +{ + char path[256]; + strcpy(path, STR_MODELS_PATH); + path[sizeof(MODELS_PATH)-1] = '/'; + strAppend(&path[sizeof(MODELS_PATH)], g_eeGeneral.currModelFilename, sizeof(g_eeGeneral.currModelFilename)); + return writeFile(path, (uint8_t *)&g_model, sizeof(g_model)); +} + +const char * loadFile(const char * filename, uint8_t * data, uint16_t maxsize) +{ + FIL file; + char buf[8]; + UINT read; + + FRESULT result = f_open(&file, filename, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + + if (f_size(&file) < 8) { + f_close(&file); + return STR_INCOMPATIBLE; + } + + result = f_read(&file, (uint8_t *)buf, 8, &read); + if (result != FR_OK || read != 8) { + f_close(&file); + return SDCARD_ERROR(result); + } + + uint8_t version = (uint8_t)buf[4]; + if (*(uint32_t*)&buf[0] != O9X_FOURCC || version < FIRST_CONV_EEPROM_VER || version > EEPROM_VER || buf[5] != 'M') { + f_close(&file); + return STR_INCOMPATIBLE; + } + + uint16_t size = min(maxsize, *(uint16_t*)&buf[6]); + result = f_read(&file, data, size, &read); + if (result != FR_OK || read != size) { + f_close(&file); + return SDCARD_ERROR(result); + } + + f_close(&file); + return NULL; +} + +const char * loadModel(const char * filename) +{ + char path[256]; + strcpy(path, STR_MODELS_PATH); + path[sizeof(MODELS_PATH)-1] = '/'; + strcpy(&path[sizeof(MODELS_PATH)], filename); + + preModelLoad(); + + const char * error = loadFile(path, (uint8_t *)&g_model, sizeof(g_model)); + if (error) { + TRACE("loadModel error=%s", error); + } + + bool newModel = false; + if (error) { + modelDefault(0) ; + storageCheck(true); + newModel = true; + } + + postModelLoad(newModel); + + return error; +} + +const char RADIO_SETTINGS_PATH[] = RADIO_PATH "/radio.bin"; + +const char * loadGeneralSettings() +{ + const char * error = loadFile(RADIO_SETTINGS_PATH, (uint8_t *)&g_eeGeneral, sizeof(g_eeGeneral)); + if (error) { + TRACE("loadGeneralSettings error=%s", error); + } + // TODO this is temporary, we only have one model for now + return error; +} + +const char * writeGeneralSettings() +{ + return writeFile(RADIO_SETTINGS_PATH, (uint8_t *)&g_eeGeneral, sizeof(g_eeGeneral)); +} + +void storageCheck(bool immediately) +{ + // if (immediately) { + // eepromWriteWait(); + // } + + if (s_storageDirtyMsk & EE_GENERAL) { + TRACE("eeprom write general"); + s_storageDirtyMsk -= EE_GENERAL; + writeGeneralSettings(); + // if (immediately) + // eepromWriteWait(); + // else + return; + } + + if (s_storageDirtyMsk & EE_MODEL) { + TRACE("eeprom write model"); + s_storageDirtyMsk -= EE_MODEL; + const char * error = writeModel(); + if (error) { + TRACE("writeModel error=%s", error); + } + // if (immediately) + // eepromWriteWait(); + } +} + +void storageReadAll() +{ + if (loadGeneralSettings() != NULL) { + storageEraseAll(true); + strcpy(g_eeGeneral.currModelFilename, "model.bin"); + } + + stickMode = g_eeGeneral.stickMode; + +#if defined(CPUARM) + for (uint8_t i=0; languagePacks[i]!=NULL; i++) { + if (!strncmp(g_eeGeneral.ttsLanguage, languagePacks[i]->id, 2)) { + currentLanguagePackIdx = i; + currentLanguagePack = languagePacks[i]; + } + } +#endif + + loadModel(g_eeGeneral.currModelFilename); +} + +void storageFormat() +{ + sdCheckAndCreateDirectory(RADIO_PATH); + sdCheckAndCreateDirectory(MODELS_PATH); +} diff --git a/radio/src/storage/sdcard_raw.h b/radio/src/storage/sdcard_raw.h new file mode 100644 index 000000000..448e77d36 --- /dev/null +++ b/radio/src/storage/sdcard_raw.h @@ -0,0 +1,39 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +const char * loadModel(const char * filename); + + diff --git a/radio/src/storage/storage.h b/radio/src/storage/storage.h new file mode 100644 index 000000000..30f15aacc --- /dev/null +++ b/radio/src/storage/storage.h @@ -0,0 +1,76 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#ifndef _STORAGE_H_ +#define _STORAGE_H_ + +#if defined(SIMU) + #define WRITE_DELAY_10MS 200 +#elif defined(PCBTARANIS) + #define WRITE_DELAY_10MS 500 +#elif defined(PCBSKY9X) && !defined(REV0) + #define WRITE_DELAY_10MS 500 +#elif defined(PCBGRUVIN9X) && !defined(REV0) + #define WRITE_DELAY_10MS 500 +#else + #define WRITE_DELAY_10MS 200 +#endif + +extern uint8_t s_storageDirtyMsk; +extern tmr10ms_t s_storageDirtyTime10ms; + +#define TIME_TO_WRITE() (s_storageDirtyMsk && (tmr10ms_t)(get_tmr10ms() - s_storageDirtyTime10ms) >= (tmr10ms_t)WRITE_DELAY_10MS) + +void storageEraseAll(bool warn); +void storageFormat(); +void storageReadAll(); +void storageDirty(uint8_t msk); +void storageCheck(bool immediately); + +void preModelLoad(); +void postModelLoad(bool newModel); + +#if defined(EEPROM_RLC) +#include "eeprom_common.h" +#include "eeprom_rlc.h" +#elif defined(EEPROM) +#include "eeprom_common.h" +#include "eeprom_raw.h" +#elif defined(SDCARD) +#include "sdcard_raw.h" +#endif + +#endif diff --git a/radio/src/storage/storage_common.cpp b/radio/src/storage/storage_common.cpp new file mode 100644 index 000000000..ee4df2e72 --- /dev/null +++ b/radio/src/storage/storage_common.cpp @@ -0,0 +1,129 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#include "../opentx.h" + +uint8_t s_storageDirtyMsk; +tmr10ms_t s_storageDirtyTime10ms; + +void storageDirty(uint8_t msk) +{ + s_storageDirtyMsk |= msk; + s_storageDirtyTime10ms = get_tmr10ms() ; +} + +void preModelLoad() +{ +#if defined(CPUARM) + watchdogSetTimeout(500/*5s*/); +#endif + +#if defined(SDCARD) + closeLogs(); +#endif + + if (pulsesStarted()) { + pausePulses(); + } + + pauseMixerCalculations(); +} + +void postModelLoad(bool newModel) +{ + AUDIO_FLUSH(); + flightReset(); + logicalSwitchesReset(); + + if (pulsesStarted()) { +#if defined(GUI) + if (!newModel) { + checkAll(); + } +#endif + resumePulses(); + } + + customFunctionsReset(); + + restoreTimers(); + +#if defined(CPUARM) + for (int i=0; i> (i*2)]; putsMixerSource(x, y, MIXSRC_FIRST_SWITCH+i, attr); - lcd_putcAtt(lcdNextPos, y, c, attr); + lcdDrawChar(lcdNextPos, y, c, attr); x = lcdNextPos + 3; } else if (numWarnings == 7) { @@ -876,9 +876,9 @@ void checkSwitches() #else lcd_putsiAtt(x, y, STR_VSRCRAW, NUM_STICKS+1+i, INVERS); if (IS_POT(POT1+i)) - lcd_putcAtt(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? 126 : 127, INVERS); + lcdDrawChar(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? 126 : 127, INVERS); else - lcd_putcAtt(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? '\300' : '\301', INVERS); + lcdDrawChar(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? '\300' : '\301', INVERS); #endif #if defined(COLORLCD) if (++numWarnings < 6) { diff --git a/radio/src/targets/stock/board_stock.cpp b/radio/src/targets/9x/board_stock.cpp similarity index 100% rename from radio/src/targets/stock/board_stock.cpp rename to radio/src/targets/9x/board_stock.cpp diff --git a/radio/src/targets/stock/board_stock.h b/radio/src/targets/9x/board_stock.h similarity index 100% rename from radio/src/targets/stock/board_stock.h rename to radio/src/targets/9x/board_stock.h diff --git a/radio/src/targets/stock/lcd_driver.cpp b/radio/src/targets/9x/lcd_driver.cpp similarity index 100% rename from radio/src/targets/stock/lcd_driver.cpp rename to radio/src/targets/9x/lcd_driver.cpp diff --git a/radio/src/targets/stock/voice.cpp b/radio/src/targets/9x/voice.cpp similarity index 100% rename from radio/src/targets/stock/voice.cpp rename to radio/src/targets/9x/voice.cpp diff --git a/radio/src/targets/stock/voice.h b/radio/src/targets/9x/voice.h similarity index 100% rename from radio/src/targets/stock/voice.h rename to radio/src/targets/9x/voice.h diff --git a/radio/src/targets/gruvin9x/board_gruvin9x.cpp b/radio/src/targets/Gruvin9x/board_gruvin9x.cpp similarity index 100% rename from radio/src/targets/gruvin9x/board_gruvin9x.cpp rename to radio/src/targets/Gruvin9x/board_gruvin9x.cpp diff --git a/radio/src/targets/gruvin9x/board_gruvin9x.h b/radio/src/targets/Gruvin9x/board_gruvin9x.h similarity index 100% rename from radio/src/targets/gruvin9x/board_gruvin9x.h rename to radio/src/targets/Gruvin9x/board_gruvin9x.h diff --git a/radio/src/targets/gruvin9x/diskio.cpp b/radio/src/targets/Gruvin9x/diskio.cpp similarity index 100% rename from radio/src/targets/gruvin9x/diskio.cpp rename to radio/src/targets/Gruvin9x/diskio.cpp diff --git a/radio/src/targets/gruvin9x/fuses_2561.txt b/radio/src/targets/Gruvin9x/fuses_2561.txt similarity index 100% rename from radio/src/targets/gruvin9x/fuses_2561.txt rename to radio/src/targets/Gruvin9x/fuses_2561.txt diff --git a/radio/src/targets/gruvin9x/rtc_driver.cpp b/radio/src/targets/Gruvin9x/rtc_driver.cpp similarity index 100% rename from radio/src/targets/gruvin9x/rtc_driver.cpp rename to radio/src/targets/Gruvin9x/rtc_driver.cpp diff --git a/radio/src/targets/gruvin9x/somo14d.cpp b/radio/src/targets/Gruvin9x/somo14d.cpp similarity index 100% rename from radio/src/targets/gruvin9x/somo14d.cpp rename to radio/src/targets/Gruvin9x/somo14d.cpp diff --git a/radio/src/targets/gruvin9x/somo14d.h b/radio/src/targets/Gruvin9x/somo14d.h similarity index 100% rename from radio/src/targets/gruvin9x/somo14d.h rename to radio/src/targets/Gruvin9x/somo14d.h diff --git a/radio/src/targets/Horus/board_horus.cpp b/radio/src/targets/Horus/board_horus.cpp index 0e0fe3a35..5a7e0ef19 100644 --- a/radio/src/targets/Horus/board_horus.cpp +++ b/radio/src/targets/Horus/board_horus.cpp @@ -246,18 +246,10 @@ void checkTrainerSettings() } // TODO -void eeDirty(uint8_t msk) { } -uint8_t s_eeDirtyMsk; -tmr10ms_t s_eeDirtyTime10ms; -void eeCheck(bool) { } -ModelHeader modelHeaders[MAX_MODELS]; -void eeLoadModelHeaders() { } -void eeLoadModelHeader(uint8_t id, ModelHeader *header) { } bool eeCopyModel(uint8_t dst, uint8_t src) { } void eeSwapModels(uint8_t id1, uint8_t id2) { } void eeDeleteModel(uint8_t idx) { } uint8_t eeFindEmptyModel(uint8_t id, bool down) { return 0; }; -void eeLoadModel(uint8_t id) { } bool eeModelExists(uint8_t id) { return true; } const pm_char * eeBackupModel(uint8_t i_fileSrc) { return NULL; } const pm_char * eeRestoreModel(uint8_t i_fileDst, char *model_name) { return NULL; } diff --git a/radio/src/targets/Horus/board_horus.h b/radio/src/targets/Horus/board_horus.h index caf84cdb4..4aa3cc449 100644 --- a/radio/src/targets/Horus/board_horus.h +++ b/radio/src/targets/Horus/board_horus.h @@ -252,7 +252,7 @@ void ledBlue(void); // LCD driver void lcdInit(void); void lcdRefresh(void); -void lcdDrawFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); +void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); // Backlight driver #define setBacklight(xx) diff --git a/radio/src/targets/Horus/lcd_driver.cpp b/radio/src/targets/Horus/lcd_driver.cpp index 5af5c9d9e..0303e2f34 100644 --- a/radio/src/targets/Horus/lcd_driver.cpp +++ b/radio/src/targets/Horus/lcd_driver.cpp @@ -439,7 +439,7 @@ void lcdInit(void) LCD_SetTransparency(255); } -void lcdDrawFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) +void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { uint32_t addr = CurrentFrameBuffer + 2*(LCD_PIXEL_WIDTH*y + x); uint16_t red = (0xF800 & color) >> 11; diff --git a/radio/src/targets/mega2560/00readme.txt b/radio/src/targets/Mega2560/00readme.txt similarity index 100% rename from radio/src/targets/mega2560/00readme.txt rename to radio/src/targets/Mega2560/00readme.txt diff --git a/radio/src/targets/mega2560/board_mega2560.cpp b/radio/src/targets/Mega2560/board_mega2560.cpp similarity index 100% rename from radio/src/targets/mega2560/board_mega2560.cpp rename to radio/src/targets/Mega2560/board_mega2560.cpp diff --git a/radio/src/targets/mega2560/board_mega2560.h b/radio/src/targets/Mega2560/board_mega2560.h similarity index 100% rename from radio/src/targets/mega2560/board_mega2560.h rename to radio/src/targets/Mega2560/board_mega2560.h diff --git a/radio/src/targets/sky9x/AT91SAM3S2.h b/radio/src/targets/Sky9x/AT91SAM3S2.h similarity index 100% rename from radio/src/targets/sky9x/AT91SAM3S2.h rename to radio/src/targets/Sky9x/AT91SAM3S2.h diff --git a/radio/src/targets/sky9x/AT91SAM3S4.h b/radio/src/targets/Sky9x/AT91SAM3S4.h similarity index 100% rename from radio/src/targets/sky9x/AT91SAM3S4.h rename to radio/src/targets/Sky9x/AT91SAM3S4.h diff --git a/radio/src/targets/sky9x/MEDSdcard.c b/radio/src/targets/Sky9x/MEDSdcard.c similarity index 96% rename from radio/src/targets/sky9x/MEDSdcard.c rename to radio/src/targets/Sky9x/MEDSdcard.c index 40c360413..de334dafe 100644 --- a/radio/src/targets/sky9x/MEDSdcard.c +++ b/radio/src/targets/Sky9x/MEDSdcard.c @@ -32,10 +32,10 @@ //------------------------------------------------------------------------------ #include -#include "Media.h" #include "debug.h" -#include "board.h" #include "../FatFs/diskio.h" +#include "../Sky9x/board.h" +#include "../Sky9x/Media.h" //------------------------------------------------------------------------------ // Constants diff --git a/radio/src/targets/sky9x/Media.h b/radio/src/targets/Sky9x/Media.h similarity index 100% rename from radio/src/targets/sky9x/Media.h rename to radio/src/targets/Sky9x/Media.h diff --git a/radio/src/targets/sky9x/adc_driver.cpp b/radio/src/targets/Sky9x/adc_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/adc_driver.cpp rename to radio/src/targets/Sky9x/adc_driver.cpp diff --git a/radio/src/targets/sky9x/audio_driver.cpp b/radio/src/targets/Sky9x/audio_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/audio_driver.cpp rename to radio/src/targets/Sky9x/audio_driver.cpp diff --git a/radio/src/targets/sky9x/audio_driver.h b/radio/src/targets/Sky9x/audio_driver.h similarity index 100% rename from radio/src/targets/sky9x/audio_driver.h rename to radio/src/targets/Sky9x/audio_driver.h diff --git a/radio/src/targets/sky9x/bluetooth.cpp b/radio/src/targets/Sky9x/bluetooth.cpp similarity index 100% rename from radio/src/targets/sky9x/bluetooth.cpp rename to radio/src/targets/Sky9x/bluetooth.cpp diff --git a/radio/src/targets/sky9x/board.h b/radio/src/targets/Sky9x/board.h similarity index 96% rename from radio/src/targets/sky9x/board.h rename to radio/src/targets/Sky9x/board.h index 3434c126d..366684fd2 100644 --- a/radio/src/targets/sky9x/board.h +++ b/radio/src/targets/Sky9x/board.h @@ -40,16 +40,17 @@ #define board_h #include -#include "chip.h" + +#include "../Sky9x/chip.h" #if defined(REVA) -#include "AT91SAM3S2.h" +#include "../Sky9x/AT91SAM3S2.h" #else -#include "AT91SAM3S4.h" +#include "../Sky9x/AT91SAM3S4.h" #endif #if !defined(SIMU) -#include "core_cm3.h" +#include "../Sky9x/core_cm3.h" #endif //------------------------------------------------------------------------------ @@ -97,7 +98,7 @@ // ADC //------------------------------------------------------------------------------ -/// Startup time max, return from Idle mode (in µs) +/// Startup time max, return from Idle mode (in �s) #define ADC_STARTUP_TIME_MAX 15 /// Track and hold Acquisition Time min (in ns) #define ADC_TRACK_HOLD_TIME_MIN 1200 diff --git a/radio/src/targets/sky9x/board_lowlevel.c b/radio/src/targets/Sky9x/board_lowlevel.c similarity index 96% rename from radio/src/targets/sky9x/board_lowlevel.c rename to radio/src/targets/Sky9x/board_lowlevel.c index 49d3a1b53..9e72d975f 100644 --- a/radio/src/targets/sky9x/board_lowlevel.c +++ b/radio/src/targets/Sky9x/board_lowlevel.c @@ -39,7 +39,7 @@ * Headers *----------------------------------------------------------------------------*/ -#include "board.h" +#include "../Sky9x/board.h" /*---------------------------------------------------------------------------- * Local definitions diff --git a/radio/src/targets/sky9x/board_sky9x.cpp b/radio/src/targets/Sky9x/board_sky9x.cpp similarity index 100% rename from radio/src/targets/sky9x/board_sky9x.cpp rename to radio/src/targets/Sky9x/board_sky9x.cpp diff --git a/radio/src/targets/sky9x/board_sky9x.h b/radio/src/targets/Sky9x/board_sky9x.h similarity index 100% rename from radio/src/targets/sky9x/board_sky9x.h rename to radio/src/targets/Sky9x/board_sky9x.h diff --git a/radio/src/targets/sky9x/buzzer_driver.cpp b/radio/src/targets/Sky9x/buzzer_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/buzzer_driver.cpp rename to radio/src/targets/Sky9x/buzzer_driver.cpp diff --git a/radio/src/targets/sky9x/chip.h b/radio/src/targets/Sky9x/chip.h similarity index 100% rename from radio/src/targets/sky9x/chip.h rename to radio/src/targets/Sky9x/chip.h diff --git a/radio/src/targets/sky9x/coproc_driver.cpp b/radio/src/targets/Sky9x/coproc_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/coproc_driver.cpp rename to radio/src/targets/Sky9x/coproc_driver.cpp diff --git a/radio/src/targets/sky9x/core_cm3.c b/radio/src/targets/Sky9x/core_cm3.c similarity index 100% rename from radio/src/targets/sky9x/core_cm3.c rename to radio/src/targets/Sky9x/core_cm3.c diff --git a/radio/src/targets/sky9x/core_cm3.h b/radio/src/targets/Sky9x/core_cm3.h similarity index 100% rename from radio/src/targets/sky9x/core_cm3.h rename to radio/src/targets/Sky9x/core_cm3.h diff --git a/radio/src/targets/sky9x/crt.c b/radio/src/targets/Sky9x/crt.c similarity index 100% rename from radio/src/targets/sky9x/crt.c rename to radio/src/targets/Sky9x/crt.c diff --git a/radio/src/targets/sky9x/diskio.cpp b/radio/src/targets/Sky9x/diskio.cpp similarity index 96% rename from radio/src/targets/sky9x/diskio.cpp rename to radio/src/targets/Sky9x/diskio.cpp index 45aaa1068..0b7b790ba 100644 --- a/radio/src/targets/sky9x/diskio.cpp +++ b/radio/src/targets/Sky9x/diskio.cpp @@ -42,11 +42,11 @@ /*-----------------------------------------------------------------------*/ #include -#include "board.h" #include "debug.h" #include "../FatFs/diskio.h" #include "../FatFs/ff.h" #include "../CoOS/kernel/CoOS.h" +#include "../Sky9x/board.h" #define CARD_TYPE_bmHC (1 << 0) /**< Bit for High-Capacity(Density) */ #define CARD_TYPE_bmSDMMC (0x3 << 1) /**< Bits mask for SD/MMC */ diff --git a/radio/src/targets/sky9x/eeprom_driver.cpp b/radio/src/targets/Sky9x/eeprom_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/eeprom_driver.cpp rename to radio/src/targets/Sky9x/eeprom_driver.cpp diff --git a/radio/src/targets/sky9x/flash_driver.cpp b/radio/src/targets/Sky9x/flash_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/flash_driver.cpp rename to radio/src/targets/Sky9x/flash_driver.cpp diff --git a/radio/src/targets/sky9x/haptic_driver.cpp b/radio/src/targets/Sky9x/haptic_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/haptic_driver.cpp rename to radio/src/targets/Sky9x/haptic_driver.cpp diff --git a/radio/src/targets/sky9x/keys_driver.cpp b/radio/src/targets/Sky9x/keys_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/keys_driver.cpp rename to radio/src/targets/Sky9x/keys_driver.cpp diff --git a/radio/src/targets/sky9x/lcd_driver.cpp b/radio/src/targets/Sky9x/lcd_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/lcd_driver.cpp rename to radio/src/targets/Sky9x/lcd_driver.cpp diff --git a/radio/src/targets/sky9x/massstorage.cpp b/radio/src/targets/Sky9x/massstorage.cpp similarity index 94% rename from radio/src/targets/sky9x/massstorage.cpp rename to radio/src/targets/Sky9x/massstorage.cpp index 39a0fbd5f..9fa6ea3d3 100644 --- a/radio/src/targets/sky9x/massstorage.cpp +++ b/radio/src/targets/Sky9x/massstorage.cpp @@ -39,7 +39,7 @@ #ifndef SIMU extern "C" { -#include "usb/device/massstorage/MSDDriver.h" +#include "../Sky9x/usb/device/massstorage/MSDDriver.h" extern unsigned char MEDSdcard_Initialize(Media *media, unsigned char mciID); } diff --git a/radio/src/targets/sky9x/pulses_driver.cpp b/radio/src/targets/Sky9x/pulses_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/pulses_driver.cpp rename to radio/src/targets/Sky9x/pulses_driver.cpp diff --git a/radio/src/targets/sky9x/pwr_driver.cpp b/radio/src/targets/Sky9x/pwr_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/pwr_driver.cpp rename to radio/src/targets/Sky9x/pwr_driver.cpp diff --git a/radio/src/targets/sky9x/rotenc_driver.cpp b/radio/src/targets/Sky9x/rotenc_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/rotenc_driver.cpp rename to radio/src/targets/Sky9x/rotenc_driver.cpp diff --git a/radio/src/targets/sky9x/rtc_driver.cpp b/radio/src/targets/Sky9x/rtc_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/rtc_driver.cpp rename to radio/src/targets/Sky9x/rtc_driver.cpp diff --git a/radio/src/targets/sky9x/sam3s2c_flash.ld b/radio/src/targets/Sky9x/sam3s2c_flash.ld similarity index 100% rename from radio/src/targets/sky9x/sam3s2c_flash.ld rename to radio/src/targets/Sky9x/sam3s2c_flash.ld diff --git a/radio/src/targets/sky9x/sam3s4c_flash.ld b/radio/src/targets/Sky9x/sam3s4c_flash.ld similarity index 100% rename from radio/src/targets/sky9x/sam3s4c_flash.ld rename to radio/src/targets/Sky9x/sam3s4c_flash.ld diff --git a/radio/src/targets/sky9x/sam3s8c_flash.ld b/radio/src/targets/Sky9x/sam3s8c_flash.ld similarity index 100% rename from radio/src/targets/sky9x/sam3s8c_flash.ld rename to radio/src/targets/Sky9x/sam3s8c_flash.ld diff --git a/radio/src/targets/sky9x/sdcard_driver.cpp b/radio/src/targets/Sky9x/sdcard_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/sdcard_driver.cpp rename to radio/src/targets/Sky9x/sdcard_driver.cpp diff --git a/radio/src/targets/sky9x/serial2_driver.cpp b/radio/src/targets/Sky9x/serial2_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/serial2_driver.cpp rename to radio/src/targets/Sky9x/serial2_driver.cpp diff --git a/radio/src/targets/sky9x/telemetry_driver.cpp b/radio/src/targets/Sky9x/telemetry_driver.cpp similarity index 100% rename from radio/src/targets/sky9x/telemetry_driver.cpp rename to radio/src/targets/Sky9x/telemetry_driver.cpp diff --git a/radio/src/targets/sky9x/usb/common/core/USBConfigurationDescriptor.c b/radio/src/targets/Sky9x/usb/common/core/USBConfigurationDescriptor.c similarity index 96% rename from radio/src/targets/sky9x/usb/common/core/USBConfigurationDescriptor.c rename to radio/src/targets/Sky9x/usb/common/core/USBConfigurationDescriptor.c index 503dceb0c..b41c06be6 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBConfigurationDescriptor.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBConfigurationDescriptor.c @@ -38,7 +38,7 @@ // Headers //----------------------------------------------------------------------------- -#include "USBConfigurationDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBConfigurationDescriptor.h" //----------------------------------------------------------------------------- // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBConfigurationDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBConfigurationDescriptor.h similarity index 94% rename from radio/src/targets/sky9x/usb/common/core/USBConfigurationDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBConfigurationDescriptor.h index 150e6563e..8d10b7d8d 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBConfigurationDescriptor.h +++ b/radio/src/targets/Sky9x/usb/common/core/USBConfigurationDescriptor.h @@ -54,9 +54,9 @@ // Headers //----------------------------------------------------------------------------- -#include "USBGenericDescriptor.h" -#include "USBInterfaceDescriptor.h" -#include "USBEndpointDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBEndpointDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBGenericDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBInterfaceDescriptor.h" //----------------------------------------------------------------------------- // Definitions diff --git a/radio/src/targets/sky9x/usb/common/core/USBDeviceDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBDeviceDescriptor.h similarity index 94% rename from radio/src/targets/sky9x/usb/common/core/USBDeviceDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBDeviceDescriptor.h index ce8d19988..f526e9c47 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBDeviceDescriptor.h +++ b/radio/src/targets/Sky9x/usb/common/core/USBDeviceDescriptor.h @@ -42,7 +42,7 @@ #ifndef USBDEVICEDESCRIPTOR_H #define USBDEVICEDESCRIPTOR_H -#include +#include "../../../../Sky9x/board.h" //------------------------------------------------------------------------------ // Definitions //------------------------------------------------------------------------------ @@ -111,7 +111,7 @@ typedef struct { unsigned char bLength; /// Descriptor type (USBGenericDescriptor_OTG). unsigned char bDescriptorType; - /// Attribute Fields D7…2: Reserved D1: HNP support D0: SRP support + /// Attribute Fields D7�2: Reserved D1: HNP support D0: SRP support unsigned char bmAttributes; } __attribute__ ((packed)) USBOtgDescriptor; // GCC diff --git a/radio/src/targets/sky9x/usb/common/core/USBDeviceQualifierDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBDeviceQualifierDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBDeviceQualifierDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBDeviceQualifierDescriptor.h diff --git a/radio/src/targets/sky9x/usb/common/core/USBEndpointDescriptor.c b/radio/src/targets/Sky9x/usb/common/core/USBEndpointDescriptor.c similarity index 96% rename from radio/src/targets/sky9x/usb/common/core/USBEndpointDescriptor.c rename to radio/src/targets/Sky9x/usb/common/core/USBEndpointDescriptor.c index 6300adeea..aa3ac0220 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBEndpointDescriptor.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBEndpointDescriptor.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBEndpointDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBEndpointDescriptor.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBEndpointDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBEndpointDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBEndpointDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBEndpointDescriptor.h diff --git a/radio/src/targets/sky9x/usb/common/core/USBFeatureRequest.c b/radio/src/targets/Sky9x/usb/common/core/USBFeatureRequest.c similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBFeatureRequest.c rename to radio/src/targets/Sky9x/usb/common/core/USBFeatureRequest.c index 1e0422af4..d4793f2fb 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBFeatureRequest.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBFeatureRequest.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBFeatureRequest.h" +#include "../../../../Sky9x/usb/common/core/USBFeatureRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBFeatureRequest.h b/radio/src/targets/Sky9x/usb/common/core/USBFeatureRequest.h similarity index 96% rename from radio/src/targets/sky9x/usb/common/core/USBFeatureRequest.h rename to radio/src/targets/Sky9x/usb/common/core/USBFeatureRequest.h index 0454ffbb0..a917bb148 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBFeatureRequest.h +++ b/radio/src/targets/Sky9x/usb/common/core/USBFeatureRequest.h @@ -50,7 +50,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGenericRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" //------------------------------------------------------------------------------ // Definitions diff --git a/radio/src/targets/sky9x/usb/common/core/USBGenericDescriptor.c b/radio/src/targets/Sky9x/usb/common/core/USBGenericDescriptor.c similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBGenericDescriptor.c rename to radio/src/targets/Sky9x/usb/common/core/USBGenericDescriptor.c index 99ed6f94d..3c13044be 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBGenericDescriptor.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBGenericDescriptor.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGenericDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBGenericDescriptor.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBGenericDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBGenericDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBGenericDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBGenericDescriptor.h diff --git a/radio/src/targets/sky9x/usb/common/core/USBGenericRequest.c b/radio/src/targets/Sky9x/usb/common/core/USBGenericRequest.c similarity index 96% rename from radio/src/targets/sky9x/usb/common/core/USBGenericRequest.c rename to radio/src/targets/Sky9x/usb/common/core/USBGenericRequest.c index a3bbbfd1a..893bd1caa 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBGenericRequest.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBGenericRequest.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGenericRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBGenericRequest.h b/radio/src/targets/Sky9x/usb/common/core/USBGenericRequest.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBGenericRequest.h rename to radio/src/targets/Sky9x/usb/common/core/USBGenericRequest.h diff --git a/radio/src/targets/sky9x/usb/common/core/USBGetDescriptorRequest.c b/radio/src/targets/Sky9x/usb/common/core/USBGetDescriptorRequest.c similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBGetDescriptorRequest.c rename to radio/src/targets/Sky9x/usb/common/core/USBGetDescriptorRequest.c index 293792962..9822d2ee0 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBGetDescriptorRequest.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBGetDescriptorRequest.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGetDescriptorRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGetDescriptorRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBGetDescriptorRequest.h b/radio/src/targets/Sky9x/usb/common/core/USBGetDescriptorRequest.h similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBGetDescriptorRequest.h rename to radio/src/targets/Sky9x/usb/common/core/USBGetDescriptorRequest.h index 43f6afd3d..75e440860 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBGetDescriptorRequest.h +++ b/radio/src/targets/Sky9x/usb/common/core/USBGetDescriptorRequest.h @@ -51,7 +51,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGenericRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBInterfaceAssociationDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBInterfaceAssociationDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBInterfaceAssociationDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBInterfaceAssociationDescriptor.h diff --git a/radio/src/targets/sky9x/usb/common/core/USBInterfaceDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBInterfaceDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBInterfaceDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBInterfaceDescriptor.h diff --git a/radio/src/targets/sky9x/usb/common/core/USBInterfaceRequest.c b/radio/src/targets/Sky9x/usb/common/core/USBInterfaceRequest.c similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBInterfaceRequest.c rename to radio/src/targets/Sky9x/usb/common/core/USBInterfaceRequest.c index 61bf6ed77..4d896256e 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBInterfaceRequest.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBInterfaceRequest.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBInterfaceRequest.h" +#include "../../../../Sky9x/usb/common/core/USBInterfaceRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBInterfaceRequest.h b/radio/src/targets/Sky9x/usb/common/core/USBInterfaceRequest.h similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBInterfaceRequest.h rename to radio/src/targets/Sky9x/usb/common/core/USBInterfaceRequest.h index bd4cddd17..71c2aa2bf 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBInterfaceRequest.h +++ b/radio/src/targets/Sky9x/usb/common/core/USBInterfaceRequest.h @@ -51,7 +51,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGenericRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBIrqHandler.c b/radio/src/targets/Sky9x/usb/common/core/USBIrqHandler.c similarity index 94% rename from radio/src/targets/sky9x/usb/common/core/USBIrqHandler.c rename to radio/src/targets/Sky9x/usb/common/core/USBIrqHandler.c index 386ebd216..bc4634b89 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBIrqHandler.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBIrqHandler.c @@ -37,10 +37,9 @@ //------------------------------------------------------------------------------ // Headers //------------------------------------------------------------------------------ -#include -#include - #include +#include "../../../../Sky9x/board.h" +#include "../../../../Sky9x/usb/device/core/USBD.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBIrqHandler.h b/radio/src/targets/Sky9x/usb/common/core/USBIrqHandler.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBIrqHandler.h rename to radio/src/targets/Sky9x/usb/common/core/USBIrqHandler.h diff --git a/radio/src/targets/sky9x/usb/common/core/USBSetAddressRequest.c b/radio/src/targets/Sky9x/usb/common/core/USBSetAddressRequest.c similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBSetAddressRequest.c rename to radio/src/targets/Sky9x/usb/common/core/USBSetAddressRequest.c index 0e82a7f7f..e8cb01e1c 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBSetAddressRequest.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBSetAddressRequest.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBSetAddressRequest.h" +#include "../../../../Sky9x/usb/common/core/USBSetAddressRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBSetAddressRequest.h b/radio/src/targets/Sky9x/usb/common/core/USBSetAddressRequest.h similarity index 94% rename from radio/src/targets/sky9x/usb/common/core/USBSetAddressRequest.h rename to radio/src/targets/Sky9x/usb/common/core/USBSetAddressRequest.h index 78034c3d1..2ca7947d2 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBSetAddressRequest.h +++ b/radio/src/targets/Sky9x/usb/common/core/USBSetAddressRequest.h @@ -47,7 +47,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGenericRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBSetConfigurationRequest.c b/radio/src/targets/Sky9x/usb/common/core/USBSetConfigurationRequest.c similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBSetConfigurationRequest.c rename to radio/src/targets/Sky9x/usb/common/core/USBSetConfigurationRequest.c index 3aa747d3a..cf4b27d9c 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBSetConfigurationRequest.c +++ b/radio/src/targets/Sky9x/usb/common/core/USBSetConfigurationRequest.c @@ -38,7 +38,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBSetConfigurationRequest.h" +#include "../../../../Sky9x/usb/common/core/USBSetConfigurationRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBSetConfigurationRequest.h b/radio/src/targets/Sky9x/usb/common/core/USBSetConfigurationRequest.h similarity index 95% rename from radio/src/targets/sky9x/usb/common/core/USBSetConfigurationRequest.h rename to radio/src/targets/Sky9x/usb/common/core/USBSetConfigurationRequest.h index aa1fa1729..73b34b21e 100644 --- a/radio/src/targets/sky9x/usb/common/core/USBSetConfigurationRequest.h +++ b/radio/src/targets/Sky9x/usb/common/core/USBSetConfigurationRequest.h @@ -48,7 +48,7 @@ // Headers //------------------------------------------------------------------------------ -#include "USBGenericRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" //------------------------------------------------------------------------------ // Exported functions diff --git a/radio/src/targets/sky9x/usb/common/core/USBStringDescriptor.h b/radio/src/targets/Sky9x/usb/common/core/USBStringDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/core/USBStringDescriptor.h rename to radio/src/targets/Sky9x/usb/common/core/USBStringDescriptor.h diff --git a/radio/src/targets/sky9x/usb/common/massstorage/MSDeviceDescriptor.h b/radio/src/targets/Sky9x/usb/common/massstorage/MSDeviceDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/massstorage/MSDeviceDescriptor.h rename to radio/src/targets/Sky9x/usb/common/massstorage/MSDeviceDescriptor.h diff --git a/radio/src/targets/sky9x/usb/common/massstorage/MSInterfaceDescriptor.h b/radio/src/targets/Sky9x/usb/common/massstorage/MSInterfaceDescriptor.h similarity index 100% rename from radio/src/targets/sky9x/usb/common/massstorage/MSInterfaceDescriptor.h rename to radio/src/targets/Sky9x/usb/common/massstorage/MSInterfaceDescriptor.h diff --git a/radio/src/targets/sky9x/usb/device/core/USBD.h b/radio/src/targets/Sky9x/usb/device/core/USBD.h similarity index 96% rename from radio/src/targets/sky9x/usb/device/core/USBD.h rename to radio/src/targets/Sky9x/usb/device/core/USBD.h index 7c1474582..f466a9c38 100644 --- a/radio/src/targets/sky9x/usb/device/core/USBD.h +++ b/radio/src/targets/Sky9x/usb/device/core/USBD.h @@ -50,8 +50,8 @@ //------------------------------------------------------------------------------ // #include -#include -#include +#include "../../../../Sky9x/usb/common/core/USBEndpointDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" //------------------------------------------------------------------------------ // Compile Options diff --git a/radio/src/targets/sky9x/usb/device/core/USBDDriver.c b/radio/src/targets/Sky9x/usb/device/core/USBDDriver.c similarity index 92% rename from radio/src/targets/sky9x/usb/device/core/USBDDriver.c rename to radio/src/targets/Sky9x/usb/device/core/USBDDriver.c index e8315702b..e456b5efd 100644 --- a/radio/src/targets/sky9x/usb/device/core/USBDDriver.c +++ b/radio/src/targets/Sky9x/usb/device/core/USBDDriver.c @@ -31,22 +31,22 @@ // Headers //------------------------------------------------------------------------------ -#include "USBDDriver.h" -#include "USBD.h" -#include -#include "debug.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "../../../../Sky9x/usb/device/core/USBDDriver.h" +#include "debug.h" #include +#include "../../../../Sky9x/board.h" +#include "../../../../Sky9x/usb/common/core/USBConfigurationDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBDeviceDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBDeviceQualifierDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBEndpointDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBFeatureRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBGetDescriptorRequest.h" +#include "../../../../Sky9x/usb/common/core/USBInterfaceRequest.h" +#include "../../../../Sky9x/usb/common/core/USBSetAddressRequest.h" +#include "../../../../Sky9x/usb/common/core/USBSetConfigurationRequest.h" +#include "../../../../Sky9x/usb/device/core/USBD.h" //------------------------------------------------------------------------------ // Local functions @@ -419,8 +419,8 @@ static void USBDDriver_Test(unsigned char test) //patterns, jitter, and any other dynamic waveform specifications. //The test packet is made up by concatenating the following strings. //(Note: For J/K NRZI data, and for NRZ data, the bit on the left is the first one - //transmitted. “S” indicates that a bit stuff occurs, which inserts an “extra” NRZI data bit. - //“* N” is used to indicate N occurrences of a string of bits or symbols.) + //transmitted. �S� indicates that a bit stuff occurs, which inserts an �extra� NRZI data bit. + //�* N� is used to indicate N occurrences of a string of bits or symbols.) //A port in Test_Packet mode must send this packet repetitively. The inter-packet timing //must be no less than the minimum allowable inter-packet gap as defined in Section 7.1.18 and //no greater than 125 us. @@ -433,7 +433,7 @@ static void USBDDriver_Test(unsigned char test) case USBFeatureRequest_TESTJ: //Test mode Test_J: - //Upon command, a port’s transceiver must enter the high-speed J state and remain in that + //Upon command, a port�s transceiver must enter the high-speed J state and remain in that //state until the exit action is taken. This enables the testing of the high output drive //level on the D+ line. // Send ZLP @@ -445,7 +445,7 @@ static void USBDDriver_Test(unsigned char test) case USBFeatureRequest_TESTK: //Test mode Test_K: - //Upon command, a port’s transceiver must enter the high-speed K state and remain in + //Upon command, a port�s transceiver must enter the high-speed K state and remain in //that state until the exit action is taken. This enables the testing of the high output drive //level on the D- line. // Send a ZLP @@ -456,7 +456,7 @@ static void USBDDriver_Test(unsigned char test) case USBFeatureRequest_TESTSE0NAK: //Test mode Test_SE0_NAK: - //Upon command, a port’s transceiver must enter the high-speed receive mode + //Upon command, a port�s transceiver must enter the high-speed receive mode //and remain in that mode until the exit action is taken. This enables the testing //of output impedance, low level output voltage, and loading characteristics. //In addition, while in this mode, upstream facing ports (and only upstream facing ports) diff --git a/radio/src/targets/sky9x/usb/device/core/USBDDriver.h b/radio/src/targets/Sky9x/usb/device/core/USBDDriver.h similarity index 94% rename from radio/src/targets/sky9x/usb/device/core/USBDDriver.h rename to radio/src/targets/Sky9x/usb/device/core/USBDDriver.h index 40ca6d77a..eef7a6e44 100644 --- a/radio/src/targets/sky9x/usb/device/core/USBDDriver.h +++ b/radio/src/targets/Sky9x/usb/device/core/USBDDriver.h @@ -50,8 +50,8 @@ // Headers //------------------------------------------------------------------------------ -#include "USBDDriverDescriptors.h" -#include +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" +#include "../../../../Sky9x/usb/device/core/USBDDriverDescriptors.h" //------------------------------------------------------------------------------ // Types diff --git a/radio/src/targets/sky9x/usb/device/core/USBDDriverDescriptors.h b/radio/src/targets/Sky9x/usb/device/core/USBDDriverDescriptors.h similarity index 92% rename from radio/src/targets/sky9x/usb/device/core/USBDDriverDescriptors.h rename to radio/src/targets/Sky9x/usb/device/core/USBDDriverDescriptors.h index f1064e4ac..50416c162 100644 --- a/radio/src/targets/sky9x/usb/device/core/USBDDriverDescriptors.h +++ b/radio/src/targets/Sky9x/usb/device/core/USBDDriverDescriptors.h @@ -43,9 +43,9 @@ // Headers //------------------------------------------------------------------------------ -#include -#include -#include +#include "../../../../Sky9x/usb/common/core/USBConfigurationDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBDeviceDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBDeviceQualifierDescriptor.h" //------------------------------------------------------------------------------ // Types diff --git a/radio/src/targets/sky9x/usb/device/core/USBD_UDP.c b/radio/src/targets/Sky9x/usb/device/core/USBD_UDP.c similarity index 96% rename from radio/src/targets/sky9x/usb/device/core/USBD_UDP.c rename to radio/src/targets/Sky9x/usb/device/core/USBD_UDP.c index 544f68596..fba624a43 100644 --- a/radio/src/targets/sky9x/usb/device/core/USBD_UDP.c +++ b/radio/src/targets/Sky9x/usb/device/core/USBD_UDP.c @@ -41,11 +41,11 @@ // Headers //------------------------------------------------------------------------------ -#include "USBD.h" -#include +#include "../../../../Sky9x/board.h" +#include "../../../../Sky9x/usb/common/core/USBEndpointDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" +#include "../../../../Sky9x/usb/device/core/USBD.h" #include "debug.h" -#include -#include //------------------------------------------------------------------------------ // Local definitions diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSD.h b/radio/src/targets/Sky9x/usb/device/massstorage/MSD.h similarity index 100% rename from radio/src/targets/sky9x/usb/device/massstorage/MSD.h rename to radio/src/targets/Sky9x/usb/device/massstorage/MSD.h diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDDStateMachine.c b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDStateMachine.c similarity index 96% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDDStateMachine.c rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDDStateMachine.c index a024e26de..41c0f9e76 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDDStateMachine.c +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDStateMachine.c @@ -31,8 +31,9 @@ // Includes //----------------------------------------------------------------------------- -#include "SBCMethods.h" -#include "MSDDStateMachine.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDDStateMachine.h" + +#include "../../../../Sky9x/usb/device/massstorage/SBCMethods.h" //----------------------------------------------------------------------------- // Internal functions diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDDStateMachine.h b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDStateMachine.h similarity index 96% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDDStateMachine.h rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDDStateMachine.h index c52adc93f..bbfd88742 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDDStateMachine.h +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDStateMachine.h @@ -51,8 +51,8 @@ // Headers //----------------------------------------------------------------------------- -#include "MSD.h" -#include "MSDLun.h" +#include "../../../../Sky9x/usb/device/massstorage/MSD.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDLun.h" #include "debug.h" //----------------------------------------------------------------------------- diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriver.c b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriver.c similarity index 93% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDDriver.c rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDDriver.c index 4082bdd9e..e546d77fe 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriver.c +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriver.c @@ -31,15 +31,16 @@ // Includes //------------------------------------------------------------------------------ -#include -#include "MSDDriver.h" -#include "MSDDriverDescriptors.h" -#include "SBCMethods.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDDriver.h" + +#include "../../../../Sky9x/Media.h" +#include "../../../../Sky9x/usb/common/core/USBFeatureRequest.h" +#include "../../../../Sky9x/usb/common/core/USBGenericRequest.h" +#include "../../../../Sky9x/usb/device/core/USBD.h" +#include "../../../../Sky9x/usb/device/core/USBDDriver.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDDriverDescriptors.h" +#include "../../../../Sky9x/usb/device/massstorage/SBCMethods.h" #include "debug.h" -#include -#include -#include -#include //----------------------------------------------------------------------------- // Internal variables diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriver.h b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriver.h similarity index 94% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDDriver.h rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDDriver.h index 388f0adcb..09f797f96 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriver.h +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriver.h @@ -50,8 +50,8 @@ // Headers //------------------------------------------------------------------------------ -#include "MSD.h" -#include "MSDLun.h" +#include "../../../../Sky9x/usb/device/massstorage/MSD.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDLun.h" // #include //------------------------------------------------------------------------------ diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriverDescriptors.c b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriverDescriptors.c similarity index 93% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDDriverDescriptors.c rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDDriverDescriptors.c index 30e1efb12..529de130c 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriverDescriptors.c +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriverDescriptors.c @@ -31,16 +31,17 @@ // Headers //------------------------------------------------------------------------------ -#include "MSDDriverDescriptors.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "../../../../Sky9x/usb/device/massstorage/MSDDriverDescriptors.h" + +#include "../../../../Sky9x/board.h" +#include "../../../../Sky9x/usb/common/core/USBConfigurationDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBDeviceDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBEndpointDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBGenericDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBInterfaceDescriptor.h" +#include "../../../../Sky9x/usb/common/core/USBStringDescriptor.h" +#include "../../../../Sky9x/usb/common/massstorage/MSDeviceDescriptor.h" +#include "../../../../Sky9x/usb/common/massstorage/MSInterfaceDescriptor.h" //------------------------------------------------------------------------------ // Internal definitions diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriverDescriptors.h b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriverDescriptors.h similarity index 95% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDDriverDescriptors.h rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDDriverDescriptors.h index be046a95f..c2a3a36ef 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDDriverDescriptors.h +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDDriverDescriptors.h @@ -47,7 +47,7 @@ // Headers //------------------------------------------------------------------------------ -#include +#include "../../../../Sky9x/usb/device/core/USBDDriverDescriptors.h" //------------------------------------------------------------------------------ // Definitions diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDIOFifo.h b/radio/src/targets/Sky9x/usb/device/massstorage/MSDIOFifo.h similarity index 100% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDIOFifo.h rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDIOFifo.h diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDLun.c b/radio/src/targets/Sky9x/usb/device/massstorage/MSDLun.c similarity index 96% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDLun.c rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDLun.c index 00c8376d3..c83e0b4fa 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDLun.c +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDLun.c @@ -32,9 +32,10 @@ //------------------------------------------------------------------------------ -#include "MSDLun.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDLun.h" + +#include "../../../../Sky9x/usb/device/core/USBD.h" #include "debug.h" -#include //------------------------------------------------------------------------------ // Constants diff --git a/radio/src/targets/sky9x/usb/device/massstorage/MSDLun.h b/radio/src/targets/Sky9x/usb/device/massstorage/MSDLun.h similarity index 94% rename from radio/src/targets/sky9x/usb/device/massstorage/MSDLun.h rename to radio/src/targets/Sky9x/usb/device/massstorage/MSDLun.h index 15754f403..6124ccd6f 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/MSDLun.h +++ b/radio/src/targets/Sky9x/usb/device/massstorage/MSDLun.h @@ -50,10 +50,10 @@ // Headers //------------------------------------------------------------------------------ -#include "SBC.h" -#include -#include -#include +#include "../../../../Sky9x/Media.h" +#include "../../../../Sky9x/usb/device/core/USBD.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDIOFifo.h" +#include "../../../../Sky9x/usb/device/massstorage/SBC.h" //------------------------------------------------------------------------------ // Definitions diff --git a/radio/src/targets/sky9x/usb/device/massstorage/SBC.h b/radio/src/targets/Sky9x/usb/device/massstorage/SBC.h similarity index 100% rename from radio/src/targets/sky9x/usb/device/massstorage/SBC.h rename to radio/src/targets/Sky9x/usb/device/massstorage/SBC.h diff --git a/radio/src/targets/sky9x/usb/device/massstorage/SBCMethods.c b/radio/src/targets/Sky9x/usb/device/massstorage/SBCMethods.c similarity index 96% rename from radio/src/targets/sky9x/usb/device/massstorage/SBCMethods.c rename to radio/src/targets/Sky9x/usb/device/massstorage/SBCMethods.c index 0f69cd111..65b416bbb 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/SBCMethods.c +++ b/radio/src/targets/Sky9x/usb/device/massstorage/SBCMethods.c @@ -31,11 +31,11 @@ // Headers //------------------------------------------------------------------------------ -#include "SBCMethods.h" -#include "MSDDStateMachine.h" -#include +#include "../../../../Sky9x/usb/device/massstorage/SBCMethods.h" -#include "MSDIOFifo.h" +#include "../../../../Sky9x/usb/device/core/USBD.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDDStateMachine.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDIOFifo.h" //------------------------------------------------------------------------------ // Global variables diff --git a/radio/src/targets/sky9x/usb/device/massstorage/SBCMethods.h b/radio/src/targets/Sky9x/usb/device/massstorage/SBCMethods.h similarity index 95% rename from radio/src/targets/sky9x/usb/device/massstorage/SBCMethods.h rename to radio/src/targets/Sky9x/usb/device/massstorage/SBCMethods.h index f40f55af3..25b1d5e76 100644 --- a/radio/src/targets/sky9x/usb/device/massstorage/SBCMethods.h +++ b/radio/src/targets/Sky9x/usb/device/massstorage/SBCMethods.h @@ -51,9 +51,8 @@ // Headers //------------------------------------------------------------------------------ -#include "SBC.h" -// #include -#include "MSDDStateMachine.h" +#include "../../../../Sky9x/usb/device/massstorage/MSDDStateMachine.h" +#include "../../../../Sky9x/usb/device/massstorage/SBC.h" //------------------------------------------------------------------------------ // Definitions diff --git a/radio/src/targets/sky9x/vectors_sam3s.c b/radio/src/targets/Sky9x/vectors_sam3s.c similarity index 100% rename from radio/src/targets/sky9x/vectors_sam3s.c rename to radio/src/targets/Sky9x/vectors_sam3s.c diff --git a/radio/src/targets/taranis/adc_driver.cpp b/radio/src/targets/Taranis/adc_driver.cpp similarity index 100% rename from radio/src/targets/taranis/adc_driver.cpp rename to radio/src/targets/Taranis/adc_driver.cpp diff --git a/radio/src/targets/taranis/aspi.c b/radio/src/targets/Taranis/aspi.c similarity index 92% rename from radio/src/targets/taranis/aspi.c rename to radio/src/targets/Taranis/aspi.c index ce435faa0..1292eebfa 100644 --- a/radio/src/targets/taranis/aspi.c +++ b/radio/src/targets/Taranis/aspi.c @@ -10,7 +10,7 @@ ****************************************************************************** */ -#include "board_taranis.h" +#include "../Taranis/board_taranis.h" /** **********send command to lcd************** diff --git a/radio/src/targets/taranis/aspi.h b/radio/src/targets/Taranis/aspi.h similarity index 100% rename from radio/src/targets/taranis/aspi.h rename to radio/src/targets/Taranis/aspi.h diff --git a/radio/src/targets/taranis/audio_driver.cpp b/radio/src/targets/Taranis/audio_driver.cpp similarity index 100% rename from radio/src/targets/taranis/audio_driver.cpp rename to radio/src/targets/Taranis/audio_driver.cpp diff --git a/radio/src/targets/taranis/bluetooth_driver.cpp b/radio/src/targets/Taranis/bluetooth_driver.cpp similarity index 95% rename from radio/src/targets/taranis/bluetooth_driver.cpp rename to radio/src/targets/Taranis/bluetooth_driver.cpp index 65789778c..9490df75d 100644 --- a/radio/src/targets/taranis/bluetooth_driver.cpp +++ b/radio/src/targets/Taranis/bluetooth_driver.cpp @@ -8,9 +8,9 @@ * - */ -#include "board_taranis.h" #include "string.h" #include "../../fifo.h" +#include "../Taranis/board_taranis.h" Fifo<64> btTxFifo; Fifo<64> btRxFifo; diff --git a/radio/src/targets/taranis/board_taranis.cpp b/radio/src/targets/Taranis/board_taranis.cpp similarity index 100% rename from radio/src/targets/taranis/board_taranis.cpp rename to radio/src/targets/Taranis/board_taranis.cpp diff --git a/radio/src/targets/taranis/board_taranis.h b/radio/src/targets/Taranis/board_taranis.h similarity index 95% rename from radio/src/targets/taranis/board_taranis.h rename to radio/src/targets/Taranis/board_taranis.h index 9430d7beb..89296cd5c 100644 --- a/radio/src/targets/taranis/board_taranis.h +++ b/radio/src/targets/Taranis/board_taranis.h @@ -78,13 +78,13 @@ extern "C" { #include "usbd_msc_core.h" #include "usbd_hid_core.h" #include "usbd_usr.h" - #include "usbd_desc.h" - #include "usb_conf.h" - #include "usbd_conf.h" + #include "../Taranis/usbd_desc.h" + #include "../Taranis/usb_conf.h" + #include "../Taranis/usbd_conf.h" #endif -#include "hal.h" -#include "aspi.h" +#include "../Taranis/hal.h" +#include "../Taranis/aspi.h" #if defined(__cplusplus) && !defined(SIMU) } diff --git a/radio/src/targets/taranis/bootloader/.gitignore b/radio/src/targets/Taranis/bootloader/.gitignore similarity index 100% rename from radio/src/targets/taranis/bootloader/.gitignore rename to radio/src/targets/Taranis/bootloader/.gitignore diff --git a/radio/src/targets/taranis/bootloader/Makefile b/radio/src/targets/Taranis/bootloader/Makefile similarity index 100% rename from radio/src/targets/taranis/bootloader/Makefile rename to radio/src/targets/Taranis/bootloader/Makefile diff --git a/radio/src/targets/taranis/bootloader/boot.cpp b/radio/src/targets/Taranis/bootloader/boot.cpp similarity index 98% rename from radio/src/targets/taranis/bootloader/boot.cpp rename to radio/src/targets/Taranis/bootloader/boot.cpp index 2460dd7fe..1400fc4bc 100644 --- a/radio/src/targets/taranis/bootloader/boot.cpp +++ b/radio/src/targets/Taranis/bootloader/boot.cpp @@ -53,8 +53,8 @@ #include #include -#include "board_taranis.h" -#include "eeprom_rlc.h" +#include "../../Taranis/board_taranis.h" +#include "storage/eeprom_rlc.h" #include "pwr.h" #include "gui/Taranis/lcd.h" #include "keys.h" @@ -156,7 +156,7 @@ extern void usbPluggedIn(); #if defined(REV9E) typedef int32_t rotenc_t; -extern rotenc_t x9de_rotenc; +extern rotenc_t rotencValue; #endif void interrupt10ms(void) @@ -174,7 +174,7 @@ void interrupt10ms(void) #if defined(REV9E) checkRotaryEncoder(); static rotenc_t rePreviousValue; - rotenc_t reNewValue = (x9de_rotenc / 2); + rotenc_t reNewValue = (rotencValue / 2); int8_t scrollRE = reNewValue - rePreviousValue; if (scrollRE) { rePreviousValue = reNewValue; @@ -627,7 +627,7 @@ int main() progress = (200*eepromWritten) / EESIZE; } - lcd_rect( 3, 6*FH+4, 204, 7); + lcdDrawRect( 3, 6*FH+4, 204, 7); lcd_hline(5, 6*FH+6, progress, FORCE); lcd_hline(5, 6*FH+7, progress, FORCE); lcd_hline(5, 6*FH+8, progress, FORCE); diff --git a/radio/src/targets/taranis/bootloader/init.c b/radio/src/targets/Taranis/bootloader/init.c similarity index 100% rename from radio/src/targets/taranis/bootloader/init.c rename to radio/src/targets/Taranis/bootloader/init.c diff --git a/radio/src/targets/taranis/configure_pins.cpp b/radio/src/targets/Taranis/configure_pins.cpp similarity index 100% rename from radio/src/targets/taranis/configure_pins.cpp rename to radio/src/targets/Taranis/configure_pins.cpp diff --git a/radio/src/targets/taranis/delays.c b/radio/src/targets/Taranis/delays.c similarity index 94% rename from radio/src/targets/taranis/delays.c rename to radio/src/targets/Taranis/delays.c index 756db1ed7..6d32f8e86 100644 --- a/radio/src/targets/taranis/delays.c +++ b/radio/src/targets/Taranis/delays.c @@ -34,7 +34,7 @@ * */ -#include "board_taranis.h" +#include "../Taranis/board_taranis.h" void delaysInit(void) { diff --git a/radio/src/targets/taranis/diskio.cpp b/radio/src/targets/Taranis/diskio.cpp similarity index 96% rename from radio/src/targets/taranis/diskio.cpp rename to radio/src/targets/Taranis/diskio.cpp index 5f6bb1bd5..b91501cd5 100644 --- a/radio/src/targets/taranis/diskio.cpp +++ b/radio/src/targets/Taranis/diskio.cpp @@ -35,12 +35,12 @@ */ #include -#include "board_taranis.h" #include "../../thirdparty/FatFs/diskio.h" #include "../../thirdparty/FatFs/ff.h" #include "../../thirdparty/CoOS/kernel/CoOS.h" -#include "hal.h" #include "../../debug.h" +#include "../Taranis/board_taranis.h" +#include "../Taranis/hal.h" /* Definitions for MMC/SDC command */ #define CMD0 (0x40+0) /* GO_IDLE_STATE */ diff --git a/radio/src/targets/taranis/flash_driver.cpp b/radio/src/targets/Taranis/flash_driver.cpp similarity index 100% rename from radio/src/targets/taranis/flash_driver.cpp rename to radio/src/targets/Taranis/flash_driver.cpp diff --git a/radio/src/targets/taranis/hal.h b/radio/src/targets/Taranis/hal.h similarity index 100% rename from radio/src/targets/taranis/hal.h rename to radio/src/targets/Taranis/hal.h diff --git a/radio/src/targets/taranis/haptic_driver.cpp b/radio/src/targets/Taranis/haptic_driver.cpp similarity index 100% rename from radio/src/targets/taranis/haptic_driver.cpp rename to radio/src/targets/Taranis/haptic_driver.cpp diff --git a/radio/src/targets/taranis/i2c_driver.cpp b/radio/src/targets/Taranis/i2c_driver.cpp similarity index 96% rename from radio/src/targets/taranis/i2c_driver.cpp rename to radio/src/targets/Taranis/i2c_driver.cpp index 3655e3730..2686a0de3 100644 --- a/radio/src/targets/taranis/i2c_driver.cpp +++ b/radio/src/targets/Taranis/i2c_driver.cpp @@ -1,4 +1,4 @@ -#include "board_taranis.h" +#include "../Taranis/board_taranis.h" void eepromPageWrite(uint8_t* pBuffer, uint16_t WriteAddr, uint8_t NumByteToWrite); void eepromWaitEepromStandbyState(void); diff --git a/radio/src/targets/taranis/keys_driver.cpp b/radio/src/targets/Taranis/keys_driver.cpp similarity index 95% rename from radio/src/targets/taranis/keys_driver.cpp rename to radio/src/targets/Taranis/keys_driver.cpp index a9f7d72b6..6e824b32b 100644 --- a/radio/src/targets/taranis/keys_driver.cpp +++ b/radio/src/targets/Taranis/keys_driver.cpp @@ -99,7 +99,7 @@ uint8_t keyDown() #if defined(REV9E) uint32_t Rotary_position; -rotenc_t x9de_rotenc; +rotenc_t rotencValue; void checkRotaryEncoder() { register uint32_t dummy ; @@ -111,11 +111,11 @@ void checkRotaryEncoder() { if ( ( Rotary_position & 0x01 ) ^ ( ( dummy & 0x02) >> 1 ) ) { - --x9de_rotenc; + --rotencValue; } else { - ++x9de_rotenc; + ++rotencValue; } Rotary_position &= ~0x03 ; Rotary_position |= dummy ; @@ -135,7 +135,7 @@ void readKeysAndTrims() #if defined(REV9E) && !defined(SIMU) #define X9E_RE_TIMEOUT 5 static rotenc_t rePreviousValue; - rotenc_t reNewValue = (x9de_rotenc / 2); + rotenc_t reNewValue = (rotencValue / 2); static int timeout = X9E_RE_TIMEOUT; static int lastkey = 0; int8_t scrollRE = reNewValue - rePreviousValue; diff --git a/radio/src/targets/taranis/lcd_driver.cpp b/radio/src/targets/Taranis/lcd_driver.cpp similarity index 100% rename from radio/src/targets/taranis/lcd_driver.cpp rename to radio/src/targets/Taranis/lcd_driver.cpp diff --git a/radio/src/targets/taranis/pulses_driver.cpp b/radio/src/targets/Taranis/pulses_driver.cpp similarity index 100% rename from radio/src/targets/taranis/pulses_driver.cpp rename to radio/src/targets/Taranis/pulses_driver.cpp diff --git a/radio/src/targets/taranis/pwr_driver.c b/radio/src/targets/Taranis/pwr_driver.c similarity index 95% rename from radio/src/targets/taranis/pwr_driver.c rename to radio/src/targets/Taranis/pwr_driver.c index 6ad746999..e9b17cfd2 100644 --- a/radio/src/targets/taranis/pwr_driver.c +++ b/radio/src/targets/Taranis/pwr_driver.c @@ -34,8 +34,8 @@ * */ -#include "board_taranis.h" #include "../../pwr.h" +#include "../Taranis/board_taranis.h" void pwrInit() { diff --git a/radio/src/targets/taranis/rtc_driver.cpp b/radio/src/targets/Taranis/rtc_driver.cpp similarity index 100% rename from radio/src/targets/taranis/rtc_driver.cpp rename to radio/src/targets/Taranis/rtc_driver.cpp diff --git a/radio/src/targets/taranis/serial2_driver.cpp b/radio/src/targets/Taranis/serial2_driver.cpp similarity index 100% rename from radio/src/targets/taranis/serial2_driver.cpp rename to radio/src/targets/Taranis/serial2_driver.cpp diff --git a/radio/src/targets/taranis/stm32_ramboot.ld b/radio/src/targets/Taranis/stm32_ramboot.ld similarity index 100% rename from radio/src/targets/taranis/stm32_ramboot.ld rename to radio/src/targets/Taranis/stm32_ramboot.ld diff --git a/radio/src/targets/taranis/stm32f2_flash.ld b/radio/src/targets/Taranis/stm32f2_flash.ld similarity index 100% rename from radio/src/targets/taranis/stm32f2_flash.ld rename to radio/src/targets/Taranis/stm32f2_flash.ld diff --git a/radio/src/targets/taranis/stm32f4_flash.ld b/radio/src/targets/Taranis/stm32f4_flash.ld similarity index 100% rename from radio/src/targets/taranis/stm32f4_flash.ld rename to radio/src/targets/Taranis/stm32f4_flash.ld diff --git a/radio/src/targets/taranis/system_stm32f2xx.c b/radio/src/targets/Taranis/system_stm32f2xx.c similarity index 100% rename from radio/src/targets/taranis/system_stm32f2xx.c rename to radio/src/targets/Taranis/system_stm32f2xx.c diff --git a/radio/src/targets/taranis/system_stm32f4xx.c b/radio/src/targets/Taranis/system_stm32f4xx.c similarity index 100% rename from radio/src/targets/taranis/system_stm32f4xx.c rename to radio/src/targets/Taranis/system_stm32f4xx.c diff --git a/radio/src/targets/taranis/telemetry_driver.cpp b/radio/src/targets/Taranis/telemetry_driver.cpp similarity index 100% rename from radio/src/targets/taranis/telemetry_driver.cpp rename to radio/src/targets/Taranis/telemetry_driver.cpp diff --git a/radio/src/targets/taranis/top_lcd_driver.cpp b/radio/src/targets/Taranis/top_lcd_driver.cpp similarity index 99% rename from radio/src/targets/taranis/top_lcd_driver.cpp rename to radio/src/targets/Taranis/top_lcd_driver.cpp index e6e86c54b..938c602ee 100644 --- a/radio/src/targets/taranis/top_lcd_driver.cpp +++ b/radio/src/targets/Taranis/top_lcd_driver.cpp @@ -34,7 +34,7 @@ * */ -#include "board_taranis.h" +#include "../Taranis/board_taranis.h" #define CS1_HIGH() TOPLCD_GPIO->BSRRL = TOPLCD_GPIO_PIN_CS1 #define CS1_LOW() TOPLCD_GPIO->BSRRH = TOPLCD_GPIO_PIN_CS1 diff --git a/radio/src/targets/taranis/trainer_driver.cpp b/radio/src/targets/Taranis/trainer_driver.cpp similarity index 100% rename from radio/src/targets/taranis/trainer_driver.cpp rename to radio/src/targets/Taranis/trainer_driver.cpp diff --git a/radio/src/targets/taranis/usb_bsp.c b/radio/src/targets/Taranis/usb_bsp.c similarity index 95% rename from radio/src/targets/taranis/usb_bsp.c rename to radio/src/targets/Taranis/usb_bsp.c index eefa412a2..37bc90703 100644 --- a/radio/src/targets/taranis/usb_bsp.c +++ b/radio/src/targets/Taranis/usb_bsp.c @@ -28,9 +28,10 @@ /* Includes ------------------------------------------------------------------*/ -#include "board_taranis.h" #include "usb_bsp.h" -#include "usbd_conf.h" + +#include "../Taranis/board_taranis.h" +#include "../Taranis/usbd_conf.h" extern uint32_t SystemCoreClock; diff --git a/radio/src/targets/taranis/usb_conf.h b/radio/src/targets/Taranis/usb_conf.h similarity index 100% rename from radio/src/targets/taranis/usb_conf.h rename to radio/src/targets/Taranis/usb_conf.h diff --git a/radio/src/targets/taranis/usb_driver.c b/radio/src/targets/Taranis/usb_driver.c similarity index 98% rename from radio/src/targets/taranis/usb_driver.c rename to radio/src/targets/Taranis/usb_driver.c index 9233962b1..aff638720 100644 --- a/radio/src/targets/taranis/usb_driver.c +++ b/radio/src/targets/Taranis/usb_driver.c @@ -34,7 +34,7 @@ * */ -#include "board_taranis.h" +#include "../Taranis/board_taranis.h" #include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_dcd_int.h" int usbPlugged(void) diff --git a/radio/src/targets/taranis/usbd_cdc.cpp b/radio/src/targets/Taranis/usbd_cdc.cpp similarity index 96% rename from radio/src/targets/taranis/usbd_cdc.cpp rename to radio/src/targets/Taranis/usbd_cdc.cpp index 7b1a44634..fec1e02d7 100644 --- a/radio/src/targets/taranis/usbd_cdc.cpp +++ b/radio/src/targets/Taranis/usbd_cdc.cpp @@ -34,7 +34,7 @@ extern "C" { /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" +#include "../Taranis/usb_conf.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ diff --git a/radio/src/targets/taranis/usbd_conf.h b/radio/src/targets/Taranis/usbd_conf.h similarity index 96% rename from radio/src/targets/taranis/usbd_conf.h rename to radio/src/targets/Taranis/usbd_conf.h index 557973a26..f797f4ce1 100644 --- a/radio/src/targets/taranis/usbd_conf.h +++ b/radio/src/targets/Taranis/usbd_conf.h @@ -30,7 +30,7 @@ #define __USBD_CONF__H__ /* Includes ------------------------------------------------------------------*/ -#include "usb_conf.h" +#include "../Taranis/usb_conf.h" #define USBD_CFG_MAX_NUM 1 #define USBD_ITF_MAX_NUM 1 diff --git a/radio/src/targets/taranis/usbd_desc.c b/radio/src/targets/Taranis/usbd_desc.c similarity index 96% rename from radio/src/targets/taranis/usbd_desc.c rename to radio/src/targets/Taranis/usbd_desc.c index 2c2011255..303c04779 100644 --- a/radio/src/targets/taranis/usbd_desc.c +++ b/radio/src/targets/Taranis/usbd_desc.c @@ -27,12 +27,12 @@ /* Includes ------------------------------------------------------------------*/ #include -#include "usbd_core.h" #include "usbd_desc.h" -#include "usbd_req.h" -#include "usbd_conf.h" -#include "usb_regs.h" #include "board_taranis.h" +#include "usbd_conf.h" +#include "usbd_core.h" +#include "usbd_req.h" +#include "usb_regs.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/targets/taranis/usbd_desc.h b/radio/src/targets/Taranis/usbd_desc.h similarity index 94% rename from radio/src/targets/taranis/usbd_desc.h rename to radio/src/targets/Taranis/usbd_desc.h index 7a9d5b564..537f69b14 100644 --- a/radio/src/targets/taranis/usbd_desc.h +++ b/radio/src/targets/Taranis/usbd_desc.h @@ -31,7 +31,8 @@ #define __USB_DESC_H /* Includes ------------------------------------------------------------------*/ -#include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_def.h" +#include "usb_core.h" +#include "usbd_def.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/targets/taranis/usbd_hid_joystick.c b/radio/src/targets/Taranis/usbd_hid_joystick.c similarity index 96% rename from radio/src/targets/taranis/usbd_hid_joystick.c rename to radio/src/targets/Taranis/usbd_hid_joystick.c index 33a6c3d85..ed6706e5e 100644 --- a/radio/src/targets/taranis/usbd_hid_joystick.c +++ b/radio/src/targets/Taranis/usbd_hid_joystick.c @@ -47,9 +47,9 @@ */ /* Includes ------------------------------------------------------------------*/ +#include "../Taranis/usbd_desc.h" #include "usbd_hid_core.h" #include "usbd_req.h" -#include "usbd_desc.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/targets/taranis/usbd_storage_msd.cpp b/radio/src/targets/Taranis/usbd_storage_msd.cpp similarity index 96% rename from radio/src/targets/taranis/usbd_storage_msd.cpp rename to radio/src/targets/Taranis/usbd_storage_msd.cpp index 51cccc788..1a4904ca0 100644 --- a/radio/src/targets/taranis/usbd_storage_msd.cpp +++ b/radio/src/targets/Taranis/usbd_storage_msd.cpp @@ -28,14 +28,14 @@ /* Includes ------------------------------------------------------------------*/ #include "../../opentx.h" #include "../../thirdparty/FatFs/diskio.h" -#include "board_taranis.h" +#include "../Taranis/board_taranis.h" #if defined(__cplusplus) && !defined(SIMU) extern "C" { #endif #include "usbd_msc_mem.h" -#include "usb_conf.h" +#include "../Taranis/usb_conf.h" #if defined(BOOT) #define STORAGE_LUN_NBR 2 diff --git a/radio/src/targets/taranis/usbd_usr.cpp b/radio/src/targets/Taranis/usbd_usr.cpp similarity index 100% rename from radio/src/targets/taranis/usbd_usr.cpp rename to radio/src/targets/Taranis/usbd_usr.cpp diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 27002ae39..548dfc9ed 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -362,9 +362,7 @@ void *main_thread(void *) g_menuStack[0] = menuMainView; g_menuStack[1] = menuModelSelect; -#if defined(EEPROM) - eeReadAll(); // load general setup and selected model -#endif + storageReadAll(); // load general setup and selected model #if defined(SIMU_DISKIO) f_mount(&g_FATFS_Obj, "", 1); @@ -381,11 +379,6 @@ void *main_thread(void *) if (main_thread_running == 1) { opentxStart(); } - else { -#if defined(CPUARM) - eeLoadModel(g_eeGeneral.currModel); -#endif - } s_current_protocol[0] = 0; @@ -947,26 +940,37 @@ FRESULT f_readdir (DIR * rep, FILINFO * fil) return FR_OK; } -FRESULT f_mkfs (const TCHAR *path, BYTE, UINT) +FRESULT f_mkfs (const TCHAR * path, BYTE, UINT) { TRACE("Format SD..."); return FR_OK; } -FRESULT f_mkdir (const TCHAR*) +FRESULT f_mkdir (const TCHAR * name) { + char * path = convertSimuPath(name); + if (mkdir(path, 0777)) { + TRACE("mkdir(%s) = error %d (%s)", path, errno, strerror(errno)); + return FR_INVALID_NAME; + } + else { + TRACE("mkdir(%s) = OK", path); + return FR_OK; + } return FR_OK; } -FRESULT f_unlink (const TCHAR* name) +FRESULT f_unlink (const TCHAR * name) { - char *path = convertSimuPath(name); + char * path = convertSimuPath(name); if (unlink(path)) { TRACE("f_unlink(%s) = error %d (%s)", path, errno, strerror(errno)); return FR_INVALID_NAME; } - TRACE("f_unlink(%s) = OK", path); - return FR_OK; + else { + TRACE("f_unlink(%s) = OK", path); + return FR_OK; + } } FRESULT f_rename(const TCHAR *oldname, const TCHAR *newname) @@ -1222,10 +1226,8 @@ uint32_t sdGetSpeed() #endif // #if defined(SIMU_DISKIO) - - bool lcd_refresh = true; -display_t lcd_buf[DISPLAY_BUF_SIZE]; +display_t lcd_buf[DISPLAY_BUFFER_SIZE]; #if !defined(PCBFLAMENCO) && !defined(PCBHORUS) void lcdSetRefVolt(uint8_t val) diff --git a/radio/src/telemetry/ardupilot.cpp b/radio/src/telemetry/ardupilot.cpp index 256553b75..50f22d970 100644 --- a/radio/src/telemetry/ardupilot.cpp +++ b/radio/src/telemetry/ardupilot.cpp @@ -699,7 +699,7 @@ void menuTelemetryArduPilot8(uint8_t event) void title(char x) { lcd_putsAtt (0, 0, PSTR(" ARDU PILOT Mega ?/8 "), INVERS); - lcd_putcAtt(17*FW, 0*FH, x, INVERS); + lcdDrawChar(17*FW, 0*FH, x, INVERS); } void initval(uint8_t num, uint8_t pack, uint8_t val) diff --git a/radio/src/telemetry/frsky_d_arm.cpp b/radio/src/telemetry/frsky_d_arm.cpp index eadcae3fa..664a8cec1 100644 --- a/radio/src/telemetry/frsky_d_arm.cpp +++ b/radio/src/telemetry/frsky_d_arm.cpp @@ -300,5 +300,5 @@ void frskyDSetDefault(int index, uint16_t id) telemetrySensor.init(id); } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } diff --git a/radio/src/telemetry/frsky_sport.cpp b/radio/src/telemetry/frsky_sport.cpp index 1394d9081..6be1a96c7 100644 --- a/radio/src/telemetry/frsky_sport.cpp +++ b/radio/src/telemetry/frsky_sport.cpp @@ -294,7 +294,7 @@ void frskySportSetDefault(int index, uint16_t id, uint8_t instance) telemetrySensor.init(id); } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } #if defined(PCBTARANIS) diff --git a/radio/src/telemetry/jeti.cpp b/radio/src/telemetry/jeti.cpp index 4a56eb644..11c235833 100644 --- a/radio/src/telemetry/jeti.cpp +++ b/radio/src/telemetry/jeti.cpp @@ -211,8 +211,8 @@ void menuTelemetryJeti(uint8_t event) lcdDrawTelemetryTopBar(); for (uint8_t i=0; i<16; i++) { - lcd_putcAtt((i+2)*FW, 3*FH, jetiRxBuffer[i], BSS); - lcd_putcAtt((i+2)*FW, 4*FH, jetiRxBuffer[i+16], BSS); + lcdDrawChar((i+2)*FW, 3*FH, jetiRxBuffer[i], BSS); + lcdDrawChar((i+2)*FW, 4*FH, jetiRxBuffer[i+16], BSS); } if (event == EVT_KEY_FIRST(KEY_EXIT)) { diff --git a/radio/src/telemetry/mavlink.cpp b/radio/src/telemetry/mavlink.cpp index 094c94ec3..c5c04e16e 100644 --- a/radio/src/telemetry/mavlink.cpp +++ b/radio/src/telemetry/mavlink.cpp @@ -341,12 +341,12 @@ void putsMavlinParams(uint8_t x, uint8_t y, uint8_t idx, uint8_t att) { const pm_char * s = getParamId(idx); char c; while ((c = pgm_read_byte(s++))) { - lcd_putcAtt(x, y, (c == '_' ? ' ' : c), 0); + lcdDrawChar(x, y, (c == '_' ? ' ' : c), 0); x += FW; } if (idx < NB_PID_PARAMS) { x = 11 * FW; - lcd_putcAtt(x, y, "PI"[idx & 0x01], att); + lcdDrawChar(x, y, "PI"[idx & 0x01], att); } } } diff --git a/radio/src/telemetry/nmea.cpp b/radio/src/telemetry/nmea.cpp index 3630602e5..4adf107f3 100644 --- a/radio/src/telemetry/nmea.cpp +++ b/radio/src/telemetry/nmea.cpp @@ -463,11 +463,11 @@ void menuTelemetryNMEA1(uint8_t event) lcd_puts ( 2*FW, 1*FH, PSTR("UTC-Time Sat")); if (rbuf[0][0]) { // show always if data have been received - lcd_putcAtt ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view + lcdDrawChar ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view lcd_putsnAtt ( 2*FW, 2*FH, &rbuf[0][0], 2, APSIZE); // hours - lcd_putcAtt ( 6*FW, 2*FH, ':', DBLSIZE); // ":" + lcdDrawChar ( 6*FW, 2*FH, ':', DBLSIZE); // ":" lcd_putsnAtt ( 8*FW, 2*FH, &rbuf[0][2], 2, APSIZE); // minutes - lcd_putcAtt ( 12*FW, 2*FH, ':', DBLSIZE); // ":" + lcdDrawChar ( 12*FW, 2*FH, ':', DBLSIZE); // ":" lcd_putsnAtt ( 14*FW, 2*FH, &rbuf[0][4], 2, APSIZE); // seconds } else @@ -484,9 +484,9 @@ void menuTelemetryNMEA1(uint8_t event) if (rbuf[1][0]) { lcd_putsnAtt( 2*FW, 5*FH, &rbuf[1][0], 2, APSIZE); // year - lcd_putcAtt ( 6*FW, 5*FH, '/', DBLSIZE); // "/" + lcdDrawChar ( 6*FW, 5*FH, '/', DBLSIZE); // "/" lcd_putsnAtt( 8*FW, 5*FH, &rbuf[1][2], 2, APSIZE); // month - lcd_putcAtt (12*FW, 5*FH, '/', DBLSIZE); // "/" + lcdDrawChar (12*FW, 5*FH, '/', DBLSIZE); // "/" lcd_putsnAtt(14*FW, 5*FH, &rbuf[1][4], 2, APSIZE); // day } else @@ -630,7 +630,7 @@ void menuTelemetryNMEA2(uint8_t event) } if (rbuf[0][0]) { - lcd_putcAtt ( 13*FW, 1*FH, sbuf[2], 0); // satellites in view + lcdDrawChar ( 13*FW, 1*FH, sbuf[2], 0); // satellites in view if (sbuf[1]>0x30) { // & GGA has FIX > 0 @@ -643,10 +643,10 @@ void menuTelemetryNMEA2(uint8_t event) lcd_outdezNAtt( 20*FW, 2*FH, max_alt, PREC1, 6); // display small characters - lcd_putcAtt ( 11*FW, 3*FH, sbuf[0], 0); // dimension [m] + lcdDrawChar ( 11*FW, 3*FH, sbuf[0], 0); // dimension [m] lcd_outdezNAtt( 10*FW, 5*FH, lift_alt, DBLSIZE|PREC1, 6); // lift - lcd_putcAtt ( 11*FW, 6*FH, sbuf[0], 0); // dimension [m/S] + lcdDrawChar ( 11*FW, 6*FH, sbuf[0], 0); // dimension [m/S] lcd_puts ( 12*FW, 6*FH, PSTR("/S") ); } } @@ -699,7 +699,7 @@ void menuTelemetryNMEA3(uint8_t event) else lcd_putsAtt ( 2*FW, 2*FH, val_unknown, APSIZE); - lcd_putcAtt ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view + lcdDrawChar ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view lcd_puts ( 1*FW, 4*FH, PSTR("Course over ground") ); lcd_putsAtt ( 2*FW, 5*FH, VALSTR(1), APSIZE); // course over ground @@ -739,10 +739,10 @@ void menuTelemetryNMEA4(uint8_t event) // first buffer into line 2 column 2 if (rbuf[0][0]) { - lcd_putcAtt ( 13*FW, 1*FH, sbuf[0], 0); // N or S - lcd_putcAtt ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view + lcdDrawChar ( 13*FW, 1*FH, sbuf[0], 0); // N or S + lcdDrawChar ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view lcd_putsnAtt ( 1*FW, 2*FH, rbuf[0], 2, APSIZE); - lcd_putcAtt ( 5*FW, 2*FH, '@',0); + lcdDrawChar ( 5*FW, 2*FH, '@',0); lcd_putsAtt ( 6*FW, 2*FH, &rbuf[0][2], APSIZE); // minutes with small decimal point } else @@ -751,9 +751,9 @@ void menuTelemetryNMEA4(uint8_t event) // second buffer into line 5 column 2 if (rbuf[0][0]) { - lcd_putcAtt ( 13*FW, 4*FH, sbuf[1], 0); // E or W + lcdDrawChar ( 13*FW, 4*FH, sbuf[1], 0); // E or W lcd_putsnAtt ( 0*FW, 5*FH, rbuf[1], 3, APSIZE); - lcd_putcAtt ( 6*FW, 5*FH, '@',0); + lcdDrawChar ( 6*FW, 5*FH, '@',0); lcd_putsAtt ( 7*FW, 5*FH, &rbuf[1][3], APSIZE); // minutes with small decimal point } @@ -764,7 +764,7 @@ void menuTelemetryNMEA4(uint8_t event) void title(char x) { lcd_putsAtt (0*FW, 0*FH, PSTR(" GPS NMEA data ?/4 "), INVERS); - lcd_putcAtt(16*FW, 0*FH, x, INVERS); + lcdDrawChar(16*FW, 0*FH, x, INVERS); } void initval(uint8_t num, uint8_t pack, uint8_t val) diff --git a/radio/src/telemetry/telemetry.cpp b/radio/src/telemetry/telemetry.cpp index 872e389c2..7fdad03e4 100644 --- a/radio/src/telemetry/telemetry.cpp +++ b/radio/src/telemetry/telemetry.cpp @@ -458,7 +458,7 @@ void delTelemetryIndex(uint8_t index) { memclear(&g_model.telemetrySensors[index], sizeof(TelemetrySensor)); telemetryItems[index].clear(); - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } int availableTelemetryIndex() diff --git a/radio/src/templates.cpp b/radio/src/templates.cpp index 2bb663e03..12a152697 100644 --- a/radio/src/templates.cpp +++ b/radio/src/templates.cpp @@ -270,5 +270,5 @@ void applyTemplate(uint8_t idx) } - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } diff --git a/radio/src/tests/eeprom.cpp b/radio/src/tests/eeprom.cpp index bf0a5536f..1ad3bddbe 100644 --- a/radio/src/tests/eeprom.cpp +++ b/radio/src/tests/eeprom.cpp @@ -44,7 +44,7 @@ TEST(Eeprom, 100_random_writes) uint8_t buf[1000]; uint8_t buf2[1000]; - eepromFormat(); + storageFormat(); for(int i=0; i<100; i++) { int size = rand()%800; @@ -66,7 +66,7 @@ TEST(Eeprom, test2) RlcFile f; uint8_t buf[1000]; - eepromFormat(); + storageFormat(); for(int i=0; i<1000; i++) buf[i]='6'+i%4; @@ -83,19 +83,19 @@ TEST(Eeprom, test2) EXPECT_EQ(sz, 300); } -TEST(Eeprom, eeCheckImmediately) +TEST(Eeprom, storageCheckImmediately) { eepromFile = NULL; // in memory // RlcFile f; uint8_t buf[1000]; - eepromFormat(); + storageFormat(); for(int i=0; i<1000; i++) buf[i]='6'+i%4; theFile.writeRlc(6, 6, buf, 300, false); - eeCheck(true); + storageCheck(true); theFile.openRd(6); uint16_t sz=0; @@ -114,7 +114,7 @@ TEST(Eeprom, copy) uint8_t buf[1000]; - eepromFormat(); + storageFormat(); for(int i=0; i<1000; i++) buf[i]='6'+i%4; @@ -139,7 +139,7 @@ TEST(Eeprom, rm) uint8_t buf[1000]; - eepromFormat(); + storageFormat(); for(int i=0; i<1000; i++) buf[i]='6'+i%4; diff --git a/radio/src/tests/lcd.cpp b/radio/src/tests/lcd.cpp index dbba72798..8d3521112 100644 --- a/radio/src/tests/lcd.cpp +++ b/radio/src/tests/lcd.cpp @@ -306,28 +306,28 @@ TEST(Lcd, BMPWrapping) #endif #if defined(PCBTARANIS) -TEST(Lcd, lcd_hlineStip) +TEST(Lcd, lcdDrawHorizontalLine) { lcd_clear(); - lcd_hlineStip(0, 10, LCD_W, DOTTED); - lcd_hlineStip(0, 20, LCD_W, SOLID); - lcd_hlineStip(50, 30, LCD_W, 0xEE); //too wide - lcd_hlineStip(50, LCD_H + 10, 20, SOLID); //too low - lcd_hlineStip(250, 30, LCD_W, SOLID); //x outside display - EXPECT_TRUE(checkScreenshot("lcd_hlineStip")); + lcdDrawHorizontalLine(0, 10, LCD_W, DOTTED); + lcdDrawHorizontalLine(0, 20, LCD_W, SOLID); + lcdDrawHorizontalLine(50, 30, LCD_W, 0xEE); //too wide + lcdDrawHorizontalLine(50, LCD_H + 10, 20, SOLID); //too low + lcdDrawHorizontalLine(250, 30, LCD_W, SOLID); //x outside display + EXPECT_TRUE(checkScreenshot("lcdDrawHorizontalLine")); } #endif #if defined(PCBTARANIS) -TEST(Lcd, lcd_vlineStip) +TEST(Lcd, lcdDrawVerticalLine) { lcd_clear(); - lcd_vlineStip(10, 0, LCD_H, DOTTED); - lcd_vlineStip(20, 0, LCD_H, SOLID); - lcd_vlineStip(30, 30, LCD_H, 0xEE); //too high - lcd_vlineStip(40, LCD_H + 10, 20, SOLID); //too low - lcd_vlineStip(250, LCD_H + 10, LCD_H, SOLID); //x outside display - EXPECT_TRUE(checkScreenshot("lcd_vlineStip")); + lcdDrawVerticalLine(10, 0, LCD_H, DOTTED); + lcdDrawVerticalLine(20, 0, LCD_H, SOLID); + lcdDrawVerticalLine(30, 30, LCD_H, 0xEE); //too high + lcdDrawVerticalLine(40, LCD_H + 10, 20, SOLID); //too low + lcdDrawVerticalLine(250, LCD_H + 10, LCD_H, SOLID); //x outside display + EXPECT_TRUE(checkScreenshot("lcdDrawVerticalLine")); } #endif @@ -410,7 +410,7 @@ TEST(Lcd, lcd_bmpLoadAndDisplay) #endif #if defined(PCBTARANIS) -TEST(Lcd, lcd_line) +TEST(Lcd, lcdDrawLine) { int start, length, xOffset; uint8_t pattern; @@ -421,57 +421,57 @@ TEST(Lcd, lcd_line) pattern = SOLID; length = 40; xOffset = 0; - lcd_line(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); - lcd_line(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); + lcdDrawLine(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); + lcdDrawLine(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); start = 10; pattern = DOTTED; length = 40; xOffset = 0; - lcd_line(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); - lcd_line(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); + lcdDrawLine(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); + lcdDrawLine(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); start = 55; pattern = SOLID; length = -40; xOffset = 80; - lcd_line(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); - lcd_line(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); + lcdDrawLine(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); + lcdDrawLine(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); start = 50; pattern = DOTTED; length = -40; xOffset = 80; - lcd_line(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); - lcd_line(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); + lcdDrawLine(start+(length>0?1:-1)+xOffset, start, start+(length>0?1:-1)+xOffset+length, start, pattern, 0); + lcdDrawLine(start+xOffset, start+(length>0?1:-1), start+xOffset, start+(length>0?1:-1)+length, pattern, 0); // 45 deg lines - lcd_line( 35, 40, 45, 40, SOLID, FORCE ); - lcd_line( 40, 35, 40, 45, SOLID, FORCE ); + lcdDrawLine( 35, 40, 45, 40, SOLID, FORCE ); + lcdDrawLine( 40, 35, 40, 45, SOLID, FORCE ); - lcd_line( 20, 40, 40, 20, SOLID, FORCE ); - lcd_line( 40, 20, 60, 40, SOLID, FORCE ); - lcd_line( 60, 40, 40, 60, SOLID, FORCE ); - lcd_line( 40, 60, 20, 40, SOLID, FORCE ); + lcdDrawLine( 20, 40, 40, 20, SOLID, FORCE ); + lcdDrawLine( 40, 20, 60, 40, SOLID, FORCE ); + lcdDrawLine( 60, 40, 40, 60, SOLID, FORCE ); + lcdDrawLine( 40, 60, 20, 40, SOLID, FORCE ); - lcd_line( 31, 39, 39, 31, SOLID, FORCE ); - lcd_line( 41, 31, 49, 39, SOLID, FORCE ); - lcd_line( 49, 41, 41, 49, SOLID, FORCE ); - lcd_line( 39, 49, 31, 41, SOLID, FORCE ); + lcdDrawLine( 31, 39, 39, 31, SOLID, FORCE ); + lcdDrawLine( 41, 31, 49, 39, SOLID, FORCE ); + lcdDrawLine( 49, 41, 41, 49, SOLID, FORCE ); + lcdDrawLine( 39, 49, 31, 41, SOLID, FORCE ); // slanted lines - lcd_line( 150, 10, 190, 10, SOLID, FORCE ); - lcd_line( 150, 10, 190, 20, SOLID, FORCE ); - lcd_line( 150, 10, 190, 30, SOLID, FORCE ); - lcd_line( 150, 10, 190, 40, SOLID, FORCE ); - lcd_line( 150, 10, 190, 50, SOLID, FORCE ); + lcdDrawLine( 150, 10, 190, 10, SOLID, FORCE ); + lcdDrawLine( 150, 10, 190, 20, SOLID, FORCE ); + lcdDrawLine( 150, 10, 190, 30, SOLID, FORCE ); + lcdDrawLine( 150, 10, 190, 40, SOLID, FORCE ); + lcdDrawLine( 150, 10, 190, 50, SOLID, FORCE ); - lcd_line( 150, 10, 190, 50, SOLID, FORCE ); - lcd_line( 150, 10, 180, 50, SOLID, FORCE ); - lcd_line( 150, 10, 170, 50, SOLID, FORCE ); - lcd_line( 150, 10, 160, 50, SOLID, FORCE ); - lcd_line( 150, 10, 150, 50, SOLID, FORCE ); + lcdDrawLine( 150, 10, 190, 50, SOLID, FORCE ); + lcdDrawLine( 150, 10, 180, 50, SOLID, FORCE ); + lcdDrawLine( 150, 10, 170, 50, SOLID, FORCE ); + lcdDrawLine( 150, 10, 160, 50, SOLID, FORCE ); + lcdDrawLine( 150, 10, 150, 50, SOLID, FORCE ); - EXPECT_TRUE(checkScreenshot("lcd_line")); + EXPECT_TRUE(checkScreenshot("lcdDrawLine")); } #endif diff --git a/radio/src/tests/lcd_hlineStip_212x64.png b/radio/src/tests/lcdDrawHorizontalLine_212x64.png similarity index 100% rename from radio/src/tests/lcd_hlineStip_212x64.png rename to radio/src/tests/lcdDrawHorizontalLine_212x64.png diff --git a/radio/src/tests/lcd_line_212x64.png b/radio/src/tests/lcdDrawLine_212x64.png similarity index 100% rename from radio/src/tests/lcd_line_212x64.png rename to radio/src/tests/lcdDrawLine_212x64.png diff --git a/radio/src/tests/lcd_vlineStip_212x64.png b/radio/src/tests/lcdDrawVerticalLine_212x64.png similarity index 100% rename from radio/src/tests/lcd_vlineStip_212x64.png rename to radio/src/tests/lcdDrawVerticalLine_212x64.png diff --git a/radio/src/timers.cpp b/radio/src/timers.cpp index 4826eea64..589a9c628 100644 --- a/radio/src/timers.cpp +++ b/radio/src/timers.cpp @@ -78,7 +78,7 @@ void saveTimers() TimerState *timerState = &timersStates[i]; if (g_model.timers[i].value != (uint16_t)timerState->val) { g_model.timers[i].value = timerState->val; - eeDirty(EE_MODEL); + storageDirty(EE_MODEL); } } } @@ -86,7 +86,7 @@ void saveTimers() #if defined(CPUARM) && !defined(REVA) if (sessionTimer > 0) { g_eeGeneral.globalTimer += sessionTimer; - eeDirty(EE_GENERAL); + storageDirty(EE_GENERAL); sessionTimer = 0; } #endif diff --git a/radio/util/font2png.py b/radio/util/font2png.py index b99347a64..135671542 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -22,7 +22,10 @@ def getCharWidth(fontsize, metrics, index): return 1 else: rect = metrics.boundingRect(chars[index]) - return rect.width() + 1 + if fontsize >= 24: + return rect.width() + 3 + else: + return rect.width() + 1 def getFontTopBottom(fontsize, metrics): top, bottom = 0, 0 @@ -38,10 +41,11 @@ def getFontWidth(fontsize, metrics): width += getCharWidth(fontsize, metrics, i) return width -def createFontBitmap(filename, fontname, fontsize, foreground, background, coordsfile=True): +def createFontBitmap(filename, fontname, fontsize, fontbold, foreground, background, coordsfile=True): coords = [ ] font = QtGui.QFont(fontname) font.setPixelSize(fontsize) + font.setBold(fontbold) font.setHintingPreference(QtGui.QFont.PreferNoHinting) metrics = QtGui.QFontMetrics(font) width = getFontWidth(fontsize, metrics) @@ -67,7 +71,10 @@ def createFontBitmap(filename, fontname, fontsize, foreground, background, coord painter.drawPoint(width, fontsize); else: rect = metrics.boundingRect(c) - painter.drawText(width-rect.left(), fontsize-2, c) # fontsize-bottom+1 -17 / 7 + if fontsize >= 24: + painter.drawText(width-rect.left()+1, fontsize-2, c) # fontsize-bottom+1 -17 / 7 + else: + painter.drawText(width-rect.left(), fontsize-2, c) # fontsize-bottom+1 -17 / 7 width += getCharWidth(fontsize, metrics, i) if extraWidth: painter.drawImage(QtCore.QPoint(width, 0), extraImage) @@ -87,5 +94,5 @@ def createFontBitmap(filename, fontname, fontsize, foreground, background, coord f.close() return coords -if len(sys.argv) == 4: - createFontBitmap(sys.argv[3], sys.argv[1], float(sys.argv[2]), QtGui.QColor(0x00, 0x00, 0x00), QtGui.QColor(0xFF, 0xFF, 0xFF)) +if __name__ == "__main__": + createFontBitmap(sys.argv[4], sys.argv[1], float(sys.argv[2]), sys.argv[3] == "True", QtGui.QColor(0x00, 0x00, 0x00), QtGui.QColor(0xFF, 0xFF, 0xFF)) From e18e6f9c41a9cefcd52772613b6804da79db42e4 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 22 Oct 2015 17:16:21 +0200 Subject: [PATCH 33/74] [Horus] Refactoring continued --- radio/src/bitmaps/Horus/mask_rscale_p1.png | Bin 453 -> 0 bytes radio/src/bitmaps/Horus/mask_rscale_p13.png | Bin 852 -> 0 bytes radio/src/bitmaps/Horus/mask_rscale_p2.png | Bin 520 -> 0 bytes radio/src/bitmaps/Horus/mask_rscale_p3.png | Bin 318 -> 0 bytes ...pmenu_usb_ico.png => mask_topmenu_usb.png} | Bin radio/src/gui/9X/gui.h | 1 + radio/src/gui/9X/lcd.cpp | 2 +- radio/src/gui/9X/lcd.h | 2 +- radio/src/gui/9X/menus.h | 35 +-- radio/src/gui/9X/navigation.cpp | 10 +- radio/src/gui/9X/popups.cpp | 2 +- radio/src/gui/9X/splash.cpp | 2 +- radio/src/gui/Horus/bitmaps.cpp | 16 ++ radio/src/gui/Horus/gui.h | 5 + radio/src/gui/Horus/lcd.cpp | 155 ++++-------- radio/src/gui/Horus/lcd.h | 40 +-- radio/src/gui/Horus/menu_general_calib.cpp | 32 ++- radio/src/gui/Horus/menus.h | 15 +- radio/src/gui/Horus/navigation.cpp | 6 +- radio/src/gui/Horus/view_main.cpp | 119 ++------- radio/src/gui/Taranis/gui.h | 1 + radio/src/gui/Taranis/lcd.cpp | 2 +- radio/src/gui/Taranis/lcd.h | 2 +- .../gui/Taranis/menu_general_sdmanager.cpp | 4 +- .../src/gui/Taranis/menu_general_version.cpp | 2 +- radio/src/gui/Taranis/menu_model_setup.cpp | 4 +- radio/src/gui/Taranis/menus.h | 130 +++++----- radio/src/gui/Taranis/navigation.cpp | 16 +- radio/src/gui/Taranis/popups.cpp | 2 +- radio/src/gui/Taranis/splash.cpp | 2 +- radio/src/gui/Taranis/view_main.cpp | 15 +- radio/src/gui/Taranis/widgets.cpp | 2 +- radio/src/lua_api.cpp | 2 +- radio/src/main_arm.cpp | 115 ++------- radio/src/main_avr.cpp | 2 +- radio/src/myeeprom.h | 2 +- radio/src/opentx.cpp | 10 +- radio/src/storage/eeprom_conversions.cpp | 8 +- radio/src/storage/eeprom_raw.cpp | 8 +- radio/src/storage/eeprom_rlc.cpp | 12 +- radio/src/storage/sdcard_raw.cpp | 8 +- radio/src/storage/storage.h | 6 +- radio/src/storage/storage_common.cpp | 8 +- radio/src/targets/Taranis/board_taranis.cpp | 8 +- radio/src/targets/Taranis/board_taranis.h | 8 +- radio/src/targets/Taranis/bootloader/boot.cpp | 6 +- radio/src/targets/Taranis/top_lcd_driver.cpp | 16 +- radio/src/tasks_arm.cpp | 4 +- radio/src/tests/frsky.cpp | 4 +- radio/src/tests/lcd.cpp | 44 ++-- radio/util/compile.py | 234 ------------------ 51 files changed, 376 insertions(+), 753 deletions(-) delete mode 100644 radio/src/bitmaps/Horus/mask_rscale_p1.png delete mode 100644 radio/src/bitmaps/Horus/mask_rscale_p13.png delete mode 100644 radio/src/bitmaps/Horus/mask_rscale_p2.png delete mode 100644 radio/src/bitmaps/Horus/mask_rscale_p3.png rename radio/src/bitmaps/Horus/{mask_topmenu_usb_ico.png => mask_topmenu_usb.png} (100%) delete mode 100755 radio/util/compile.py diff --git a/radio/src/bitmaps/Horus/mask_rscale_p1.png b/radio/src/bitmaps/Horus/mask_rscale_p1.png deleted file mode 100644 index 852c9170fc76289d9d3c3bb0416bbd518ced7164..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 453 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=E7;o-U3d z5v^~p+jlhw2(W!96m;cKQK(=ER_m=0e*eI7ixB?weO6Tk1X0Wx&lO4 z`yMY?wQBmWG{!Bt+`g9`TmQ!ym`O{tF`nM^;J4i-9q0Ss zKQo?tZp`8+!SZMN)hyQ7>l2r~G;z3Wa^wB?eBSzVPm2yN(cPfa?YI2$i#pFq$MPK~ zsXVBx`|`4rlC{JD diff --git a/radio/src/bitmaps/Horus/mask_rscale_p13.png b/radio/src/bitmaps/Horus/mask_rscale_p13.png deleted file mode 100644 index 9a93beb95c371a21805f77b8919458876124ca2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 852 zcmV-a1FQUrP)b72(6&#kMMXwxPdv1r6VOc>~7GnJWtfP~Fv zGKiZy_C zT3T949<0_ictk`Zk;s1oP(?)g`ue_1YMLhf{rwV+MrCMds44&gU>L^7$c4jU#>dCm z*x2Ct`T1j9PESvA3579*KV79{WP?pR!0q^G9`5g`_fRSzJqY&Of<+8Tp{ zgQ%)nu#lRYn+v+)Du8L4jE;^H2n335MP1jc1K>Cgot>Q}*K!~b;NjuHH2_cEjEbVj zwzgd9z$X*V%*>Po5E1I?>e$`gbsxa8 zEZW-I%7SQUXdoVsy9)rYy}ex)fc!P((b18+001K+BV|K`LLp4kbQj?M{+`y>*0KPm zrltTmI5@zvEO!6^PEJnn`~5|W4-5>D%jHNWlW3YoQ&SW3^Ydgfnco8dY;JB+Q&aP0 z@o+dyI-TbA^_AY<-hy0XVn+=`B7)E7BNB;_N~OvN0NB~t z!RPZaHZ}%;ZQFErcUM&W-$*19l>q=G5(!>jUI3V$p8j!h5#jv&ymElN&d$z0y%#Et zo12@T1Hk))w70ka7~t{o@pl05?t_Jeg^~}MuOgex{vIH&OeVwf@-kgrU1b$l6a}bY z3@g$8{yq~E6LfTRd<#%tUk`u+AXU`u?XB$X?MXZym#eEQF$_Zt!w|=DB%Mx6)d9X! eE|(KkRpk#DDW^h`gqMf_00003mA^{ZE=l#+J4{g@f> zJP+Hpubx6Q8f82lYdL`*L)dIKuL4AbSS-eDHq#IAJdbQP>kAQ&$C=OPdI12F$;1~x zzJ_wST~}U&Kt;PfT^vIq zTHjti$k}2bzPmDpFj{*}D= zin#5 1) break; killEvents(event); - if (l_posVert != POS_VERT_INIT) { - l_posVert = POS_VERT_INIT; + if (l_posVert != 0) { + l_posVert = 0; s_editMode = EDIT_MODE_INIT; break; } @@ -574,7 +574,7 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_FIRST(KEY_DOWN): //inc if (s_editMode>0) break; do { - INC(l_posVert, POS_VERT_INIT, maxrow); + INC(l_posVert, 0, maxrow); } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); #if defined(ROTARY_ENCODER_NAVIGATION) @@ -617,7 +617,7 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t if (s_editMode>0) break; do { - DEC(l_posVert, POS_VERT_INIT, maxrow); + DEC(l_posVert, 0, maxrow); } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); #if defined(ROTARY_ENCODER_NAVIGATION) diff --git a/radio/src/gui/9X/popups.cpp b/radio/src/gui/9X/popups.cpp index 1f50cfab0..2da1eaa1a 100644 --- a/radio/src/gui/9X/popups.cpp +++ b/radio/src/gui/9X/popups.cpp @@ -71,7 +71,7 @@ void displayPopup(const pm_char * pstr) void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SOUND_ARG) { - lcd_clear(); + lcdClear(); lcd_img(2, 0, asterisk_lbm, 0, 0); #define MESSAGE_LCD_OFFSET 6*FW diff --git a/radio/src/gui/9X/splash.cpp b/radio/src/gui/9X/splash.cpp index 5075df0dc..5d9fc72b7 100644 --- a/radio/src/gui/9X/splash.cpp +++ b/radio/src/gui/9X/splash.cpp @@ -45,7 +45,7 @@ const pm_uchar * const splash_lbm = splashdata+4; void displaySplash() { - lcd_clear(); + lcdClear(); lcd_img(0, 0, splash_lbm, 0, 0); #if MENUS_LOCK == 1 diff --git a/radio/src/gui/Horus/bitmaps.cpp b/radio/src/gui/Horus/bitmaps.cpp index 926363f5f..0dcd39ba1 100644 --- a/radio/src/gui/Horus/bitmaps.cpp +++ b/radio/src/gui/Horus/bitmaps.cpp @@ -64,10 +64,18 @@ const uint8_t LBM_CURRENT_DOT[] = { * Main view bitmaps */ +const uint16_t LBM_MAINVIEW_BACKGROUND[] = { +#include "../../bitmaps/Horus/background.lbm" +}; + const uint8_t LBM_TOPMENU_OPENTX[] = { #include "../../bitmaps/Horus/mask_topmenu_opentx.lbm" }; +const uint8_t LBM_TOPMENU_USB[] = { +#include "../../bitmaps/Horus/mask_topmenu_usb.lbm" +}; + const uint8_t LBM_HTRIM_FRAME[] = { #include "../../bitmaps/Horus/mask_htrim_frame.lbm" }; @@ -80,6 +88,14 @@ const uint8_t LBM_TRIM_SHADOW[] = { #include "../../bitmaps/Horus/mask_trim_shadow.lbm" }; +const uint8_t LBM_TIMER_BACKGROUND[] = { +#include "../../bitmaps/Horus/mask_timer_bg.lbm" +}; + +const uint8_t LBM_RSCALE[] = { +#include "../../bitmaps/Horus/mask_rscale.lbm" +}; + /* * Radio menu bitmaps */ diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index a259d766d..7dd1e4036 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -64,6 +64,7 @@ #define CURVE_CENTER_Y 159 #define MENU_TITLE_NEXT_POS (lcdNextPos + 10) +#define MENU_INIT_VPOS -1 void displaySplash(); void displayScreenIndex(uint8_t index, uint8_t count); @@ -82,12 +83,16 @@ extern const uint8_t LBM_CURRENT_SHADOW[]; extern const uint8_t LBM_CURRENT_DOT[]; // Main view icons +extern const uint16_t LBM_MAINVIEW_BACKGROUND[]; extern const uint8_t LBM_TOPMENU_OPENTX[]; +extern const uint8_t LBM_TOPMENU_USB[]; extern const uint8_t LBM_HTRIM_FRAME[]; extern const uint8_t LBM_VTRIM_FRAME[]; extern const uint8_t LBM_TRIM_SHADOW[]; extern const uint8_t LBM_RADIO_ICON[]; extern const uint8_t LBM_MODEL_ICON[]; +extern const uint8_t LBM_TIMER_BACKGROUND[]; +extern const uint8_t LBM_RSCALE[]; // Menu icons extern const uint8_t * const LBM_RADIO_ICONS[]; diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index 1ebc7dc71..ac0802476 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -54,6 +54,7 @@ void lcdColorsInit() lcdColorTable[SCROLLBOX_COLOR_INDEX] = RED; lcdColorTable[MENU_TITLE_BGCOLOR_INDEX] = DARKGREY; lcdColorTable[MENU_TITLE_COLOR_INDEX] = WHITE; + lcdColorTable[MENU_TITLE_DISABLE_COLOR_INDEX] = RGB(130, 1, 5); lcdColorTable[HEADER_COLOR_INDEX] = DARKGREY; lcdColorTable[ALARM_COLOR_INDEX] = RED; lcdColorTable[WARNING_COLOR_INDEX] = YELLOW; @@ -790,18 +791,18 @@ void lcdSetContrast() lcdSetRefVolt(g_eeGeneral.contrast); } -void lcdDrawPixel(display_t * p, display_t value) +inline void lcdDrawPixel(display_t * p, display_t value) { *p = value; } -void lcdDrawPixel(coord_t x, coord_t y, display_t value) +inline void lcdDrawPixel(coord_t x, coord_t y, display_t value) { display_t * p = PIXEL_PTR(x, y); lcdDrawPixel(p, value); } -void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color) +inline void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color) { display_t * p = PIXEL_PTR(x, y); lcdDrawTransparentPixel(p, opacity, color); @@ -938,56 +939,66 @@ void lcdDrawCircle(int x0, int y0, int radius) #define PI 3.14159265 -void lcdDrawPie(int x0, int y0, int radius, int startAngle, int endAngle) +bool evalSlopes(int * slopes, int startAngle, int endAngle) { - int slope1s, slope1e, slope2s, slope2e; - if (startAngle >= 360 || endAngle <= 0) - return; - - float angle1 = float(startAngle) * PI / 180; - float angle2 = float(endAngle) * PI / 180; + return false; if (startAngle == 0) { - slope1e = 100000; - slope2s = -100000; - } - else if (startAngle >= 180) { - slope1e = -100000; - slope2s = cos(angle1)*100/sin(angle1); + slopes[1] = 100000; + slopes[2] = -100000; } else { - slope1e = cos(angle1)*100/sin(angle1); - slope2s = -100000; + float angle1 = float(startAngle) * PI / 180; + if (startAngle >= 180) { + slopes[1] = -100000; + slopes[2] = cos(angle1) * 100 / sin(angle1); + } + else { + slopes[1] = cos(angle1) * 100 / sin(angle1); + slopes[2] = -100000; + } } if (endAngle == 360) { - slope1s = -100000; - slope2e = 100000; - } - else if (endAngle >= 180) { - slope1s = -100000; - slope2e = -cos(angle2)*100/sin(angle2); + slopes[0] = -100000; + slopes[3] = 100000; } else { - slope1s = cos(angle2)*100/sin(angle2); - slope2e = -100000; + float angle2 = float(endAngle) * PI / 180; + if (endAngle >= 180) { + slopes[0] = -100000; + slopes[3] = -cos(angle2) * 100 / sin(angle2); + } + else { + slopes[0] = cos(angle2) * 100 / sin(angle2); + slopes[3] = -100000; + } } + return true; +} + +void lcdDrawPie(int x0, int y0, int radius, int startAngle, int endAngle) +{ + int slopes[4]; + if (!evalSlopes(slopes, startAngle, endAngle)) + return; + for (int y=0; y<=radius; y++) { for (int x=0; x<=radius; x++) { if (x*x+y*y <= radius*radius) { int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); - if (slope >= slope1s && slope < slope1e) { + if (slope >= slopes[0] && slope < slopes[1]) { lcdDrawPixel(x0+x, y0-y, WHITE); } - if (-slope >= slope1s && -slope < slope1e) { + if (-slope >= slopes[0] && -slope < slopes[1]) { lcdDrawPixel(x0+x, y0+y, WHITE); } - if (slope >= slope2s && slope < slope2e) { + if (slope >= slopes[2] && slope < slopes[3]) { lcdDrawPixel(x0-x, y0-y, WHITE); } - if (-slope >= slope2s && -slope < slope2e) { + if (-slope >= slopes[2] && -slope < slopes[3]) { lcdDrawPixel(x0-x, y0+y, WHITE); } } @@ -1001,56 +1012,26 @@ void lcdDrawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int coord_t width = *q++; coord_t height = *q++; - int slope1s, slope1e, slope2s, slope2e; - - if (startAngle >= 360 || endAngle <= 0) + int slopes[4]; + if (!evalSlopes(slopes, startAngle, endAngle)) return; - float angle1 = float(startAngle) * PI / 180; - float angle2 = float(endAngle) * PI / 180; - - if (startAngle == 0) { - slope1e = 100000; - slope2s = -100000; - } - else if (startAngle >= 180) { - slope1e = -100000; - slope2s = cos(angle1)*100/sin(angle1); - } - else { - slope1e = cos(angle1)*100/sin(angle1); - slope2s = -100000; - } - - if (endAngle == 360) { - slope1s = -100000; - slope2e = 100000; - } - else if (endAngle >= 180) { - slope1s = -100000; - slope2e = -cos(angle2)*100/sin(angle2); - } - else { - slope1s = cos(angle2)*100/sin(angle2); - slope2e = -100000; - } - int w2 = width/2; int h2 = height/2; for (int y=h2-1; y>=0; y--) { for (int x=w2-1; x>=0; x--) { int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); - if (slope >= slope1s && slope < slope1e) { + if (slope >= slopes[0] && slope < slopes[1]) { displayBuf[(y0+h2-y)*LCD_W + x0+w2+x] = q[(h2-y)*width + w2+x]; } - if (-slope >= slope1s && -slope < slope1e) { + if (-slope >= slopes[0] && -slope < slopes[1]) { displayBuf[(y0+h2+y)*LCD_W + x0+w2+x] = q[(h2+y)*width + w2+x]; } - if (slope >= slope2s && slope < slope2e) { + if (slope >= slopes[2] && slope < slopes[3]) { displayBuf[(y0+h2-y)*LCD_W + x0+w2-x] = q[(h2-y)*width + w2-x]; } - if (-slope >= slope2s && -slope < slope2e) { + if (-slope >= slopes[2] && -slope < slopes[3]) { displayBuf[(y0+h2+y)*LCD_W + x0+w2-x] = q[(h2+y)*width + w2-x]; } } @@ -1063,58 +1044,28 @@ void lcdDrawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFla coord_t height = *(((uint16_t *)img)+1); const uint8_t * q = img+4; - int slope1s, slope1e, slope2s, slope2e; - - if (startAngle >= 360 || endAngle <= 0) + int slopes[4]; + if (!evalSlopes(slopes, startAngle, endAngle)) return; display_t color = lcdColorTable[COLOR_IDX(flags)]; - float angle1 = float(startAngle) * PI / 180; - float angle2 = float(endAngle) * PI / 180; - - if (startAngle == 0) { - slope1e = 100000; - slope2s = -100000; - } - else if (startAngle >= 180) { - slope1e = -100000; - slope2s = cos(angle1)*100/sin(angle1); - } - else { - slope1e = cos(angle1)*100/sin(angle1); - slope2s = -100000; - } - - if (endAngle == 360) { - slope1s = -100000; - slope2e = 100000; - } - else if (endAngle >= 180) { - slope1s = -100000; - slope2e = -cos(angle2)*100/sin(angle2); - } - else { - slope1s = cos(angle2)*100/sin(angle2); - slope2e = -100000; - } - int w2 = width/2; int h2 = height/2; for (int y=h2-1; y>=0; y--) { for (int x=w2-1; x>=0; x--) { int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); - if (slope >= slope1s && slope < slope1e) { + if (slope >= slopes[0] && slope < slopes[1]) { lcdDrawTransparentPixel(x0+w2+x, y0+h2-y, q[(h2-y)*width + w2+x], color); } - if (-slope >= slope1s && -slope < slope1e) { + if (-slope >= slopes[0] && -slope < slopes[1]) { lcdDrawTransparentPixel(x0+w2+x, y0+h2+y, q[(h2+y)*width + w2+x], color); } - if (slope >= slope2s && slope < slope2e) { + if (slope >= slopes[2] && slope < slopes[3]) { lcdDrawTransparentPixel(x0+w2-x, y0+h2-y, q[(h2-y)*width + w2-x], color); } - if (-slope >= slope2s && -slope < slope2e) { + if (-slope >= slopes[2] && -slope < slopes[3]) { lcdDrawTransparentPixel(x0+w2-x, y0+h2+y, q[(h2+y)*width + w2-x], color); } } diff --git a/radio/src/gui/Horus/lcd.h b/radio/src/gui/Horus/lcd.h index e53092fd0..e9e8a22f6 100644 --- a/radio/src/gui/Horus/lcd.h +++ b/radio/src/gui/Horus/lcd.h @@ -123,6 +123,7 @@ enum LcdColorIndex SCROLLBOX_COLOR_INDEX, MENU_TITLE_BGCOLOR_INDEX, MENU_TITLE_COLOR_INDEX, + MENU_TITLE_DISABLE_COLOR_INDEX, HEADER_COLOR_INDEX, ALARM_COLOR_INDEX, WARNING_COLOR_INDEX, @@ -141,25 +142,26 @@ void lcdColorsInit(); #define OPACITY_MAX 0x0F #define OPACITY(x) ((x)<<24) -#define COLOR(index) ((index) << 16) -#define TEXT_COLOR COLOR(TEXT_COLOR_INDEX) -#define TEXT_BGCOLOR COLOR(TEXT_BGCOLOR_INDEX) -#define TEXT_INVERTED_COLOR COLOR(TEXT_INVERTED_COLOR_INDEX) -#define TEXT_INVERTED_BGCOLOR COLOR(TEXT_INVERTED_BGCOLOR_INDEX) -#define LINE_COLOR COLOR(LINE_COLOR_INDEX) -#define SCROLLBOX_COLOR COLOR(SCROLLBOX_COLOR_INDEX) -#define HEADER_SEPARATOR_COLOR COLOR(HEADER_SEPARATOR_COLOR_INDEX) -#define MENU_TITLE_BGCOLOR COLOR(MENU_TITLE_BGCOLOR_INDEX) -#define MENU_TITLE_COLOR COLOR(MENU_TITLE_COLOR_INDEX) -#define HEADER_COLOR COLOR(HEADER_COLOR_INDEX) -#define ALARM_COLOR COLOR(ALARM_COLOR_INDEX) -#define WARNING_COLOR COLOR(WARNING_COLOR_INDEX) -#define TEXT_DISABLE_COLOR COLOR(TEXT_DISABLE_COLOR_INDEX) -#define CURVE_AXIS_COLOR COLOR(CURVE_AXIS_COLOR_INDEX) -#define CURVE_COLOR COLOR(CURVE_COLOR_INDEX) -#define CURVE_CURSOR_COLOR COLOR(CURVE_CURSOR_COLOR_INDEX) -#define TITLE_BGCOLOR COLOR(TITLE_BGCOLOR_INDEX) -#define HEADER_BGCOLOR COLOR(HEADER_BGCOLOR_INDEX) +#define COLOR(index) ((index) << 16) +#define TEXT_COLOR COLOR(TEXT_COLOR_INDEX) +#define TEXT_BGCOLOR COLOR(TEXT_BGCOLOR_INDEX) +#define TEXT_INVERTED_COLOR COLOR(TEXT_INVERTED_COLOR_INDEX) +#define TEXT_INVERTED_BGCOLOR COLOR(TEXT_INVERTED_BGCOLOR_INDEX) +#define LINE_COLOR COLOR(LINE_COLOR_INDEX) +#define SCROLLBOX_COLOR COLOR(SCROLLBOX_COLOR_INDEX) +#define HEADER_SEPARATOR_COLOR COLOR(HEADER_SEPARATOR_COLOR_INDEX) +#define MENU_TITLE_BGCOLOR COLOR(MENU_TITLE_BGCOLOR_INDEX) +#define MENU_TITLE_COLOR COLOR(MENU_TITLE_COLOR_INDEX) +#define MENU_TITLE_DISABLE_COLOR COLOR(MENU_TITLE_DISABLE_COLOR_INDEX) +#define HEADER_COLOR COLOR(HEADER_COLOR_INDEX) +#define ALARM_COLOR COLOR(ALARM_COLOR_INDEX) +#define WARNING_COLOR COLOR(WARNING_COLOR_INDEX) +#define TEXT_DISABLE_COLOR COLOR(TEXT_DISABLE_COLOR_INDEX) +#define CURVE_AXIS_COLOR COLOR(CURVE_AXIS_COLOR_INDEX) +#define CURVE_COLOR COLOR(CURVE_COLOR_INDEX) +#define CURVE_CURSOR_COLOR COLOR(CURVE_CURSOR_COLOR_INDEX) +#define TITLE_BGCOLOR COLOR(TITLE_BGCOLOR_INDEX) +#define HEADER_BGCOLOR COLOR(HEADER_BGCOLOR_INDEX) #define COLOR_IDX(att) uint8_t((att) >> 16) diff --git a/radio/src/gui/Horus/menu_general_calib.cpp b/radio/src/gui/Horus/menu_general_calib.cpp index ebc3a901e..c06d4bd82 100644 --- a/radio/src/gui/Horus/menu_general_calib.cpp +++ b/radio/src/gui/Horus/menu_general_calib.cpp @@ -39,6 +39,36 @@ #define XPOT_DELTA 10 #define XPOT_DELAY 10 /* cycles */ +#define BAR_HEIGHT (BOX_WIDTH-9) +#define LBOX_CENTERX (BOX_WIDTH/2 + 17) +#define RBOX_CENTERX (LCD_W-LBOX_CENTERX) + +void drawPotsBars() +{ + // Optimization by Mike Blandford + uint8_t x, i, len ; // declare temporary variables + for (x=LCD_W/2-9, i=NUM_STICKS; i 0) REPEAT_LAST_CURSOR_MOVE() -#define POS_VERT_INIT (MAXCOL((uint16_t)0) >= HIDDEN_ROW ? (MAXCOL((uint16_t)1) >= HIDDEN_ROW ? 2 : 1) : 0) -#define POS_HORZ_INIT(posVert) ((COLATTR(posVert) & NAVIGATION_LINE_BY_LINE) ? -1 : 0) -#define EDIT_MODE_INIT 0 // TODO enum +#define MENU_FIRST_LINE_EDIT (MAXCOL((uint16_t)0) >= HIDDEN_ROW ? (MAXCOL((uint16_t)1) >= HIDDEN_ROW ? 2 : 1) : 0) +#define POS_HORZ_INIT(posVert) ((COLATTR(posVert) & NAVIGATION_LINE_BY_LINE) ? -1 : 0) +#define EDIT_MODE_INIT 0 // TODO enum typedef int (*FnFuncP) (int x); void DrawFunction(FnFuncP fn, int offset=0); diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp index 916c5eaf9..dda8c5029 100644 --- a/radio/src/gui/Horus/navigation.cpp +++ b/radio/src/gui/Horus/navigation.cpp @@ -243,7 +243,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_BREAK(KEY_ENTER): if (rowcount > 0) { - l_posVert = POS_VERT_INIT; + l_posVert = MENU_FIRST_LINE_EDIT; event = 0; } break; @@ -347,7 +347,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_FIRST(KEY_DOWN): case EVT_KEY_REPT(KEY_DOWN): do { - INC(l_posVert, POS_VERT_INIT, rowcount-1); + INC(l_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); s_editMode = 0; // if we go down, we must be in this mode l_posHorz = POS_HORZ_INIT(l_posVert); @@ -376,7 +376,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_FIRST(KEY_UP): case EVT_KEY_REPT(KEY_UP): do { - DEC(l_posVert, POS_VERT_INIT, rowcount-1); + DEC(l_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); s_editMode = 0; // if we go up, we must be in this mode diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index 8ba758b20..e5e2a261d 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -36,13 +36,6 @@ #include "../../opentx.h" -#define BIGSIZE MIDSIZE -#define BAR_HEIGHT (BOX_WIDTH-9) -#define LBOX_CENTERX (BOX_WIDTH/2 + 17) -#define RBOX_CENTERX (LCD_W-LBOX_CENTERX) -#define BITMAP_X ((LCD_W-64)/2) -#define BITMAP_Y (LCD_H/2) - #define TRIM_LH_X 90 #define TRIM_LV_X 24 #define TRIM_RV_X (LCD_W-35) @@ -51,32 +44,6 @@ #define TRIM_H_Y 235 #define TRIM_LEN 80 -void drawPotsBars() -{ - // Optimization by Mike Blandford - uint8_t x, i, len ; // declare temporary variables - for (x=LCD_W/2-9, i=NUM_STICKS; i 0) - lcd_putsnAtt(TIMERS_MARGIN+TIMERS_PADDING, y+2, timerData.name, LEN_TIMER_NAME, color|ZCHAR); - else - putsTimerMode(TIMERS_MARGIN+TIMERS_PADDING, y+2, timerData.mode, color); - } + TimerData & timerData = g_model.timers[index]; + TimerState & timerState = timersStates[index]; + lcdDrawBitmapPattern(x, y, LBM_TIMER_BACKGROUND, TEXT_BGCOLOR); + if (timerData.start) { + lcdDrawBitmapPatternPie(x+2, y+3, LBM_RSCALE, TITLE_BGCOLOR, 0, timerState.val <= 0 ? 360 : 360*(timerData.start-timerState.val)/timerData.start); } + putsTimer(x+74, y+31, abs(timerState.val), TEXT_COLOR|DBLSIZE|LEFT); + if (ZLEN(timerData.name) > 0) { + lcd_putsnAtt(x+74, y+20, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE|TEXT_COLOR); + } + putsStrIdx(x+137, y+17, "TMR", 1, SMLSIZE|TEXT_COLOR); } + void displayMainTelemetryFields() { const int ALTITUDE_Y = 16; @@ -329,12 +285,6 @@ void onMainViewMenu(const char *result) #endif } -void displayTelemetryScreen(int index, unsigned int evt); - -const uint16_t LBM_MAINVIEW_BACKGROUND[] = { -#include "../../bitmaps/Horus/background.lbm" -}; - const uint16_t LBM_MAINVIEW_FLAT[] = { #include "../../bitmaps/Horus/mainview_flat.lbm" }; @@ -343,13 +293,6 @@ const uint16_t LBM_CORSAIR[] = { #include "../../bitmaps/Horus/corsair.lbm" }; -const uint8_t LBM_TIMER_BACKGROUND[] = { -#include "../../bitmaps/Horus/mask_timer_bg.lbm" -}; - -const uint8_t LBM_RSCALE[] = { -#include "../../bitmaps/Horus/mask_rscale.lbm" -}; void menuMainView(evt_t event) { @@ -412,6 +355,16 @@ void menuMainView(evt_t event) lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); lcdDrawTopmenuDatetime(); + if (1 || usbPlugged()) { + lcdDrawBitmapPattern(378, 8, LBM_TOPMENU_USB, MENU_TITLE_COLOR); + } + const uint8_t rssiBarsValue[] = { 30, 40, 50, 60, 80 }; + const uint8_t rssiBarsHeight[] = { 5, 10, 15, 21, 31 }; + for (unsigned int i=0; i= rssiBarsValue[i] ? MENU_TITLE_COLOR : MENU_TITLE_DISABLE_COLOR); + } + // Flight Mode Name int mode = mixerCurrentFlightMode; @@ -426,38 +379,20 @@ void menuMainView(evt_t event) drawSticks(); // Trims - displayTrims(mode); + drawTrims(mode); // Model panel lcdDrawFilledRect(248, 58, 188, 158, SOLID, TEXT_BGCOLOR | OPACITY(5)); lcdDrawBitmapPattern(256, 62, LBM_MODEL_ICON, TITLE_BGCOLOR); - lcd_putsnAtt(293, 68, g_model.header.name, ZCHAR|SMLSIZE, LEN_MODEL_NAME); + lcd_putsnAtt(293, 68, g_model.header.name, LEN_MODEL_NAME, ZCHAR|SMLSIZE); lcdDrawSolidHorizontalLine(287, 85, 140, TITLE_BGCOLOR); lcdDrawBitmap(256, 104, LBM_CORSAIR); // Timer 1 - if (g_model.timers[0].start) { - TimerState & timerState = timersStates[0]; - lcdDrawBitmapPattern(58, 61, LBM_TIMER_BACKGROUND, TEXT_BGCOLOR); - lcdDrawBitmapPatternPie(60, 64, LBM_RSCALE, TITLE_BGCOLOR, 0, 360*timerState.val/g_model.timers[0].start); - putsTimer(125, 94, abs(timerState.val), TEXT_COLOR|DBLSIZE|LEFT); + if (g_model.timers[0].mode) { + drawTimer(50, 61, 0); } -#if 0 - displayMainViewIndex(); - - if (g_eeGeneral.view == VIEW_TIMERS_ALTITUDE) { - displayTimers(); - displayMainTelemetryFields(); - } - else if (g_eeGeneral.view == VIEW_CHANNELS) { - menuChannelsView(event); - } - else { - displayTelemetryScreen(g_eeGeneral.view - VIEW_TELEM1, event); - } -#endif - #if 0 if (s_gvar_timer > 0) { s_gvar_timer--; diff --git a/radio/src/gui/Taranis/gui.h b/radio/src/gui/Taranis/gui.h index 3b1133990..a4fd2619a 100644 --- a/radio/src/gui/Taranis/gui.h +++ b/radio/src/gui/Taranis/gui.h @@ -40,6 +40,7 @@ #define DEFAULT_SCROLLBAR_X (LCD_W-1) #define NUM_BODY_LINES (LCD_LINES-1) #define MENU_HEADER_HEIGHT FH +#define MENU_INIT_VPOS 0 struct MenuItem { const char *name; diff --git a/radio/src/gui/Taranis/lcd.cpp b/radio/src/gui/Taranis/lcd.cpp index 23326517e..88c6ebeea 100644 --- a/radio/src/gui/Taranis/lcd.cpp +++ b/radio/src/gui/Taranis/lcd.cpp @@ -50,7 +50,7 @@ inline bool lcdIsPointOutside(coord_t x, coord_t y) return (x<0 || x>=LCD_W || y<0 || y>=LCD_H); } -void lcd_clear() +void lcdClear() { memset(displayBuf, 0, DISPLAY_BUFFER_SIZE); } diff --git a/radio/src/gui/Taranis/lcd.h b/radio/src/gui/Taranis/lcd.h index 6278e7849..003f32c5e 100644 --- a/radio/src/gui/Taranis/lcd.h +++ b/radio/src/gui/Taranis/lcd.h @@ -227,7 +227,7 @@ void lcd_bmp(coord_t x, coord_t y, const uint8_t * img, coord_t offset=0, coord_ #define LCD_ICON(x, y, icon) lcd_bmp(x, y, icons, icon) void lcdSetRefVolt(unsigned char val); -void lcd_clear(); +void lcdClear(); void lcdSetContrast(); #if defined(REVPLUS) && !defined(SIMU) diff --git a/radio/src/gui/Taranis/menu_general_sdmanager.cpp b/radio/src/gui/Taranis/menu_general_sdmanager.cpp index 330bf4895..ab12ac9fd 100644 --- a/radio/src/gui/Taranis/menu_general_sdmanager.cpp +++ b/radio/src/gui/Taranis/menu_general_sdmanager.cpp @@ -98,7 +98,7 @@ void flashBootloader(const char * filename) uint8_t buffer[1024]; UINT count; - lcd_clear(); + lcdClear(); displayProgressBar(STR_WRITING); static uint8_t unlocked = 0; @@ -137,7 +137,7 @@ void flashSportDevice(ModuleIndex module, const char *filename) pausePulses(); watchdogSetTimeout(60*60*100/*1h*/); - lcd_clear(); + lcdClear(); displayProgressBar(STR_WRITING); sportFirmwareUpdate(module, filename); diff --git a/radio/src/gui/Taranis/menu_general_version.cpp b/radio/src/gui/Taranis/menu_general_version.cpp index 545267343..3151f8761 100644 --- a/radio/src/gui/Taranis/menu_general_version.cpp +++ b/radio/src/gui/Taranis/menu_general_version.cpp @@ -42,7 +42,7 @@ void backupEeprom() uint8_t buffer[1024]; FIL file; - lcd_clear(); + lcdClear(); displayProgressBar(STR_WRITING); // reset unexpectedShutdown to prevent warning when user restores EEPROM backup diff --git a/radio/src/gui/Taranis/menu_model_setup.cpp b/radio/src/gui/Taranis/menu_model_setup.cpp index 4db7ed90a..e52ca025d 100644 --- a/radio/src/gui/Taranis/menu_model_setup.cpp +++ b/radio/src/gui/Taranis/menu_model_setup.cpp @@ -381,9 +381,9 @@ void menuModelSetup(uint8_t event) #if defined(REV9E) case ITEM_MODEL_TOP_LCD_TIMER: lcd_putsLeft(y, STR_TOPLCDTIMER); - putsStrIdx(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER, g_model.topLcdTimer+1, attr); + putsStrIdx(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER, g_model.toplcdTimer+1, attr); if (attr) { - g_model.topLcdTimer = checkIncDec(event, g_model.topLcdTimer, 0, TIMERS-1, EE_MODEL); + g_model.toplcdTimer = checkIncDec(event, g_model.toplcdTimer, 0, TIMERS-1, EE_MODEL); } break; #endif diff --git a/radio/src/gui/Taranis/menus.h b/radio/src/gui/Taranis/menus.h index 0bc157359..81736caf0 100644 --- a/radio/src/gui/Taranis/menus.h +++ b/radio/src/gui/Taranis/menus.h @@ -37,17 +37,17 @@ #ifndef _MENUS_H_ #define _MENUS_H_ -#define NO_HI_LEN 25 +#define NO_HI_LEN 25 #if defined(TRANSLATIONS_FR) - #define MENU_COLUMNS 1 + #define MENU_COLUMNS 1 #else - #define MENU_COLUMNS 2 + #define MENU_COLUMNS 2 #endif -#define MENUS_SCROLLBAR_WIDTH 2 -#define MENU_COLUMN2_X (8 + LCD_W / 2) -#define lcd_putsColumnLeft(x, y, str) lcd_puts((x > (LCD_W-10*FW-MENUS_SCROLLBAR_WIDTH)) ? MENU_COLUMN2_X : 0, y, str) +#define MENUS_SCROLLBAR_WIDTH 2 +#define MENU_COLUMN2_X (8 + LCD_W / 2) +#define lcd_putsColumnLeft(x, y, str) lcd_puts((x > (LCD_W-10*FW-MENUS_SCROLLBAR_WIDTH)) ? MENU_COLUMN2_X : 0, y, str) // Menus related stuff ... typedef uint16_t vertpos_t; @@ -69,8 +69,6 @@ typedef void (*MenuFuncP)(uint8_t event); typedef void (*MenuFuncP_PROGMEM)(uint8_t event); extern const MenuFuncP_PROGMEM menuTabModel[]; extern const MenuFuncP_PROGMEM menuTabGeneral[]; -extern const MenuFuncP_PROGMEM menuTabFPV[]; -extern const MenuFuncP_PROGMEM menuTabTelemetry[]; extern MenuFuncP g_menuStack[5]; extern uint8_t g_menuPos[4]; @@ -83,11 +81,6 @@ void chainMenu(MenuFuncP newMenu); void pushMenu(MenuFuncP newMenu); /// return to last menu in menustack void popMenu(); -///deliver address of last menu which was popped from -inline MenuFuncP lastPopMenu() -{ - return g_menuStack[g_menuStackPtr+1]; -} void doMainScreenGraphics(); void menuFirstCalib(uint8_t event); @@ -122,26 +115,25 @@ void menuChannelsView(uint8_t event); extern int8_t checkIncDec_Ret; // global helper vars -#define EDIT_SELECT_MENU -1 -#define EDIT_SELECT_FIELD 0 -#define EDIT_MODIFY_FIELD 1 -#define EDIT_MODIFY_STRING 2 -extern int8_t s_editMode; // global editmode +#define EDIT_SELECT_MENU -1 +#define EDIT_SELECT_FIELD 0 +#define EDIT_MODIFY_FIELD 1 +#define EDIT_MODIFY_STRING 2 +extern int8_t s_editMode; // global editmode // checkIncDec flags -#define EE_GENERAL 0x01 -#define EE_MODEL 0x02 -#define NO_INCDEC_MARKS 0x04 -#define INCDEC_SWITCH 0x08 -#define INCDEC_SOURCE 0x10 -#define INCDEC_REP10 0x40 -#define NO_DBLKEYS 0x80 +#define EE_GENERAL 0x01 +#define EE_MODEL 0x02 +#define NO_INCDEC_MARKS 0x04 +#define INCDEC_SWITCH 0x08 +#define INCDEC_SOURCE 0x10 +#define INCDEC_REP10 0x40 +#define NO_DBLKEYS 0x80 // mawrow special values -#define TITLE_ROW ((uint8_t)-1) -#define HIDDEN_ROW ((uint8_t)-2) +#define TITLE_ROW ((uint8_t)-1) +#define HIDDEN_ROW ((uint8_t)-2) -typedef bool (*IsValueAvailable)(int); struct CheckIncDecStops { const int count; const int stops[]; @@ -274,9 +266,9 @@ swsrc_t switchMenuItem(coord_t x, coord_t y, swsrc_t value, LcdFlags attr, uint8 void editName(coord_t x, coord_t y, char *name, uint8_t size, uint8_t event, uint8_t active, uint8_t attr=ZCHAR); -#define WARNING_TYPE_ASTERISK 0 -#define WARNING_TYPE_CONFIRM 1 -#define WARNING_TYPE_INPUT 2 +#define WARNING_TYPE_ASTERISK 0 +#define WARNING_TYPE_CONFIRM 1 +#define WARNING_TYPE_INPUT 2 extern const pm_char * s_warning; extern const pm_char * s_warning_info; @@ -284,12 +276,12 @@ extern uint8_t s_warning_info_len; extern uint8_t s_warning_result; extern uint8_t s_warning_type; -#define MENU_X 30 -#define MENU_Y 16 -#define MENU_W LCD_W-(2*MENU_X) -#define WARNING_LINE_LEN 32 -#define WARNING_LINE_X 16 -#define WARNING_LINE_Y 3*FH +#define MENU_X 30 +#define MENU_Y 16 +#define MENU_W LCD_W-(2*MENU_X) +#define WARNING_LINE_LEN 32 +#define WARNING_LINE_X 16 +#define WARNING_LINE_Y 3*FH void displayBox(const char *title); void displayPopup(const char *title); @@ -301,19 +293,19 @@ extern int16_t s_warning_input_min; extern int16_t s_warning_input_max; extern uint8_t s_warning_info_flags; -#define DISPLAY_WARNING (*popupFunc) -#define POPUP_WARNING(s) (s_warning = s, s_warning_info = 0, popupFunc = displayWarning) -#define POPUP_CONFIRMATION(s) (s_warning = s, s_warning_type = WARNING_TYPE_CONFIRM, s_warning_info = 0, popupFunc = displayWarning) +#define DISPLAY_WARNING (*popupFunc) +#define POPUP_WARNING(s) (s_warning = s, s_warning_info = 0, popupFunc = displayWarning) +#define POPUP_CONFIRMATION(s) (s_warning = s, s_warning_type = WARNING_TYPE_CONFIRM, s_warning_info = 0, popupFunc = displayWarning) #define POPUP_INPUT(s, func, start, min, max) (s_warning = s, s_warning_type = WARNING_TYPE_INPUT, popupFunc = func, s_warning_input_value = start, s_warning_input_min = min, s_warning_input_max = max) -#define WARNING_INFO_FLAGS s_warning_info_flags +#define WARNING_INFO_FLAGS s_warning_info_flags #define SET_WARNING_INFO(info, len, flags) (s_warning_info = info, s_warning_info_len = len, s_warning_info_flags = flags) #define NAVIGATION_MENUS -#define MENU_ADD_ITEM(s) do { s_menu_offset_type = MENU_OFFSET_INTERNAL; if (s_menu_count < MENU_MAX_LINES) s_menu[s_menu_count++] = s; } while (0) -#define MENU_MAX_LINES 12 -#define MENU_MAX_DISPLAY_LINES 6 -#define MENU_ADD_SD_ITEM(s) MENU_ADD_ITEM(s) -#define MENU_LINE_LENGTH (LEN_MODEL_NAME+12) +#define MENU_ADD_ITEM(s) do { s_menu_offset_type = MENU_OFFSET_INTERNAL; if (s_menu_count < MENU_MAX_LINES) s_menu[s_menu_count++] = s; } while (0) +#define MENU_MAX_LINES 12 +#define MENU_MAX_DISPLAY_LINES 6 +#define MENU_ADD_SD_ITEM(s) MENU_ADD_ITEM(s) +#define MENU_LINE_LENGTH (LEN_MODEL_NAME+12) extern const char *s_menu[MENU_MAX_LINES]; extern uint16_t s_menu_count; extern uint8_t s_menu_flags; @@ -326,12 +318,12 @@ extern uint8_t s_menu_offset_type; const char * displayMenu(uint8_t event); extern void (*menuHandler)(const char *result); -#define STATUS_LINE_LENGTH 32 +#define STATUS_LINE_LENGTH 32 extern char statusLineMsg[STATUS_LINE_LENGTH]; void showStatusLine(); void drawStatusLine(); -#define TEXT_FILENAME_MAXLEN 40 +#define TEXT_FILENAME_MAXLEN 40 extern char s_text_file[TEXT_FILENAME_MAXLEN]; void menuTextView(uint8_t event); void pushMenuTextView(const char *filename); @@ -343,31 +335,31 @@ void menuChannelsView(uint8_t event); #define LABEL(...) (uint8_t)-1 #if defined(REV9E) && !defined(SIMU) - #define KEY_MOVE_UP KEY_MINUS - #define KEY_MOVE_DOWN KEY_PLUS - #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_MINUS) - #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_PLUS) - #define CASE_EVT_ROTARY_MOVE_RIGHT CASE_EVT_ROTARY_RIGHT - #define CASE_EVT_ROTARY_MOVE_LEFT CASE_EVT_ROTARY_LEFT - #define IS_ROTARY_MOVE_RIGHT IS_ROTARY_RIGHT - #define IS_ROTARY_MOVE_LEFT IS_ROTARY_LEFT + #define KEY_MOVE_UP KEY_MINUS + #define KEY_MOVE_DOWN KEY_PLUS + #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_MINUS) + #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_PLUS) + #define CASE_EVT_ROTARY_MOVE_RIGHT CASE_EVT_ROTARY_RIGHT + #define CASE_EVT_ROTARY_MOVE_LEFT CASE_EVT_ROTARY_LEFT + #define IS_ROTARY_MOVE_RIGHT IS_ROTARY_RIGHT + #define IS_ROTARY_MOVE_LEFT IS_ROTARY_LEFT #else - #define KEY_MOVE_UP KEY_PLUS - #define KEY_MOVE_DOWN KEY_MINUS - #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_PLUS) - #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_MINUS) - #define CASE_EVT_ROTARY_MOVE_RIGHT CASE_EVT_ROTARY_LEFT - #define CASE_EVT_ROTARY_MOVE_LEFT CASE_EVT_ROTARY_RIGHT - #define IS_ROTARY_MOVE_RIGHT IS_ROTARY_LEFT - #define IS_ROTARY_MOVE_LEFT IS_ROTARY_RIGHT + #define KEY_MOVE_UP KEY_PLUS + #define KEY_MOVE_DOWN KEY_MINUS + #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_PLUS) + #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_MINUS) + #define CASE_EVT_ROTARY_MOVE_RIGHT CASE_EVT_ROTARY_LEFT + #define CASE_EVT_ROTARY_MOVE_LEFT CASE_EVT_ROTARY_RIGHT + #define IS_ROTARY_MOVE_RIGHT IS_ROTARY_LEFT + #define IS_ROTARY_MOVE_LEFT IS_ROTARY_RIGHT #endif -#define REPEAT_LAST_CURSOR_MOVE() { if (CURSOR_MOVED_LEFT(event) || CURSOR_MOVED_RIGHT(event)) putEvent(event); else m_posHorz = 0; } -#define MOVE_CURSOR_FROM_HERE() if (m_posHorz > 0) REPEAT_LAST_CURSOR_MOVE() +#define REPEAT_LAST_CURSOR_MOVE() { if (CURSOR_MOVED_LEFT(event) || CURSOR_MOVED_RIGHT(event)) putEvent(event); else m_posHorz = 0; } +#define MOVE_CURSOR_FROM_HERE() if (m_posHorz > 0) REPEAT_LAST_CURSOR_MOVE() -#define POS_VERT_INIT (menuTab ? (MAXCOL((uint16_t)0) >= HIDDEN_ROW ? (MAXCOL((uint16_t)1) >= HIDDEN_ROW ? 2 : 1) : 0) : 0) -#define POS_HORZ_INIT(posVert) ((COLATTR(posVert) & NAVIGATION_LINE_BY_LINE) ? -1 : 0) -#define EDIT_MODE_INIT 0 // TODO enum +#define MENU_FIRST_LINE_EDIT (menuTab ? (MAXCOL((uint16_t)0) >= HIDDEN_ROW ? (MAXCOL((uint16_t)1) >= HIDDEN_ROW ? 2 : 1) : 0) : 0) +#define POS_HORZ_INIT(posVert) ((COLATTR(posVert) & NAVIGATION_LINE_BY_LINE) ? -1 : 0) +#define EDIT_MODE_INIT 0 // TODO enum typedef int16_t (*FnFuncP) (int16_t x); void DrawFunction(FnFuncP fn, uint8_t offset=0); diff --git a/radio/src/gui/Taranis/navigation.cpp b/radio/src/gui/Taranis/navigation.cpp index b8509711a..6724101e6 100644 --- a/radio/src/gui/Taranis/navigation.cpp +++ b/radio/src/gui/Taranis/navigation.cpp @@ -422,7 +422,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP { case EVT_ENTRY: menuEntryTime = get_tmr10ms(); - l_posVert = POS_VERT_INIT; + l_posVert = MENU_FIRST_LINE_EDIT; l_posHorz = POS_HORZ_INIT(l_posVert); SET_SCROLLBAR_X(LCD_W-1); s_editMode = EDIT_MODE_INIT; @@ -462,7 +462,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP } else { - uint8_t posVertInit = POS_VERT_INIT; + uint8_t posVertInit = MENU_FIRST_LINE_EDIT; if (s_pgOfs != 0 || l_posVert != posVertInit) { s_pgOfs = 0; l_posVert = posVertInit; @@ -495,7 +495,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP } do { - INC(l_posVert, POS_VERT_INIT, rowcount-1); + INC(l_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); s_editMode = 0; // if we go down, we must be in this mode @@ -526,7 +526,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP } do { - DEC(l_posVert, POS_VERT_INIT, rowcount-1); + DEC(l_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); s_editMode = 0; // if we go up, we must be in this mode @@ -600,17 +600,21 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP lcdDrawScrollbar(scrollbar_X, MENU_HEADER_HEIGHT, LCD_H-MENU_HEADER_HEIGHT, s_pgOfs, linesCount, NUM_BODY_LINES); } + if (name) { + title(name); + } + m_posVert = l_posVert; m_posHorz = l_posHorz; } -void check_simple(const char *name, check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount) +void check_simple(const char * name, check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount) { check(name, event, curr, menuTab, menuTabSize, 0, 0, rowcount); } -void check_submenu_simple(const char *name, check_event_t event, uint8_t rowcount) +void check_submenu_simple(const char * name, check_event_t event, uint8_t rowcount) { check_simple(name, event, 0, 0, 0, rowcount); } diff --git a/radio/src/gui/Taranis/popups.cpp b/radio/src/gui/Taranis/popups.cpp index 37cd02783..0fd46346c 100644 --- a/radio/src/gui/Taranis/popups.cpp +++ b/radio/src/gui/Taranis/popups.cpp @@ -70,7 +70,7 @@ void displayPopup(const char *title) void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SOUND_ARG) { - lcd_clear(); + lcdClear(); lcd_bmp(0, 0, asterisk_lbm); #define MESSAGE_LCD_OFFSET 60 diff --git a/radio/src/gui/Taranis/splash.cpp b/radio/src/gui/Taranis/splash.cpp index 4f0e88d7d..d9045b6bf 100644 --- a/radio/src/gui/Taranis/splash.cpp +++ b/radio/src/gui/Taranis/splash.cpp @@ -45,7 +45,7 @@ const pm_uchar * const splash_lbm = splashdata+4; void displaySplash() { - lcd_clear(); + lcdClear(); lcd_bmp(0, 0, splash_lbm); #if MENUS_LOCK == 1 diff --git a/radio/src/gui/Taranis/view_main.cpp b/radio/src/gui/Taranis/view_main.cpp index 7b41f1ab0..d9b91211f 100644 --- a/radio/src/gui/Taranis/view_main.cpp +++ b/radio/src/gui/Taranis/view_main.cpp @@ -268,12 +268,13 @@ void displayTopBar() x -= 12; } - if (TRAINER_CONNECTED() && SLAVE_MODE()) { - LCD_NOTIF_ICON(x, ICON_TRAINEE); - x -= 12; - } - else if (TRAINER_CONNECTED() && !SLAVE_MODE()) { - LCD_NOTIF_ICON(x, ICON_TRAINER); + if (TRAINER_CONNECTED()) { + if (SLAVE_MODE()) { + LCD_NOTIF_ICON(x, ICON_TRAINEE); + } + else { + LCD_NOTIF_ICON(x, ICON_TRAINER); + } x -= 12; } @@ -315,7 +316,7 @@ void displayTimers() TimerState & timerState = timersStates[i]; TimerData & timerData = g_model.timers[i]; uint8_t y = TIMERS_Y + i*TIMERS_H; - if (zlen(timerData.name, LEN_TIMER_NAME) > 0) { + if (ZLEN(timerData.name) > 0) { lcd_putsnAtt(TIMERS_X, y-7, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE); } else { diff --git a/radio/src/gui/Taranis/widgets.cpp b/radio/src/gui/Taranis/widgets.cpp index 65fc53e10..b9ebd0496 100644 --- a/radio/src/gui/Taranis/widgets.cpp +++ b/radio/src/gui/Taranis/widgets.cpp @@ -42,7 +42,7 @@ const pm_uchar bmp_sleep[] PROGMEM = { void displaySleepBitmap() { - lcd_clear(); + lcdClear(); lcd_bmp(76, 2, bmp_sleep, 0, 60); lcdRefresh(); } diff --git a/radio/src/lua_api.cpp b/radio/src/lua_api.cpp index b01e3fa0f..61915ac71 100644 --- a/radio/src/lua_api.cpp +++ b/radio/src/lua_api.cpp @@ -417,7 +417,7 @@ static int luaLcdLock(lua_State *L) static int luaLcdClear(lua_State *L) { - if (luaLcdAllowed) lcd_clear(); + if (luaLcdAllowed) lcdClear(); return 0; } diff --git a/radio/src/main_arm.cpp b/radio/src/main_arm.cpp index 443ee65e8..a0736f7d6 100644 --- a/radio/src/main_arm.cpp +++ b/radio/src/main_arm.cpp @@ -107,66 +107,7 @@ void checkEeprom() } #endif -#if defined(PCBFLAMENCO) -void guiMain(evt_t evt) -{ - lcd_clear(); - -#if defined(LUA) - uint32_t t0 = get_tmr10ms(); - static uint32_t lastLuaTime = 0; - uint16_t interval = (lastLuaTime == 0 ? 0 : (t0 - lastLuaTime)); - lastLuaTime = t0; - if (interval > maxLuaInterval) { - maxLuaInterval = interval; - } - - // run Lua scripts that don't use LCD (to use CPU time while LCD DMA is running) - luaTask(0, RUN_MIX_SCRIPT | RUN_FUNC_SCRIPT | RUN_TELEM_BG_SCRIPT, false); - - // wait for LCD DMA to finish before continuing, because code from this point - // is allowed to change the contents of LCD buffer - // - // WARNING: make sure no code above this line does any change to the LCD display buffer! - // - - bool scriptWasRun = luaTask(evt, RUN_STNDAL_SCRIPT, true); - - t0 = get_tmr10ms() - t0; - if (t0 > maxLuaDuration) { - maxLuaDuration = t0; - } - - if (!scriptWasRun) -#endif - - { - // normal GUI from menus - const char *warn = s_warning; - uint8_t menu = s_menu_count; - // lcd_clear(); - if (menuEvent) { - m_posVert = menuEvent == EVT_ENTRY_UP ? g_menuPos[g_menuStackPtr] : 0; - m_posHorz = 0; - evt = menuEvent; - menuEvent = 0; - AUDIO_MENUS(); - } - g_menuStack[g_menuStackPtr]((warn || menu) ? 0 : evt); - if (warn) DISPLAY_WARNING(evt); - if (menu) { - const char * result = displayMenu(evt); - if (result) { - menuHandler(result); - putEvent(EVT_MENU_UP); - } - } - drawStatusLine(); - } - - lcdRefresh(); -} -#else +#if defined(GUI) void guiMain(evt_t evt) { #if defined(LUA) @@ -192,40 +133,27 @@ void guiMain(evt_t evt) // run Lua scripts that use LCD bool standaloneScriptWasRun = luaTask(evt, RUN_STNDAL_SCRIPT, true); - bool refreshScreen = true; if (!standaloneScriptWasRun) { - refreshScreen = !luaTask(evt, RUN_TELEM_FG_SCRIPT, true); + luaTask(evt, RUN_TELEM_FG_SCRIPT, true); } t0 = get_tmr10ms() - t0; if (t0 > maxLuaDuration) { maxLuaDuration = t0; } - - if (!standaloneScriptWasRun) #else lcdRefreshWait(); // WARNING: make sure no code above this line does any change to the LCD display buffer! - const bool refreshScreen = true; + const bool standaloneScriptWasRun = false; #endif - { -#if defined(PCBHORUS) - for (int i=0; i<2; i++) { + + if (!standaloneScriptWasRun) { + while (1) { +#if !defined(COLORLCD) + lcdClear(); #endif // normal GUI from menus - const char *warn = s_warning; + const char * warn = s_warning; uint8_t menu = s_menu_count; - #if !defined(COLORLCD) - if (refreshScreen) { - lcd_clear(); - } - #endif - if (menuEvent) { - m_posVert = menuEvent == EVT_ENTRY_UP ? g_menuPos[g_menuStackPtr] : -1; - m_posHorz = 0; - evt = menuEvent; - menuEvent = 0; - AUDIO_MENUS(); - } g_menuStack[g_menuStackPtr]((warn || menu) ? 0 : evt); if (warn) DISPLAY_WARNING(evt); if (menu) { @@ -236,11 +164,22 @@ void guiMain(evt_t evt) } } drawStatusLine(); -#if defined(PCBHORUS) - if (menuEvent != EVT_ENTRY || menuEvent != EVT_ENTRY_UP) + if (menuEvent == EVT_ENTRY) { + m_posVert = -1; + m_posHorz = 0; + evt = menuEvent; + menuEvent = 0; + } + else if (menuEvent == EVT_ENTRY_UP) { + m_posVert = g_menuPos[g_menuStackPtr]; + m_posHorz = 0; + evt = menuEvent; + menuEvent = 0; + } + else { break; + } } -#endif } lcdRefresh(); @@ -271,7 +210,7 @@ void perMain() #if defined(USB_MASS_STORAGE) if (usbPlugged()) { // disable access to menus - lcd_clear(); + lcdClear(); menuMainView(0); lcdRefresh(); return; @@ -290,13 +229,13 @@ void perMain() #endif #if defined(PCBTARANIS) && defined(REV9E) && !defined(SIMU) - topLcdRefreshStart(); - setTopFirstTimer(getValue(MIXSRC_FIRST_TIMER+g_model.topLcdTimer)); + toplcdRefreshStart(); + setTopFirstTimer(getValue(MIXSRC_FIRST_TIMER+g_model.toplcdTimer)); setTopSecondTimer(g_eeGeneral.globalTimer + sessionTimer); setTopRssi(TELEMETRY_RSSI()); setTopBatteryValue(g_vbat100mV); setTopBatteryState(GET_TXBATT_BARS(), IS_TXBATT_WARNING()); - topLcdRefreshEnd(); + toplcdRefreshEnd(); #endif #if defined(PCBTARANIS) && defined(REV9E) && !defined(SIMU) diff --git a/radio/src/main_avr.cpp b/radio/src/main_avr.cpp index 367b6d928..cb1738b40 100644 --- a/radio/src/main_avr.cpp +++ b/radio/src/main_avr.cpp @@ -129,7 +129,7 @@ void perMain() const char *warn = s_warning; uint8_t menu = s_menu_count; - lcd_clear(); + lcdClear(); if (menuEvent) { m_posVert = menuEvent == EVT_ENTRY_UP ? g_menuPos[g_menuStackPtr] : 0; m_posHorz = 0; diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 346a5f2e3..df9f5dea9 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -2333,7 +2333,7 @@ PACK(typedef struct { ARM_FIELD(TelemetrySensor telemetrySensors[MAX_SENSORS]) - TARANIS_REV9E_FIELD(uint8_t topLcdTimer) + TARANIS_REV9E_FIELD(uint8_t toplcdTimer) }) ModelData; extern EEGeneral g_eeGeneral; diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 80a53dca6..0fbcc8a48 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -1925,7 +1925,7 @@ void opentxClose() #endif #if !defined(PCBTARANIS) - if (s_storageDirtyMsk & EE_MODEL) { + if (storageDirtyMsk & EE_MODEL) { displayPopup(STR_SAVEMODEL); } #endif @@ -2607,10 +2607,10 @@ int main(void) #if defined(CPUM2560) // Time to switch off - lcd_clear(); + lcdClear(); displayPopup(STR_SHUTDOWN); opentxClose(); - lcd_clear() ; + lcdClear() ; lcdRefresh() ; boardOff(); // Only turn power off if necessary wdt_disable(); @@ -2663,7 +2663,7 @@ uint32_t pwrCheck() #if defined(SHUTDOWN_CONFIRMATION) while (1) { lcdRefreshWait(); - lcd_clear(); + lcdClear(); POPUP_CONFIRMATION("Confirm Shutdown"); evt_t evt = getEvent(false); DISPLAY_WARNING(evt); @@ -2687,7 +2687,7 @@ uint32_t pwrCheck() else { lcdRefreshWait(); unsigned index = pwrPressedDuration() / (PWR_PRESS_SHUTDOWN / 4); - lcd_clear(); + lcdClear(); lcd_bmp(76, 2, bmp_shutdown, index*60, 60); lcdRefresh(); return e_power_press; diff --git a/radio/src/storage/eeprom_conversions.cpp b/radio/src/storage/eeprom_conversions.cpp index 85a9232b5..1506d2fed 100644 --- a/radio/src/storage/eeprom_conversions.cpp +++ b/radio/src/storage/eeprom_conversions.cpp @@ -747,7 +747,7 @@ PACK(typedef struct { TelemetrySensor telemetrySensors[MAX_SENSORS]; - TARANIS_REV9E_FIELD(uint8_t topLcdTimer) + TARANIS_REV9E_FIELD(uint8_t toplcdTimer) }) ModelData_v217; #if defined(PCBTARANIS) @@ -1356,7 +1356,7 @@ void ConvertModel_217_to_218(ModelData & model) newModel.telemetrySensors[i] = oldModel.telemetrySensors[i]; } #if defined(PCBTARANIS) && defined(REV9E) - newModel.topLcdTimer = oldModel.topLcdTimer; + newModel.toplcdTimer = oldModel.toplcdTimer; #endif } @@ -1375,7 +1375,7 @@ void ConvertModel(int id, int version) uint8_t currModel = g_eeGeneral.currModel; g_eeGeneral.currModel = id; - s_storageDirtyMsk = EE_MODEL; + storageDirty(EE_MODEL); storageCheck(true); g_eeGeneral.currModel = currModel; } @@ -1421,7 +1421,7 @@ bool eeConvert() version = 218; ConvertGeneralSettings_217_to_218(g_eeGeneral); } - s_storageDirtyMsk = EE_GENERAL; + storageDirty(EE_GENERAL); storageCheck(true); #if defined(COLORLCD) diff --git a/radio/src/storage/eeprom_raw.cpp b/radio/src/storage/eeprom_raw.cpp index 03f664507..078b9c0ab 100644 --- a/radio/src/storage/eeprom_raw.cpp +++ b/radio/src/storage/eeprom_raw.cpp @@ -375,9 +375,9 @@ void storageCheck(bool immediately) assert(eepromWriteState == EEPROM_IDLE); - if (s_storageDirtyMsk & EE_GENERAL) { + if (storageDirtyMsk & EE_GENERAL) { TRACE("eeprom write general"); - s_storageDirtyMsk -= EE_GENERAL; + storageDirtyMsk -= EE_GENERAL; writeGeneralSettings(); if (immediately) eepromWriteWait(); @@ -385,9 +385,9 @@ void storageCheck(bool immediately) return; } - if (s_storageDirtyMsk & EE_MODEL) { + if (storageDirtyMsk & EE_MODEL) { TRACE("eeprom write model"); - s_storageDirtyMsk -= EE_MODEL; + storageDirtyMsk -= EE_MODEL; writeModel(g_eeGeneral.currModel); if (immediately) eepromWriteWait(); diff --git a/radio/src/storage/eeprom_rlc.cpp b/radio/src/storage/eeprom_rlc.cpp index 160e83a59..2cc1e6ce3 100644 --- a/radio/src/storage/eeprom_rlc.cpp +++ b/radio/src/storage/eeprom_rlc.cpp @@ -865,8 +865,8 @@ void RlcFile::flush() #if defined (EEPROM_PROGRESS_BAR) void RlcFile::DisplayProgressBar(uint8_t x) { - if (s_storageDirtyMsk || isWriting() || eeprom_buffer_size) { - uint8_t len = s_storageDirtyMsk ? 1 : limit((uint8_t)1, (uint8_t)(7 - (m_rlc_len/m_ratio)), (uint8_t)7); + if (storageDirtyMsk || isWriting() || eeprom_buffer_size) { + uint8_t len = storageDirtyMsk ? 1 : limit((uint8_t)1, (uint8_t)(7 - (m_rlc_len/m_ratio)), (uint8_t)7); drawFilledRect(x+1, 0, 5, FH, SOLID, ERASE); drawFilledRect(x+2, 7-len, 3, len); } @@ -940,16 +940,16 @@ void storageCheck(bool immediately) eeFlush(); } - if (s_storageDirtyMsk & EE_GENERAL) { + if (storageDirtyMsk & EE_GENERAL) { TRACE("eeprom write general"); - s_storageDirtyMsk -= EE_GENERAL; + storageDirtyMsk -= EE_GENERAL; theFile.writeRlc(FILE_GENERAL, FILE_TYP_GENERAL, (uint8_t*)&g_eeGeneral, sizeof(EEGeneral), immediately); if (!immediately) return; } - if (s_storageDirtyMsk & EE_MODEL) { + if (storageDirtyMsk & EE_MODEL) { TRACE("eeprom write model"); - s_storageDirtyMsk = 0; + storageDirtyMsk = 0; theFile.writeRlc(FILE_MODEL(g_eeGeneral.currModel), FILE_TYP_MODEL, (uint8_t*)&g_model, sizeof(g_model), immediately); } } diff --git a/radio/src/storage/sdcard_raw.cpp b/radio/src/storage/sdcard_raw.cpp index 062ccc7b5..a1eca7eb0 100644 --- a/radio/src/storage/sdcard_raw.cpp +++ b/radio/src/storage/sdcard_raw.cpp @@ -165,9 +165,9 @@ void storageCheck(bool immediately) // eepromWriteWait(); // } - if (s_storageDirtyMsk & EE_GENERAL) { + if (storageDirtyMsk & EE_GENERAL) { TRACE("eeprom write general"); - s_storageDirtyMsk -= EE_GENERAL; + storageDirtyMsk -= EE_GENERAL; writeGeneralSettings(); // if (immediately) // eepromWriteWait(); @@ -175,9 +175,9 @@ void storageCheck(bool immediately) return; } - if (s_storageDirtyMsk & EE_MODEL) { + if (storageDirtyMsk & EE_MODEL) { TRACE("eeprom write model"); - s_storageDirtyMsk -= EE_MODEL; + storageDirtyMsk -= EE_MODEL; const char * error = writeModel(); if (error) { TRACE("writeModel error=%s", error); diff --git a/radio/src/storage/storage.h b/radio/src/storage/storage.h index 30f15aacc..71af54093 100644 --- a/radio/src/storage/storage.h +++ b/radio/src/storage/storage.h @@ -49,10 +49,10 @@ #define WRITE_DELAY_10MS 200 #endif -extern uint8_t s_storageDirtyMsk; -extern tmr10ms_t s_storageDirtyTime10ms; +extern uint8_t storageDirtyMsk; +extern tmr10ms_t storageDirtyTime10ms; -#define TIME_TO_WRITE() (s_storageDirtyMsk && (tmr10ms_t)(get_tmr10ms() - s_storageDirtyTime10ms) >= (tmr10ms_t)WRITE_DELAY_10MS) +#define TIME_TO_WRITE() (storageDirtyMsk && (tmr10ms_t)(get_tmr10ms() - storageDirtyTime10ms) >= (tmr10ms_t)WRITE_DELAY_10MS) void storageEraseAll(bool warn); void storageFormat(); diff --git a/radio/src/storage/storage_common.cpp b/radio/src/storage/storage_common.cpp index ee4df2e72..9dc2ab2c6 100644 --- a/radio/src/storage/storage_common.cpp +++ b/radio/src/storage/storage_common.cpp @@ -36,13 +36,13 @@ #include "../opentx.h" -uint8_t s_storageDirtyMsk; -tmr10ms_t s_storageDirtyTime10ms; +uint8_t storageDirtyMsk; +tmr10ms_t storageDirtyTime10ms; void storageDirty(uint8_t msk) { - s_storageDirtyMsk |= msk; - s_storageDirtyTime10ms = get_tmr10ms() ; + storageDirtyMsk |= msk; + storageDirtyTime10ms = get_tmr10ms() ; } void preModelLoad() diff --git a/radio/src/targets/Taranis/board_taranis.cpp b/radio/src/targets/Taranis/board_taranis.cpp index e73accc14..c78b31eee 100644 --- a/radio/src/targets/Taranis/board_taranis.cpp +++ b/radio/src/targets/Taranis/board_taranis.cpp @@ -157,7 +157,7 @@ void boardInit() #if defined(REV9E) if (!WAS_RESET_BY_WATCHDOG_OR_SOFTWARE()) { - lcd_clear(); + lcdClear(); lcd_bmp(76, 2, bmp_lock, 0, 60); lcdRefresh(); lcdRefreshWait(); @@ -169,7 +169,7 @@ void boardInit() duration = get_tmr10ms() - start; if (duration < PWR_PRESS_DURATION_MIN) { unsigned index = duration / (PWR_PRESS_DURATION_MIN / 4); - lcd_clear(); + lcdClear(); lcd_bmp(76, 2, bmp_startup, index*60, 60); } else if (duration >= PWR_PRESS_DURATION_MAX) { @@ -193,7 +193,7 @@ void boardInit() else { backlightInit(); } - topLcdInit(); + toplcdInit(); #else backlightInit(); #endif @@ -203,7 +203,7 @@ void boardOff() { BACKLIGHT_OFF(); #if defined(REV9E) - topLcdOff(); + toplcdOff(); #endif #if defined(REV9E) diff --git a/radio/src/targets/Taranis/board_taranis.h b/radio/src/targets/Taranis/board_taranis.h index 89296cd5c..1be5ca851 100644 --- a/radio/src/targets/Taranis/board_taranis.h +++ b/radio/src/targets/Taranis/board_taranis.h @@ -347,10 +347,10 @@ void lcdOff(void); // Top LCD driver #if defined(REV9E) -void topLcdInit(void); -void topLcdOff(void); -void topLcdRefreshStart(void); -void topLcdRefreshEnd(void); +void toplcdInit(void); +void toplcdOff(void); +void toplcdRefreshStart(void); +void toplcdRefreshEnd(void); void setTopFirstTimer(int32_t value); void setTopSecondTimer(uint32_t value); void setTopRssi(uint32_t rssi); diff --git a/radio/src/targets/Taranis/bootloader/boot.cpp b/radio/src/targets/Taranis/bootloader/boot.cpp index 1400fc4bc..fb47cb1e6 100644 --- a/radio/src/targets/Taranis/bootloader/boot.cpp +++ b/radio/src/targets/Taranis/bootloader/boot.cpp @@ -402,7 +402,7 @@ int main() lcdInit(); backlightInit(); - lcd_clear(); + lcdClear(); lcd_putsn(0, 0, (const char *)bootloaderVersion, 0); // trick to avoid bootloaderVersion to be optimized out ... lcd_putsLeft(0, BOOTLOADER_TITLE); lcd_invert_line(0); @@ -427,7 +427,7 @@ int main() Tenms = 0; lcdRefreshWait(); - lcd_clear(); + lcdClear(); lcd_putsLeft(0, BOOTLOADER_TITLE); lcd_invert_line(0); @@ -686,7 +686,7 @@ int main() if (state == ST_REBOOT) { if (readKeys() == 0) { - lcd_clear(); + lcdClear(); lcdRefresh(); lcdRefreshWait(); RCC->CSR |= RCC_CSR_RMVF; //clear the reset flags in RCC clock control & status register diff --git a/radio/src/targets/Taranis/top_lcd_driver.cpp b/radio/src/targets/Taranis/top_lcd_driver.cpp index 938c602ee..566cd27fb 100644 --- a/radio/src/targets/Taranis/top_lcd_driver.cpp +++ b/radio/src/targets/Taranis/top_lcd_driver.cpp @@ -233,7 +233,7 @@ extern "C" void TIM8_BRK_TIM12_IRQHandler() } #endif -void topLcdInit() +void toplcdInit() { RCC_AHB1PeriphClockCmd(TOPLCD_RCC_AHB1Periph, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; @@ -254,15 +254,15 @@ void topLcdInit() ht1621SendCommand(1, 0x01); ht1621SendCommand(1, 0x29); - topLcdRefreshStart(); - topLcdRefreshEnd(); + toplcdRefreshStart(); + toplcdRefreshEnd(); } -void topLcdOff() +void toplcdOff() { BL_OFF(); - topLcdRefreshStart(); - topLcdRefreshEnd(); + toplcdRefreshStart(); + toplcdRefreshEnd(); } void setTopFirstTimer(int32_t value) @@ -419,13 +419,13 @@ void setTopSecondTimer(uint32_t value) Ht1621Data1[3] |= 0x10; // "Operation Time" } -void topLcdRefreshStart() +void toplcdRefreshStart() { memset(Ht1621Data1, 0, sizeof(Ht1621Data1)); memset(Ht1621Data2, 0, sizeof(Ht1621Data2)); } -void topLcdRefreshEnd() +void toplcdRefreshEnd() { ht1621WrAllData(0, Ht1621Data1); ht1621WrAllData(1, Ht1621Data2); diff --git a/radio/src/tasks_arm.cpp b/radio/src/tasks_arm.cpp index 523153b33..502336dc4 100644 --- a/radio/src/tasks_arm.cpp +++ b/radio/src/tasks_arm.cpp @@ -192,7 +192,7 @@ void menusTask(void * pdata) } #if defined(PCBTARANIS) && defined(REV9E) - topLcdOff(); + toplcdOff(); #endif BACKLIGHT_OFF(); @@ -201,7 +201,7 @@ void menusTask(void * pdata) #elif defined(PCBTARANIS) displaySleepBitmap(); #else - lcd_clear(); + lcdClear(); displayPopup(STR_SHUTDOWN); #endif diff --git a/radio/src/tests/frsky.cpp b/radio/src/tests/frsky.cpp index c82109319..5dd2391ad 100644 --- a/radio/src/tests/frsky.cpp +++ b/radio/src/tests/frsky.cpp @@ -341,7 +341,7 @@ TEST(FrSkySPORT, frskySetCellVoltage) EXPECT_EQ(telemetryItems[0].valueMax, 2071); //display test - lcd_clear(); + lcdClear(); g_model.frsky.voltsSource = FRSKY_VOLTS_SOURCE_A1; } @@ -416,7 +416,7 @@ TEST(FrSkySPORT, frskySetCellVoltageTwoSensors) EXPECT_EQ(telemetryItems[2].valueMax, 287); //display test - lcd_clear(); + lcdClear(); g_model.frsky.voltsSource = FRSKY_VOLTS_SOURCE_A1; } diff --git a/radio/src/tests/lcd.cpp b/radio/src/tests/lcd.cpp index 8d3521112..e1200efb5 100644 --- a/radio/src/tests/lcd.cpp +++ b/radio/src/tests/lcd.cpp @@ -114,7 +114,7 @@ bool checkScreenshot(const QString & test) TEST(outdezNAtt, test_unsigned) { - lcd_clear(); + lcdClear(); lcd_outdezNAtt(0, 0, 65530, LEFT|UNSIGN); EXPECT_TRUE(checkScreenshot("unsigned")) << "Unsigned numbers will be bad displayed"; } @@ -122,7 +122,7 @@ TEST(outdezNAtt, test_unsigned) #if defined(CPUARM) TEST(outdezNAtt, testBigNumbers) { - lcd_clear(); + lcdClear(); lcd_outdezNAtt(0, 0, 1234567, LEFT); lcd_outdezNAtt(0, FH, -1234567, LEFT); EXPECT_TRUE(checkScreenshot("big_numbers")); @@ -132,28 +132,28 @@ TEST(outdezNAtt, testBigNumbers) TEST(Lcd, Invers_0_0) { - lcd_clear(); + lcdClear(); lcd_putsAtt(0, 0, "Test", INVERS); EXPECT_TRUE(checkScreenshot("invers_0_0")); } TEST(Lcd, Invers_0_1) { - lcd_clear(); + lcdClear(); lcd_putsAtt(0, 1, "Test", INVERS); EXPECT_TRUE(checkScreenshot("invers_0_1")); } TEST(Lcd, Prec2_Left) { - lcd_clear(); + lcdClear(); lcd_outdezAtt(0, 0, 2, PREC2|LEFT); EXPECT_TRUE(checkScreenshot("prec2_left")); } TEST(Lcd, Prec2_Right) { - lcd_clear(); + lcdClear(); lcd_outdezAtt(LCD_W, LCD_H-FH, 2, PREC2); EXPECT_TRUE(checkScreenshot("prec2_right")); } @@ -161,7 +161,7 @@ TEST(Lcd, Prec2_Right) #if defined(CPUARM) TEST(Lcd, Prec1_Dblsize_Invers) { - lcd_clear(); + lcdClear(); lcd_outdezAtt(LCD_W, 10, 51, PREC1|DBLSIZE|INVERS); EXPECT_TRUE(checkScreenshot("prec1_dblsize_invers")); } @@ -169,14 +169,14 @@ TEST(Lcd, Prec1_Dblsize_Invers) TEST(Lcd, Line_Wrap) { - lcd_clear(); + lcdClear(); lcd_puts(LCD_W-10, 0, "TEST"); EXPECT_TRUE(checkScreenshot("line_wrap")); } TEST(Lcd, DblsizeBottomRight) { - lcd_clear(); + lcdClear(); lcd_putsAtt(LCD_W-20, LCD_H-16, "TEST", DBLSIZE); EXPECT_TRUE(checkScreenshot("dblsize_bottom_right")); } @@ -184,7 +184,7 @@ TEST(Lcd, DblsizeBottomRight) #if defined(CPUARM) TEST(Lcd, Smlsize_putsStrIdx) { - lcd_clear(); + lcdClear(); putsStrIdx(0, 0, "FM", 0, SMLSIZE); EXPECT_TRUE(checkScreenshot("smlsize_putsstridx")); } @@ -192,7 +192,7 @@ TEST(Lcd, Smlsize_putsStrIdx) TEST(Lcd, vline) { - lcd_clear(); + lcdClear(); for (int x=0; x<100; x+=2) { lcd_vline(x, x/2, 12); } @@ -202,7 +202,7 @@ TEST(Lcd, vline) #if defined(CPUARM) TEST(Lcd, vline_x_lt0) { - lcd_clear(); + lcdClear(); lcd_vline(50, -10, 12); lcd_vline(100, -10, 1); EXPECT_TRUE(checkScreenshot("vline_lt0")); @@ -212,7 +212,7 @@ TEST(Lcd, vline_x_lt0) #if defined(CPUARM) TEST(Lcd, Smlsize) { - lcd_clear(); + lcdClear(); lcd_putsAtt(0, 0, "TESTgy,", SMLSIZE); lcd_putsAtt(10, 22, "TESTgy,", SMLSIZE|INVERS); drawFilledRect(8, 40, 100, 20); @@ -229,7 +229,7 @@ TEST(Lcd, Smlsize) TEST(Lcd, Stdsize) { - lcd_clear(); + lcdClear(); lcd_putsAtt(0, 0, "TEST", 0); lcd_putsAtt(10, 22, "TEST", INVERS); drawFilledRect(8, 40, 100, 20); @@ -246,7 +246,7 @@ TEST(Lcd, Stdsize) TEST(Lcd, Midsize) { - lcd_clear(); + lcdClear(); lcd_putsAtt(0, 0, "TEST", MIDSIZE); lcd_putsAtt(10, 22, "TEST", MIDSIZE|INVERS); drawFilledRect(8, 40, 100, 20); @@ -263,7 +263,7 @@ TEST(Lcd, Midsize) TEST(Lcd, Dblsize) { - lcd_clear(); + lcdClear(); lcd_putsAtt(2, 10, "TST", DBLSIZE); lcd_putsAtt(42, 10, "TST", DBLSIZE|INVERS); drawFilledRect(80, 8, 46, 24); @@ -282,7 +282,7 @@ TEST(Lcd, Dblsize) #if defined(PCBTARANIS) TEST(Lcd, DrawSwitch) { - lcd_clear(); + lcdClear(); putsSwitches(0, 10, SWSRC_SA0, 0); putsSwitches(30, 10, SWSRC_SA0, SMLSIZE); // putsSwitches(60, 10, SWSRC_SA0, MIDSIZE); missing arrows in this font @@ -294,7 +294,7 @@ TEST(Lcd, DrawSwitch) #if defined(PCBTARANIS) TEST(Lcd, BMPWrapping) { - lcd_clear(); + lcdClear(); uint8_t bitmap[2+40*40/2]; bmpLoad(bitmap, "./tests/plane.bmp", 40, 40); lcd_bmp(200, 0, bitmap); @@ -308,7 +308,7 @@ TEST(Lcd, BMPWrapping) #if defined(PCBTARANIS) TEST(Lcd, lcdDrawHorizontalLine) { - lcd_clear(); + lcdClear(); lcdDrawHorizontalLine(0, 10, LCD_W, DOTTED); lcdDrawHorizontalLine(0, 20, LCD_W, SOLID); lcdDrawHorizontalLine(50, 30, LCD_W, 0xEE); //too wide @@ -321,7 +321,7 @@ TEST(Lcd, lcdDrawHorizontalLine) #if defined(PCBTARANIS) TEST(Lcd, lcdDrawVerticalLine) { - lcd_clear(); + lcdClear(); lcdDrawVerticalLine(10, 0, LCD_H, DOTTED); lcdDrawVerticalLine(20, 0, LCD_H, SOLID); lcdDrawVerticalLine(30, 30, LCD_H, 0xEE); //too high @@ -361,7 +361,7 @@ public: #if defined(PCBTARANIS) TEST(Lcd, lcd_bmpLoadAndDisplay) { - lcd_clear(); + lcdClear(); // Test proper BMP files, they should display correctly { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(7, 32)); @@ -415,7 +415,7 @@ TEST(Lcd, lcdDrawLine) int start, length, xOffset; uint8_t pattern; - lcd_clear(); + lcdClear(); start = 5; pattern = SOLID; diff --git a/radio/util/compile.py b/radio/util/compile.py deleted file mode 100755 index c41dcfd82..000000000 --- a/radio/util/compile.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python - -import os, sys, shutil, platform, getpass, subprocess, zipfile, ftplib, httplib, threading - -BINARY_DIR = "../build/" - -options_stock = [[("", "EXT=STD"), ("frsky", "EXT=FRSKY"), ("jeti", "EXT=JETI"), ("ardupilot", "EXT=ARDUPILOT"), ("nmea", "EXT=NMEA")], - [("", "HELI=NO"), ("heli", "HELI=YES")], - [("", "TEMPLATES=NO"), ("templates", "TEMPLATES=YES")], - [("", ""), ("nosplash", "SPLASH=NO")], - [("", ""), ("nofp", "FLIGHT_PHASES=NO")], - [("", "AUDIO=NO"), ("audio", "AUDIO=YES")], - [("", "HAPTIC=NO"), ("haptic", "HAPTIC=YES")], - [("", "PXX=NO"), ("PXX", "PXX=YES")], - [("", "DSM2=NO"), ("DSM2", "DSM2=PPM")], - [("", ""), ("ppmca", "PPM_CENTER_ADJUSTABLE=YES")], - [("", "NAVIGATION=NO"), ("potscroll", "NAVIGATION=POTS")], - [("", ""), ("autoswitch", "AUTOSWITCH=YES")], - [("", ""), ("pgbar", "EEPROM_PROGRESS_BAR=YES")], - [("", "UNITS=METRIC"), ("imperial", "UNITS=IMPERIAL")], - ] - -options_v4 = [[("", "EXT=FRSKY")], - [("", "HELI=NO"), ("heli", "HELI=YES")], - [("", "TEMPLATES=NO"), ("templates", "TEMPLATES=YES")], - [("", ""), ("nofp", "FLIGHT_PHASES=NO")], - [("", "SDCARD=NO"), ("sdcard", "SDCARD=YES")], - [("", "SOMO=NO"), ("SOMO", "SOMO=YES")], - [("", ""), ("ppmca", "PPM_CENTER_ADJUSTABLE=YES")], - [("", ""), ("autoswitch", "AUTOSWITCH=YES")], - [("", ""), ("pgbar", "EEPROM_PROGRESS_BAR=YES")], - [("", "UNITS=METRIC"), ("imperial", "UNITS=IMPERIAL")], - ] - -options_arm = [[("", "EXT=FRSKY")], - [("", "HELI=NO"), ("heli", "HELI=YES")], - [("", "TEMPLATES=NO"), ("templates", "TEMPLATES=YES")], - [("", ""), ("nofp", "FLIGHT_PHASES=NO")], - [("", ""), ("autoswitch", "AUTOSWITCH=YES")], - [("", "UNITS=METRIC"), ("imperial", "UNITS=IMPERIAL")], - ] - -languages = ["en", "fr", "se"] - -#ftp_host = "ftpperso.free.fr" -#ftp_user = "open9x" -ftp_host = "open9x.freehosting.com" -ftp_user = "openxfre" -ftp_directory = "public_html/binaries/" - -def upload(binaries, ext, stamp): - if not ftp: - return - - print "Zip creation..." - zip_name = "release.zip" - zip = zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED) - for bin in binaries: - zip.write(BINARY_DIR + bin + "." + ext, bin + "." + ext) - zip.write(BINARY_DIR + stamp, stamp) - zip.close() - - print "FTP transfer..." - ftp_connection = ftplib.FTP(ftp_host, ftp_user, ftp_password) - try: - ftp_connection.delete(ftp_directory + zip_name) - except: - pass - f = open(zip_name, 'rb') - ftp_connection.storbinary('STOR ' + ftp_directory + zip_name, f) - f.close() - ftp_connection.quit() - - print "ZIP extraction...", - http_connection = httplib.HTTPConnection("open9x.freehosting.com") - http_connection.request("GET", "/binaries/uncompress.php") - response = http_connection.getresponse() - print response.status, response.reason - os.remove(zip_name) - -global_current = 0 -global_count = 0 -def generate(hex, arg, extension, options, languages, maxsize): - global global_current - global global_count - - result = [] - states = [0] * len(options) - - count = len(languages) - for option in options: - count *= len(option) - global_count += count - - while 1: - # print index, states - - for language in languages: - hex_file = hex - make_args = ["make", arg] - for i, option in enumerate(options): - state = states[len(options) - 1 - i] - if option[state][0]: - hex_file += "-" + option[state][0] - make_args.append(option[state][1]) - hex_file += "-" + language - make_args.append("TRANSLATIONS=" + language.upper()) - if mt: - cwd = language - else: - cwd = "." - - size = 0 - if compile: - subprocess.Popen(["make", "clean", arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd).wait() - p = subprocess.Popen(make_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) - ret = p.wait() - stderr = p.stderr.read() - global_current += 1 - print "[%d/%d]" % (global_current, global_count), hex_file - if ret or "error" in stderr: - print stderr - exit() - for line in stderr.split("\n"): - if "warning" in line: - print " ", line - for line in p.stdout.readlines(): - if line.startswith("Program:"): - parts = line.split(" ") - while "" in parts: - parts.remove("") - size = int(parts[1]) - if size > maxsize: - print " ", line[:-1], "[NOT RELEASED]" - else: - print " ", line, - if line.startswith("Data:"): - print " ", line, - - if size <= maxsize: - binary_name = hex_file + "." + extension - if compile: - shutil.copyfile(cwd + "/open9x." + extension, BINARY_DIR + binary_name) - result.append(hex_file) - - for index, state in enumerate(states): - if state < len(options[len(options) - 1 - index]) - 1: - for i in range(index): - states[i] = 0 - states[index] += 1 - break - else: - break - - # stamp - stamp = "stamp-open9x-%s.txt" % hex.split("-")[1] - subprocess.Popen(["make", hex.split("-")[1] + "-stamp"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd="../src").wait() - shutil.copyfile("../src/" + stamp, BINARY_DIR + stamp) - - return result, stamp - -class GenerateThread(threading.Thread): - def __init__ (self, hex, arg, extension, options, language, maxsize): - threading.Thread.__init__(self) - self.hex = hex - self.arg = arg - self.extension = extension - self.options = options - self.language = language - self.maxsize = maxsize - - def run(self): - self.hexes, self.stamp = generate(self.hex, self.arg, self.extension, self.options, [self.language], self.maxsize) - -def multithread_generate(hex, arg, extension, options, languages, maxsize): - if mt: - result = [] - threads = [] - for language in languages: - thread = GenerateThread(hex, arg, extension, options, language, maxsize) - threads.append(thread) - thread.start() - for thread in threads: - thread.join() - result.extend(thread.hexes) - stamp = thread.stamp - return result, stamp - else: - return generate(hex, arg, extension, options, languages, maxsize) - - -def generate_c9x_list(filename, hexes, board): - f = open(filename, "w") - f.write("const char *open9x_%s_binaries[] = {\n" % board) - for hex in hexes: - f.write('"%s",\n' % hex) - f.write("0\n};\n") - -if __name__ == "__main__": - - ftp = "ftp" in sys.argv - mt = "mt" in sys.argv and platform.system() != "Windows" - compile = not "nc" in sys.argv - - if ftp: - ftp_password = getpass.getpass() - - if mt: - shutil.rmtree("util", True) - shutil.copytree("../util", "util") - for lang in languages: - print "Directory %s creation..." % lang - shutil.rmtree(lang, True) - shutil.copytree("../src", lang) - - if platform.system() == "Windows": - # arm board - hexes, stamp = generate("open9x-arm", "PCB=ARM", "bin", options_arm, languages, 262000) - generate_c9x_list("../../companion9x/src/open9x-arm-binaries.cpp", hexes, "arm") - upload(hexes, "bin", stamp) - - else: - # stock board - if not "v4" in sys.argv: - hexes, stamp = multithread_generate("open9x-stock", "PCB=STD", "hex", options_stock, languages, 65530) - generate_c9x_list("../../companion9x/src/open9x-stock-binaries.cpp", hexes, "stock") - upload(hexes, "hex", stamp) - - # v4 board - if not "stock" in sys.argv: - hexes, stamp = multithread_generate("open9x-v4", "PCB=V4", "hex", options_v4, languages, 262000) - generate_c9x_list("../../companion9x/src/open9x-v4-binaries.cpp", hexes, "v4") - upload(hexes, "hex", stamp) - From fb371cf5b7df3310b941c33e2dcabbe6e1121b70 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 22 Oct 2015 20:18:55 +0200 Subject: [PATCH 34/74] =?UTF-8?q?[Horus]=C2=A0Continued=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- radio/src/lua_api.cpp | 18 +++++++++++++++--- radio/src/myeeprom.h | 11 ++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/radio/src/lua_api.cpp b/radio/src/lua_api.cpp index 61915ac71..6f6a70c9e 100644 --- a/radio/src/lua_api.cpp +++ b/radio/src/lua_api.cpp @@ -52,10 +52,12 @@ extern "C" { } #endif -#if defined(PCBFLAMENCO) - #include "lua_exports_flamenco.inc" // this line must be after lua headers +#if defined(PCBHORUS) + #include "lua_exports_horus.inc" // this line must be after lua headers +#elif defined(PCBFLAMENCO) + #include "lua_exports_flamenco.inc" // this line must be after lua headers #elif defined(PCBTARANIS) - #include "lua_exports_taranis.inc" // this line must be after lua headers + #include "lua_exports_taranis.inc" // this line must be after lua headers #endif #define lua_registernumber(L, n, i) (lua_pushnumber(L, (i)), lua_setglobal(L, (n))) @@ -392,12 +394,14 @@ static int luaKillEvents(lua_State *L) return 0; } +#if !defined(COLORLCD) static int luaGrey(lua_State *L) { int index = luaL_checkinteger(L, 1); lua_pushunsigned(L, GREY(index)); return 1; } +#endif static int luaGetGeneralSettings(lua_State *L) { @@ -1465,7 +1469,9 @@ const luaL_Reg opentxLib[] = { { "defaultStick", luaDefaultStick }, { "defaultChannel", luaDefaultChannel }, { "killEvents", luaKillEvents }, +#if !defined(COLORLCD) { "GREY", luaGrey }, +#endif { NULL, NULL } /* sentinel */ }; @@ -1525,8 +1531,10 @@ const luaR_value_entry opentxConstants[] = { { "MIXSRC_CH1", MIXSRC_CH1 }, { "SWSRC_LAST", SWSRC_LAST_LOGICAL_SWITCH }, { "EVT_MENU_BREAK", EVT_KEY_BREAK(KEY_MENU) }, +#if !defined(PCBHORUS) { "EVT_PAGE_BREAK", EVT_KEY_BREAK(KEY_PAGE) }, { "EVT_PAGE_LONG", EVT_KEY_LONG(KEY_PAGE) }, +#endif { "EVT_ENTER_BREAK", EVT_KEY_BREAK(KEY_ENTER) }, { "EVT_ENTER_LONG", EVT_KEY_LONG(KEY_ENTER) }, { "EVT_EXIT_BREAK", EVT_KEY_BREAK(KEY_EXIT) }, @@ -1536,8 +1544,10 @@ const luaR_value_entry opentxConstants[] = { { "EVT_MINUS_FIRST", EVT_KEY_FIRST(KEY_MINUS) }, { "EVT_PLUS_REPT", EVT_KEY_REPT(KEY_PLUS) }, { "EVT_MINUS_REPT", EVT_KEY_REPT(KEY_MINUS) }, +#if !defined(COLORLCD) { "FILL_WHITE", FILL_WHITE }, { "GREY_DEFAULT", GREY_DEFAULT }, +#endif { "SOLID", SOLID }, { "DOTTED", DOTTED }, { "FORCE", FORCE }, @@ -1874,7 +1884,9 @@ char lua_warning_info[LUA_WARNING_INFO_LEN+1]; void displayLuaError(const char * title) { +#if !defined(COLORLCD) displayBox(title); +#endif if (lua_warning_info[0]) { char * split = strstr(lua_warning_info, ": "); if (split) { diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index df9f5dea9..ab21f4a17 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -239,6 +239,7 @@ extern CurveInfo curveInfo(uint8_t idx); #define LEN_MODEL_NAME 12 #define LEN_TIMER_NAME 8 #define LEN_FLIGHT_MODE_NAME 10 + #define LEN_BITMAP_NAME 10 #define LEN_EXPOMIX_NAME 6 #define LEN_CHANNEL_NAME 6 #define LEN_INPUT_NAME 4 @@ -1934,7 +1935,15 @@ enum MixSources { MIXSRC_Ail, LUA_EXPORT("ail", "Aileron") MIXSRC_FIRST_POT, -#if defined(PCBFLAMENCO) +#if defined(PCBHORUS) + MIXSRC_POT1 = MIXSRC_FIRST_POT, LUA_EXPORT("s1", "Potentiometer 1") + MIXSRC_POT2, LUA_EXPORT("s2", "Potentiometer 2") + MIXSRC_POT3, LUA_EXPORT("s3", "Potentiometer 3") + MIXSRC_FIRST_SLIDER, + MIXSRC_SLIDER1 = MIXSRC_FIRST_SLIDER, LUA_EXPORT("ls", "Left slider") + MIXSRC_SLIDER2, LUA_EXPORT("rs", "Right slider") + MIXSRC_LAST_POT = MIXSRC_SLIDER2, +#elif defined(PCBFLAMENCO) MIXSRC_POT1 = MIXSRC_FIRST_POT, LUA_EXPORT("sd", "Potentiometer D") MIXSRC_SLIDER1, LUA_EXPORT("ls", "Left slider") MIXSRC_SLIDER2, LUA_EXPORT("rs", "Right slider") From ab0272c16cf02dd85b600abed74e73e569e316af Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 24 Oct 2015 11:27:34 +0200 Subject: [PATCH 35/74] [Horus] Refactoring continued --- radio/src/audio_arm.cpp | 7 +- radio/src/gui/9X/gui.h | 2 +- radio/src/gui/9X/lcd.cpp | 42 ++-- radio/src/gui/9X/lcd.h | 6 +- radio/src/gui/9X/menu_general_calib.cpp | 4 +- radio/src/gui/9X/menu_general_diagkeys.cpp | 4 +- radio/src/gui/9X/menu_general_hardware.cpp | 2 +- radio/src/gui/9X/menu_general_sdmanager.cpp | 2 +- radio/src/gui/9X/menu_general_setup.cpp | 16 +- radio/src/gui/9X/menu_general_trainer.cpp | 6 +- radio/src/gui/9X/menu_model.cpp | 2 +- radio/src/gui/9X/menu_model_curves.cpp | 2 +- .../gui/9X/menu_model_custom_functions.cpp | 20 +- radio/src/gui/9X/menu_model_flightmodes.cpp | 4 +- radio/src/gui/9X/menu_model_inputs_mixes.cpp | 32 +-- radio/src/gui/9X/menu_model_limits.cpp | 4 +- .../gui/9X/menu_model_logical_switches.cpp | 18 +- radio/src/gui/9X/menu_model_select.cpp | 10 +- radio/src/gui/9X/menu_model_setup.cpp | 34 +-- radio/src/gui/9X/menu_model_telemetry.cpp | 32 +-- radio/src/gui/9X/menu_model_templates.cpp | 2 +- radio/src/gui/9X/menus.h | 33 ++- radio/src/gui/9X/navigation.cpp | 8 +- radio/src/gui/9X/popups.cpp | 22 +- radio/src/gui/9X/view_about.cpp | 68 +++--- radio/src/gui/9X/view_main.cpp | 10 +- radio/src/gui/9X/view_mavlink.cpp | 26 +-- radio/src/gui/9X/view_statistics.cpp | 6 +- radio/src/gui/9X/view_telemetry.cpp | 10 +- radio/src/gui/9X/view_text.cpp | 4 +- radio/src/gui/9X/widgets.cpp | 12 +- radio/src/gui/Horus/gui.h | 4 +- radio/src/gui/Horus/lcd.cpp | 86 +++---- radio/src/gui/Horus/lcd.h | 31 ++- radio/src/gui/Horus/menu_general.cpp | 4 +- .../src/gui/Horus/menu_general_sdmanager.cpp | 15 +- radio/src/gui/Horus/menu_general_setup.cpp | 42 ++-- radio/src/gui/Horus/menu_general_trainer.cpp | 12 +- radio/src/gui/Horus/menu_model.cpp | 36 ++- radio/src/gui/Horus/menu_model_curves.cpp | 14 +- .../gui/Horus/menu_model_custom_functions.cpp | 28 +-- .../src/gui/Horus/menu_model_flightmodes.cpp | 15 +- .../src/gui/Horus/menu_model_inputs_mixes.cpp | 35 ++- radio/src/gui/Horus/menu_model_limits.cpp | 14 +- .../gui/Horus/menu_model_logical_switches.cpp | 10 +- radio/src/gui/Horus/menu_model_select.cpp | 213 ++---------------- radio/src/gui/Horus/menu_model_setup.cpp | 75 ++++-- radio/src/gui/Horus/menu_model_telemetry.cpp | 16 +- radio/src/gui/Horus/menus.h | 61 ++--- radio/src/gui/Horus/navigation.cpp | 180 +++++++-------- radio/src/gui/Horus/popups.cpp | 49 ++-- radio/src/gui/Horus/view_about.cpp | 18 +- radio/src/gui/Horus/view_channels.cpp | 2 +- radio/src/gui/Horus/view_main.cpp | 31 +-- radio/src/gui/Horus/view_statistics.cpp | 16 +- radio/src/gui/Horus/view_text.cpp | 2 +- radio/src/gui/Horus/widgets.cpp | 34 +-- radio/src/gui/Taranis/gui.h | 4 +- radio/src/gui/Taranis/lcd.cpp | 70 +++--- radio/src/gui/Taranis/lcd.h | 12 +- radio/src/gui/Taranis/menu_general_calib.cpp | 4 +- .../src/gui/Taranis/menu_general_diagkeys.cpp | 2 +- .../src/gui/Taranis/menu_general_hardware.cpp | 16 +- .../gui/Taranis/menu_general_sdmanager.cpp | 6 +- radio/src/gui/Taranis/menu_general_setup.cpp | 34 +-- .../src/gui/Taranis/menu_general_trainer.cpp | 6 +- .../src/gui/Taranis/menu_general_version.cpp | 2 +- radio/src/gui/Taranis/menu_model.cpp | 5 +- radio/src/gui/Taranis/menu_model_curves.cpp | 10 +- .../Taranis/menu_model_custom_functions.cpp | 28 +-- .../gui/Taranis/menu_model_custom_scripts.cpp | 12 +- .../gui/Taranis/menu_model_inputs_mixes.cpp | 28 +-- radio/src/gui/Taranis/menu_model_limits.cpp | 4 +- .../Taranis/menu_model_logical_switches.cpp | 10 +- radio/src/gui/Taranis/menu_model_select.cpp | 12 +- radio/src/gui/Taranis/menu_model_setup.cpp | 41 ++-- .../src/gui/Taranis/menu_model_telemetry.cpp | 24 +- radio/src/gui/Taranis/menus.h | 31 ++- radio/src/gui/Taranis/navigation.cpp | 37 ++- radio/src/gui/Taranis/popups.cpp | 23 +- radio/src/gui/Taranis/view_about.cpp | 68 +++--- radio/src/gui/Taranis/view_channels.cpp | 2 +- radio/src/gui/Taranis/view_main.cpp | 12 +- radio/src/gui/Taranis/view_statistics.cpp | 12 +- radio/src/gui/Taranis/view_telemetry.cpp | 2 +- radio/src/gui/Taranis/view_text.cpp | 4 +- radio/src/gui/Taranis/widgets.cpp | 16 +- radio/src/gui/gui_helpers.h | 2 + radio/src/keys.h | 63 +----- radio/src/lua_api.cpp | 14 +- radio/src/myeeprom.h | 8 +- radio/src/opentx.cpp | 8 - radio/src/opentx.h | 23 +- radio/src/simu.cpp | 16 +- radio/src/storage/eeprom_rlc.cpp | 2 +- radio/src/storage/eeprom_rlc.h | 4 +- radio/src/storage/sdcard_raw.cpp | 2 + radio/src/switches.cpp | 10 +- radio/src/targets/Horus/diskio.cpp | 2 - radio/src/targets/Taranis/bootloader/boot.cpp | 2 +- radio/src/telemetry/ardupilot.cpp | 34 +-- radio/src/telemetry/nmea.cpp | 40 ++-- radio/src/tests/lcd.cpp | 38 ++-- radio/src/translations.cpp | 1 + radio/src/translations.h | 1 + radio/src/translations/cz.h.txt | 1 + radio/src/translations/de.h.txt | 1 + radio/src/translations/en.h.txt | 1 + radio/src/translations/es.h.txt | 1 + radio/src/translations/fi.h.txt | 1 + radio/src/translations/fr.h.txt | 1 + radio/src/translations/it.h.txt | 1 + radio/src/translations/nl.h.txt | 1 + radio/src/translations/pl.h.txt | 1 + radio/src/translations/pt.h.txt | 1 + radio/src/translations/se.h.txt | 1 + 116 files changed, 1042 insertions(+), 1269 deletions(-) diff --git a/radio/src/audio_arm.cpp b/radio/src/audio_arm.cpp index ca7254927..8fc28a398 100644 --- a/radio/src/audio_arm.cpp +++ b/radio/src/audio_arm.cpp @@ -512,7 +512,7 @@ void AudioQueue::start() #define CODEC_ID_PCM_ALAW 6 #define CODEC_ID_PCM_MULAW 7 -#ifndef SIMU +#if !defined(SIMU) void audioTask(void* pdata) { while (!audioQueue.started()) { @@ -521,8 +521,9 @@ void audioTask(void* pdata) setSampleRate(AUDIO_SAMPLE_RATE); -#if defined(SDCARD) - TRACE("unexpectedShutdown = %d", unexpectedShutdown); +#if !defined(EEPROM) + AUDIO_TADA(); +#elif defined(SDCARD) if (!unexpectedShutdown) { sdInit(); AUDIO_TADA(); diff --git a/radio/src/gui/9X/gui.h b/radio/src/gui/9X/gui.h index b4c3752a3..8f225bf2a 100644 --- a/radio/src/gui/9X/gui.h +++ b/radio/src/gui/9X/gui.h @@ -46,7 +46,7 @@ void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr); #if !defined(CPUM64) #define DEFAULT_SCROLLBAR_X (LCD_W-1) - void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); + void drawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); #endif #define SET_SCROLLBAR_X(x) diff --git a/radio/src/gui/9X/lcd.cpp b/radio/src/gui/9X/lcd.cpp index 1a318fe15..ac6901f43 100644 --- a/radio/src/gui/9X/lcd.cpp +++ b/radio/src/gui/9X/lcd.cpp @@ -208,7 +208,7 @@ void lcd_putc(coord_t x, coord_t y, const unsigned char c) lcdDrawChar(x, y, c, 0); } -void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) +void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) { const coord_t orig_x = x; #if defined(CPUARM) @@ -283,17 +283,17 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags void lcd_putsn(coord_t x, coord_t y, const pm_char * s, uint8_t len) { - lcd_putsnAtt(x, y, s, len, 0); + lcdDrawTextWithLen(x, y, s, len, 0); } -void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags flags) +void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags flags) { - lcd_putsnAtt(x, y, s, 255, flags); + lcdDrawTextWithLen(x, y, s, 255, flags); } void lcd_puts(coord_t x, coord_t y, const pm_char * s) { - lcd_putsAtt(x, y, s, 0); + lcdDrawText(x, y, s, 0); } void lcd_putsLeft(coord_t y, const pm_char * s) @@ -302,11 +302,11 @@ void lcd_putsLeft(coord_t y, const pm_char * s) } #if !defined(BOOT) -void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags) +void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags) { uint8_t length; length = pgm_read_byte(s++); - lcd_putsnAtt(x, y, s+length*idx, length, flags & ~(BSS|ZCHAR)); + lcdDrawTextWithLen(x, y, s+length*idx, length, flags & ~(BSS|ZCHAR)); } #if defined(CPUARM) @@ -656,14 +656,14 @@ void putsVBat(coord_t x, coord_t y, LcdFlags att) void putsStrIdx(coord_t x, coord_t y, const pm_char *str, uint8_t idx, LcdFlags att) { - lcd_putsAtt(x, y, str, att & ~LEADING0); + lcdDrawText(x, y, str, att & ~LEADING0); lcd_outdezNAtt(lcdNextPos, y, idx, att|LEFT, 2); } void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) { if (idx < MIXSRC_THR) - lcd_putsiAtt(x, y, STR_VSRCRAW, idx, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx, att); else if (idx < MIXSRC_SW1) putsSwitches(x, y, idx-MIXSRC_THR+1+3*(1), att); else if (idx <= MIXSRC_LAST_LOGICAL_SWITCH) @@ -678,24 +678,24 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) putsStrIdx(x, y, STR_GV, idx-MIXSRC_GVAR1+1, att); #endif else if (idx < MIXSRC_FIRST_TELEM) { - lcd_putsiAtt(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1-(MIXSRC_SW1-MIXSRC_THR)-NUM_LOGICAL_SWITCH-NUM_TRAINER-NUM_CHNOUT-MAX_GVARS, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1-(MIXSRC_SW1-MIXSRC_THR)-NUM_LOGICAL_SWITCH-NUM_TRAINER-NUM_CHNOUT-MAX_GVARS, att); } #if defined(CPUARM) else { idx -= MIXSRC_FIRST_TELEM; div_t qr = div(idx, 3); - lcd_putsnAtt(x, y, g_model.telemetrySensors[qr.quot].label, ZLEN(g_model.telemetrySensors[qr.quot].label), ZCHAR|att); + lcdDrawTextWithLen(x, y, g_model.telemetrySensors[qr.quot].label, ZLEN(g_model.telemetrySensors[qr.quot].label), ZCHAR|att); if (qr.rem) lcdDrawChar(lcdLastPos, y, qr.rem==2 ? '+' : '-', att); } #else else - lcd_putsiAtt(x, y, STR_VTELEMCHNS, idx-MIXSRC_FIRST_TELEM+1, att); + lcdDrawTextAtIndex(x, y, STR_VTELEMCHNS, idx-MIXSRC_FIRST_TELEM+1, att); #endif } void putsChnLetter(coord_t x, coord_t y, uint8_t idx, LcdFlags att) { - lcd_putsiAtt(x, y, STR_RETA123, idx-1, att); + lcdDrawTextAtIndex(x, y, STR_RETA123, idx-1, att); } void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) @@ -706,14 +706,14 @@ void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) putsStrIdx(x, y, STR_MODEL, id+1, att|LEADING0); } else { - lcd_putsnAtt(x, y, name, sizeof(g_model.header.name), ZCHAR|att); + lcdDrawTextWithLen(x, y, name, sizeof(g_model.header.name), ZCHAR|att); } } void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx == SWSRC_OFF) - return lcd_putsiAtt(x, y, STR_OFFON, 0, att); + return lcdDrawTextAtIndex(x, y, STR_OFFON, 0, att); if (idx < 0) { lcdDrawChar(x-2, y, '!', att); idx = -idx; @@ -723,13 +723,13 @@ void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) return putsStrIdx(x, y, STR_FP, idx-SWSRC_FIRST_FLIGHT_MODE, att); } #endif - return lcd_putsiAtt(x, y, STR_VSWITCHES, idx, att); + return lcdDrawTextAtIndex(x, y, STR_VSWITCHES, idx, att); } #if defined(FLIGHT_MODES) void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att) { - if (idx==0) { lcd_putsiAtt(x, y, STR_MMMINV, 0, att); return; } + if (idx==0) { lcdDrawTextAtIndex(x, y, STR_MMMINV, 0, att); return; } if (idx < 0) { lcdDrawChar(x-2, y, '!', att); idx = -idx; } if (att & CONDENSED) lcd_outdezNAtt(x+FW*1, y, idx-1, (att & ~CONDENSED), 1); @@ -745,7 +745,7 @@ void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att) idx = -idx+CURVE_BASE-1; } if (idx < CURVE_BASE) - lcd_putsiAtt(x, y, STR_VCURVEFUNC, idx, att); + lcdDrawTextAtIndex(x, y, STR_VCURVEFUNC, idx, att); else putsStrIdx(x, y, STR_CV, idx-CURVE_BASE+1, att); } @@ -754,7 +754,7 @@ void putsTimerMode(coord_t x, coord_t y, int8_t mode, LcdFlags att) { if (mode >= 0) { if (mode < TMRMODE_COUNT) - return lcd_putsiAtt(x, y, STR_VTMRMODES, mode, att); + return lcdDrawTextAtIndex(x, y, STR_VTMRMODES, mode, att); else mode -= (TMRMODE_COUNT-1); } @@ -808,7 +808,7 @@ void putsValueWithUnit(coord_t x, coord_t y, lcdint_t val, uint8_t unit, LcdFlag // convertUnit(val, unit); lcd_outdezAtt(x, y, val, att & (~NO_UNIT)); if (!(att & NO_UNIT) && unit != UNIT_RAW) { - lcd_putsiAtt(lcdLastPos/*+1*/, y, STR_VTELEMUNIT, unit, 0); + lcdDrawTextAtIndex(lcdLastPos/*+1*/, y, STR_VTELEMUNIT, unit, 0); } } @@ -931,7 +931,7 @@ void putsValueWithUnit(coord_t x, coord_t y, lcdint_t val, uint8_t unit, LcdFlag convertUnit(val, unit); lcd_outdezAtt(x, y, val, att & (~NO_UNIT)); if (!(att & NO_UNIT) && unit != UNIT_RAW) { - lcd_putsiAtt(lcdLastPos/*+1*/, y, STR_VTELEMUNIT, unit, 0); + lcdDrawTextAtIndex(lcdLastPos/*+1*/, y, STR_VTELEMUNIT, unit, 0); } } diff --git a/radio/src/gui/9X/lcd.h b/radio/src/gui/9X/lcd.h index 8a3973bc1..d0bd79e5f 100644 --- a/radio/src/gui/9X/lcd.h +++ b/radio/src/gui/9X/lcd.h @@ -164,9 +164,9 @@ typedef const char pm_char; void lcd_putc(coord_t x, coord_t y, const unsigned char c); void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags flags); -void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags flags); -void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags); -void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s,unsigned char len, LcdFlags flags); +void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags flags); +void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags); +void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s,unsigned char len, LcdFlags flags); void lcd_puts(coord_t x, coord_t y, const pm_char * s); void lcd_putsn(coord_t x, coord_t y, const pm_char * s, unsigned char len); void lcd_putsLeft(coord_t y, const pm_char * s); diff --git a/radio/src/gui/9X/menu_general_calib.cpp b/radio/src/gui/9X/menu_general_calib.cpp index 366c60d4b..8e775eb63 100644 --- a/radio/src/gui/9X/menu_general_calib.cpp +++ b/radio/src/gui/9X/menu_general_calib.cpp @@ -75,7 +75,7 @@ void menuCommonCalib(uint8_t event) case 1: // SET MIDPOINT - lcd_putsAtt(0*FW, MENU_HEADER_HEIGHT+FH, STR_SETMIDPOINT, INVERS); + lcdDrawText(0*FW, MENU_HEADER_HEIGHT+FH, STR_SETMIDPOINT, INVERS); lcd_putsLeft(MENU_HEADER_HEIGHT+2*FH, STR_MENUWHENDONE); for (uint8_t i=0; iname, attr); + lcdDrawText(RADIO_SETUP_2ND_COLUMN, y, currentLanguagePack->name, attr); if (attr) { currentLanguagePackIdx = checkIncDec(event, currentLanguagePackIdx, 0, DIM(languagePacks)-2, EE_GENERAL); if (checkIncDec_Ret) { @@ -538,7 +538,7 @@ void menuGeneralSetup(uint8_t event) case ITEM_SETUP_SWITCHES_DELAY: lcd_putsLeft(y, STR_SWITCHES_DELAY); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, 10*SWITCHES_DELAY(), attr|LEFT); - lcd_putsAtt(lcdLastPos, y, STR_MS, attr); + lcdDrawText(lcdLastPos, y, STR_MS, attr); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.switchesDelay, -15, +15); break; #endif diff --git a/radio/src/gui/9X/menu_general_trainer.cpp b/radio/src/gui/9X/menu_general_trainer.cpp index f660c06ea..f7a685793 100644 --- a/radio/src/gui/9X/menu_general_trainer.cpp +++ b/radio/src/gui/9X/menu_general_trainer.cpp @@ -68,7 +68,7 @@ void menuGeneralTrainer(uint8_t event) switch(j) { case 0: - lcd_putsiAtt(4*FW, y, STR_TRNMODE, td->mode, attr); + lcdDrawTextAtIndex(4*FW, y, STR_TRNMODE, td->mode, attr); if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->mode, 0, 2); break; @@ -78,7 +78,7 @@ void menuGeneralTrainer(uint8_t event) break; case 2: - lcd_putsiAtt(12*FW, y, STR_TRNCHN, td->srcChn, attr); + lcdDrawTextAtIndex(12*FW, y, STR_TRNCHN, td->srcChn, attr); if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->srcChn, 0, 3); break; } @@ -93,7 +93,7 @@ void menuGeneralTrainer(uint8_t event) attr = (m_posVert==6) ? INVERS : 0; if (attr) s_editMode = 0; - lcd_putsAtt(0*FW, MENU_HEADER_HEIGHT+1+6*FH, STR_CAL, attr); + lcdDrawText(0*FW, MENU_HEADER_HEIGHT+1+6*FH, STR_CAL, attr); for (uint8_t i=0; i<4; i++) { uint8_t x = (i*TRAINER_CALIB_POS+16)*FW/2; #if defined (PPM_UNIT_PERCENT_PREC1) diff --git a/radio/src/gui/9X/menu_model.cpp b/radio/src/gui/9X/menu_model.cpp index 3d06688f5..446334295 100644 --- a/radio/src/gui/9X/menu_model.cpp +++ b/radio/src/gui/9X/menu_model.cpp @@ -142,7 +142,7 @@ void editName(coord_t x, coord_t y, char *name, uint8_t size, uint8_t event, uin mode = FIXEDWIDTH; } - lcd_putsnAtt(x, y, name, size, ZCHAR | mode); + lcdDrawTextWithLen(x, y, name, size, ZCHAR | mode); if (active) { uint8_t cur = editNameCursorPos; diff --git a/radio/src/gui/9X/menu_model_curves.cpp b/radio/src/gui/9X/menu_model_curves.cpp index 89be0723b..27420a502 100644 --- a/radio/src/gui/9X/menu_model_curves.cpp +++ b/radio/src/gui/9X/menu_model_curves.cpp @@ -189,7 +189,7 @@ void menuModelCurveOne(uint8_t event) lcd_putsLeft(7*FH, STR_TYPE); uint8_t attr = (s_editMode <= 0 ? INVERS : 0); lcd_outdezAtt(5*FW-2, 7*FH, crv.points, LEFT|attr); - lcd_putsAtt(lcdLastPos, 7*FH, crv.custom ? PSTR("pt'") : PSTR("pt"), attr); + lcdDrawText(lcdLastPos, 7*FH, crv.custom ? PSTR("pt'") : PSTR("pt"), attr); DrawCurve(); diff --git a/radio/src/gui/9X/menu_model_custom_functions.cpp b/radio/src/gui/9X/menu_model_custom_functions.cpp index e529a4384..fdebfa24e 100644 --- a/radio/src/gui/9X/menu_model_custom_functions.cpp +++ b/radio/src/gui/9X/menu_model_custom_functions.cpp @@ -117,7 +117,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu case 1: if (CFN_SWITCH(cfn)) { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); if (active) { #if defined(CPUARM) CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); @@ -167,7 +167,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu #if defined(CPUARM) else if (func == FUNC_SET_TIMER) { maxParam = MAX_TIMERS-1; - lcd_putsiAtt(lcdNextPos, y, STR_VFSWRESET, CFN_TIMER_INDEX(cfn), attr); + lcdDrawTextAtIndex(lcdNextPos, y, STR_VFSWRESET, CFN_TIMER_INDEX(cfn), attr); if (active) CFN_TIMER_INDEX(cfn) = checkIncDec(event, CFN_TIMER_INDEX(cfn), 0, maxParam, eeFlags); break; } @@ -192,7 +192,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu #endif if (func == FUNC_RESET) { val_max = FUNC_RESET_PARAM_LAST; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, CFN_PARAM(cfn), attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, CFN_PARAM(cfn), attr); } #if defined(OVERRIDE_CHANNEL_FUNCTION) else if (func == FUNC_OVERRIDE_CHANNEL) { @@ -206,7 +206,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu #if defined(CPUARM) else if (func >= FUNC_SET_FAILSAFE && func <= FUNC_BIND) { val_max = NUM_MODULES-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); } #endif #if defined(CPUARM) @@ -218,7 +218,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu #if defined(AUDIO) else if (func == FUNC_PLAY_SOUND) { val_max = AU_FRSKY_LAST-AU_FRSKY_FIRST-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); } #endif #if defined(HAPTIC) @@ -231,9 +231,9 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT) { coord_t x = (func == FUNC_PLAY_TRACK ? MODEL_CUSTOM_FUNC_2ND_COLUMN + FW + FW*strlen(TR_PLAY_TRACK) : MODEL_CUSTOM_FUNC_3RD_COLUMN); if (ZEXIST(cfn->play.name)) - lcd_putsnAtt(x, y, cfn->play.name, sizeof(cfn->play.name), attr); + lcdDrawTextWithLen(x, y, cfn->play.name, sizeof(cfn->play.name), attr); else - lcd_putsiAtt(x, y, STR_VCSWFUNC, 0, attr); + lcdDrawTextAtIndex(x, y, STR_VCSWFUNC, 0, attr); if (active && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; char directory[256]; @@ -304,7 +304,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu lcd_putc(lcdLastPos, y, 's'); } else { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); } } #endif @@ -328,7 +328,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu break; default: // FUNC_ADJUST_GVAR_INC val_max = 1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); break; } @@ -369,7 +369,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu } #if defined(CPUARM) else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, "!-", attr); + lcdDrawText(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, "!-", attr); } #endif else { diff --git a/radio/src/gui/9X/menu_model_flightmodes.cpp b/radio/src/gui/9X/menu_model_flightmodes.cpp index 231e82d79..158ad4c12 100644 --- a/radio/src/gui/9X/menu_model_flightmodes.cpp +++ b/radio/src/gui/9X/menu_model_flightmodes.cpp @@ -177,7 +177,7 @@ void menuModelPhaseOne(uint8_t event) putsFlightMode(11*FW, y, p+1, posHorz==1 ? attr : 0); } else { - lcd_putsAtt(11*FW, y, STR_OWN, posHorz==1 ? attr : 0); + lcdDrawText(11*FW, y, STR_OWN, posHorz==1 ? attr : 0); } if (attr && s_currIdx>0 && posHorz==1 && (editMode>0 || p1valdiff)) { if (v < GVAR_MAX) v = GVAR_MAX; @@ -253,7 +253,7 @@ void menuModelFlightModesAll(uint8_t event) FlightModeData *p = flightModeAddress(i); putsFlightMode(0, y, i+1, att|(getFlightMode()==i ? BOLD : 0)); - lcd_putsnAtt(4*FW+NAME_OFS, y, p->name, sizeof(p->name), ZCHAR); + lcdDrawTextWithLen(4*FW+NAME_OFS, y, p->name, sizeof(p->name), ZCHAR); if (i == 0) { lcd_puts((5+LEN_FLIGHT_MODE_NAME)*FW+SWITCH_OFS, y, STR_DEFAULT); } diff --git a/radio/src/gui/9X/menu_model_inputs_mixes.cpp b/radio/src/gui/9X/menu_model_inputs_mixes.cpp index eb58e1645..33fd0d860 100644 --- a/radio/src/gui/9X/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/9X/menu_model_inputs_mixes.cpp @@ -327,7 +327,7 @@ void menuModelExpoOne(uint8_t event) ed->curveParam = GVAR_MENU_ITEM(EXPO_ONE_2ND_COLUMN, y, ed->curveParam, -100, 100, attr, 0, event); } else { - lcd_putsAtt(EXPO_ONE_2ND_COLUMN-3*FW, y, STR_NA, attr); + lcdDrawText(EXPO_ONE_2ND_COLUMN-3*FW, y, STR_NA, attr); } break; @@ -346,7 +346,7 @@ void menuModelExpoOne(uint8_t event) } } else { - lcd_putsAtt(EXPO_ONE_2ND_COLUMN-3*FW, y, STR_NA, attr); + lcdDrawText(EXPO_ONE_2ND_COLUMN-3*FW, y, STR_NA, attr); } break; #endif @@ -541,7 +541,7 @@ void menuModelMixOne(uint8_t event) uint8_t not_stick = (md2->srcRaw > NUM_STICKS); int8_t carryTrim = -md2->carryTrim; lcd_putsColumnLeft(COLUMN_X, y, STR_TRIM); - lcd_putsiAtt((not_stick ? COLUMN_X+MIXES_2ND_COLUMN : COLUMN_X+6*FW-3), y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); + lcdDrawTextAtIndex((not_stick ? COLUMN_X+MIXES_2ND_COLUMN : COLUMN_X+6*FW-3), y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); if (attr && m_posHorz==0 && (not_stick || editMode>0)) md2->carryTrim = -checkIncDecModel(event, carryTrim, not_stick ? TRIM_ON : -TRIM_OFF, -TRIM_AIL); if (!not_stick) { lcd_puts(COLUMN_X+MIXES_2ND_COLUMN, y, STR_DREX); @@ -574,7 +574,7 @@ void menuModelMixOne(uint8_t event) } } else { - lcd_putsAtt(COLUMN_X+MIXES_2ND_COLUMN, y, PSTR("Diff"), m_posHorz==0 ? attr : 0); + lcdDrawText(COLUMN_X+MIXES_2ND_COLUMN, y, PSTR("Diff"), m_posHorz==0 ? attr : 0); md2->curveParam = GVAR_MENU_ITEM(COLUMN_X+MIXES_2ND_COLUMN+5*FW, y, curveParam, -100, 100, LEFT|(m_posHorz==1 ? attr : 0), 0, editMode>0 ? event : 0); if (attr && editMode>0 && m_posHorz==0) { int8_t tmp = 0; @@ -601,7 +601,7 @@ void menuModelMixOne(uint8_t event) if (md2->mixWarn) lcd_outdezAtt(COLUMN_X+MIXES_2ND_COLUMN, y, md2->mixWarn, attr|LEFT); else - lcd_putsAtt(COLUMN_X+MIXES_2ND_COLUMN, y, STR_OFF, attr); + lcdDrawText(COLUMN_X+MIXES_2ND_COLUMN, y, STR_OFF, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, md2->mixWarn, 3); break; case MIX_FIELD_MLTPX: @@ -708,7 +708,7 @@ void displayMixInfos(coord_t y, MixData *md) void displayMixLine(coord_t y, MixData *md) { if (md->name[0]) { - lcd_putsnAtt(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); + lcdDrawTextWithLen(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); } else { displayMixInfos(y, md); @@ -734,7 +734,7 @@ void displayExpoLine(coord_t y, ExpoData *ed) displayExpoInfos(y, ed); if (ed->name[0]) { - lcd_putsnAtt(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); + lcdDrawTextWithLen(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); } } #else @@ -867,29 +867,29 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) case EVT_ROTARY_LEFT: case EVT_ROTARY_RIGHT: #endif - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_copyMode) { uint8_t key = (event & 0x1f); - uint8_t next_ofs = ((IS_ROTARY_UP(event) || key==KEY_MOVE_UP) ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); + uint8_t next_ofs = ((IS_ROTARY_LEFT(event) || key==KEY_UP) ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); if (s_copyTgtOfs==0 && s_copyMode==COPY_MODE) { // insert a mix on the same channel (just above / just below) if (reachExpoMixCountLimit(expo)) break; copyExpoMix(expo, s_currIdx); - if (IS_ROTARY_DOWN(event) || key==KEY_MOVE_DOWN) s_currIdx++; + if (IS_ROTARY_RIGHT(event) || key==KEY_DOWN) s_currIdx++; else if (sub-s_pgOfs >= 6) s_pgOfs++; } else if (next_ofs==0 && s_copyMode==COPY_MODE) { // delete the mix deleteExpoMix(expo, s_currIdx); - if (IS_ROTARY_UP(event) || key==KEY_MOVE_UP) s_currIdx--; + if (IS_ROTARY_LEFT(event) || key==KEY_UP) s_currIdx--; } else { // only swap the mix with its neighbor - if (!swapExpoMix(expo, s_currIdx, IS_ROTARY_UP(event) || key==KEY_MOVE_UP)) break; + if (!swapExpoMix(expo, s_currIdx, IS_ROTARY_LEFT(event) || key==KEY_UP)) break; storageDirty(EE_MODEL); } @@ -945,7 +945,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) } } else { - if (mixCnt > 0) lcd_putsiAtt(FW, y, STR_VMLTPX2, md->mltpx, 0); + if (mixCnt > 0) lcdDrawTextAtIndex(FW, y, STR_VMLTPX2, md->mltpx, 0); putsMixerSource(MIX_LINE_SRC_POS, y, md->srcRaw, 0); diff --git a/radio/src/gui/9X/menu_model_limits.cpp b/radio/src/gui/9X/menu_model_limits.cpp index ef92d0089..83c256ee6 100644 --- a/radio/src/gui/9X/menu_model_limits.cpp +++ b/radio/src/gui/9X/menu_model_limits.cpp @@ -135,7 +135,7 @@ void menuModelLimits(uint8_t event) if (k==NUM_CHNOUT) { // last line available - add the "copy trim menu" line uint8_t attr = (sub==NUM_CHNOUT) ? INVERS : 0; - lcd_putsAtt(CENTER_OFS, y, STR_TRIMS2OFFSETS, s_noHi ? 0 : attr); + lcdDrawText(CENTER_OFS, y, STR_TRIMS2OFFSETS, s_noHi ? 0 : attr); if (attr) { s_editMode = 0; if (event==EVT_KEY_LONG(KEY_ENTER)) { @@ -211,7 +211,7 @@ void menuModelLimits(uint8_t event) #if defined(PPM_CENTER_ADJUSTABLE) lcdDrawChar(LIMITS_REVERT_POS, y, revert ? 127 : 126, attr); #else - lcd_putsiAtt(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); + lcdDrawTextAtIndex(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); #endif if (active) { uint8_t revert_new = checkIncDecModel(event, revert, 0, 1); diff --git a/radio/src/gui/9X/menu_model_logical_switches.cpp b/radio/src/gui/9X/menu_model_logical_switches.cpp index c3287e8e6..bcb04b521 100644 --- a/radio/src/gui/9X/menu_model_logical_switches.cpp +++ b/radio/src/gui/9X/menu_model_logical_switches.cpp @@ -77,9 +77,9 @@ void putsEdgeDelayParam(coord_t x, coord_t y, LogicalSwitchData *cs, uint8_t lat lcd_outdezAtt(x, y, lswTimerValue(cs->v2), LEFT|PREC1|lattr); lcd_putc(lcdLastPos, y, ':'); if (cs->v3 < 0) - lcd_putsAtt(lcdLastPos+3, y, "<<", rattr); + lcdDrawText(lcdLastPos+3, y, "<<", rattr); else if (cs->v3 == 0) - lcd_putsAtt(lcdLastPos+3, y, "--", rattr); + lcdDrawText(lcdLastPos+3, y, "--", rattr); else lcd_outdezAtt(lcdLastPos+3, y, lswTimerValue(cs->v2+cs->v3), LEFT|PREC1|rattr); lcd_putc(lcdLastPos, y, ']'); @@ -114,7 +114,7 @@ void menuModelLogicalSwitchOne(uint8_t event) switch(i) { case LS_FIELD_FUNCTION: lcd_putsLeft(y, STR_FUNC); - lcd_putsiAtt(CSWONE_2ND_COLUMN, y, STR_VCSWFUNC, cs->func, attr); + lcdDrawTextAtIndex(CSWONE_2ND_COLUMN, y, STR_VCSWFUNC, cs->func, attr); if (attr) { cs->func = checkIncDec(event, cs->func, 0, LS_FUNC_MAX, EE_MODEL, isLogicalSwitchFunctionAvailable); uint8_t new_cstate = lswFamily(cs->func); @@ -222,7 +222,7 @@ void menuModelLogicalSwitchOne(uint8_t event) if (cs->duration > 0) lcd_outdezAtt(CSWONE_2ND_COLUMN, y, cs->duration, attr|PREC1|LEFT); else - lcd_putsiAtt(CSWONE_2ND_COLUMN, y, STR_MMMINV, 0, attr); + lcdDrawTextAtIndex(CSWONE_2ND_COLUMN, y, STR_MMMINV, 0, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, cs->duration, MAX_LS_DURATION); break; case LS_FIELD_DELAY: @@ -230,7 +230,7 @@ void menuModelLogicalSwitchOne(uint8_t event) if (cs->delay > 0) lcd_outdezAtt(CSWONE_2ND_COLUMN, y, cs->delay, attr|PREC1|LEFT); else - lcd_putsiAtt(CSWONE_2ND_COLUMN, y, STR_MMMINV, 0, attr); + lcdDrawTextAtIndex(CSWONE_2ND_COLUMN, y, STR_MMMINV, 0, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, cs->delay, MAX_LS_DELAY); break; } @@ -270,7 +270,7 @@ void menuModelLogicalSwitches(uint8_t event) if (cs->func > 0) { // CSW func - lcd_putsiAtt(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, 0); + lcdDrawTextAtIndex(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, 0); // CSW params uint8_t cstate = lswFamily(cs->func); @@ -333,7 +333,7 @@ void menuModelLogicalSwitches(uint8_t event) putsSwitches(0, y, sw, (getSwitch(sw) ? BOLD : 0) | ((sub==k && CURSOR_ON_LINE()) ? INVERS : 0)); // CSW func - lcd_putsiAtt(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); + lcdDrawTextAtIndex(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); // CSW params uint8_t cstate = lswFamily(cs->func); @@ -476,7 +476,7 @@ void menuModelLogicalSwitches(uint8_t event) if (cs->duration > 0) lcd_outdezAtt(CSW_5TH_COLUMN, y, cs->duration, (horz==LS_FIELD_DURATION ? attr : 0)|PREC1|LEFT); else - lcd_putsiAtt(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); + lcdDrawTextAtIndex(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); // CSW delay if (cstate == LS_FAMILY_EDGE) { @@ -489,7 +489,7 @@ void menuModelLogicalSwitches(uint8_t event) lcd_outdezAtt(CSW_6TH_COLUMN, y, cs->delay, (horz==LS_FIELD_DELAY ? attr : 0)|PREC1|LEFT); } else { - lcd_putsiAtt(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); + lcdDrawTextAtIndex(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); } if (attr && horz == LS_FIELD_V3 && cstate != LS_FAMILY_EDGE) { diff --git a/radio/src/gui/9X/menu_model_select.cpp b/radio/src/gui/9X/menu_model_select.cpp index 9557681ec..f763de8b2 100644 --- a/radio/src/gui/9X/menu_model_select.cpp +++ b/radio/src/gui/9X/menu_model_select.cpp @@ -290,10 +290,10 @@ void menuModelSelect(uint8_t event) // no break #endif - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_copyMode) { int8_t next_ofs = s_copyTgtOfs + oldSub - m_posVert; if (next_ofs == MAX_MODELS || next_ofs == -MAX_MODELS) @@ -302,7 +302,7 @@ void menuModelSelect(uint8_t event) if (s_copySrcRow < 0 && s_copyMode==COPY_MODE) { s_copySrcRow = oldSub; // find a hole (in the first empty slot above / below) - sub = eeFindEmptyModel(s_copySrcRow, IS_ROTARY_DOWN(event) || event==EVT_KEY_FIRST(KEY_MOVE_DOWN)); + sub = eeFindEmptyModel(s_copySrcRow, IS_ROTARY_RIGHT(event) || event==EVT_KEY_FIRST(KEY_DOWN)); if (sub < 0) { // no free room for duplicating the model AUDIO_ERROR(); diff --git a/radio/src/gui/9X/menu_model_setup.cpp b/radio/src/gui/9X/menu_model_setup.cpp index f9dd02d45..19843992e 100644 --- a/radio/src/gui/9X/menu_model_setup.cpp +++ b/radio/src/gui/9X/menu_model_setup.cpp @@ -327,7 +327,7 @@ void menuModelSetup(uint8_t event) ON_OFF_MENU_ITEM(g_model.extendedTrims, MODEL_SETUP_2ND_COLUMN, y, STR_ETRIMS, attr, event); #else ON_OFF_MENU_ITEM(g_model.extendedTrims, MODEL_SETUP_2ND_COLUMN, y, STR_ETRIMS, m_posHorz<=0 ? attr : 0, event==EVT_KEY_BREAK(KEY_ENTER) ? event : 0); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+3*FW, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+3*FW, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); if (attr && m_posHorz>0) { s_editMode = 0; if (event==EVT_KEY_LONG(KEY_ENTER)) { @@ -453,7 +453,7 @@ void menuModelSetup(uint8_t event) } lcdDrawChar(MODEL_SETUP_2ND_COLUMN+i*FW, y, (swactive || (attr & BLINK)) ? c : '-', attr); #if !defined(CPUM64) - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+(NUM_SWITCHES*FW), y, PSTR("<]"), (m_posHorz == NUM_SWITCHES-1 && !s_noHi) ? line : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+(NUM_SWITCHES*FW), y, PSTR("<]"), (m_posHorz == NUM_SWITCHES-1 && !s_noHi) ? line : 0); #endif } break; @@ -464,7 +464,7 @@ void menuModelSetup(uint8_t event) for (uint8_t i=0; i0 || p1valdiff)) { switch (m_posHorz) { case 0: @@ -540,7 +540,7 @@ void menuModelSetup(uint8_t event) ModuleData & moduleData = g_model.moduleData[moduleIdx]; lcd_putsLeft(y, STR_CHANNELRANGE); if ((int8_t)PORT_CHANNELS_ROWS(moduleIdx) >= 0) { - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN, y, STR_CH, m_posHorz==0 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN, y, STR_CH, m_posHorz==0 ? attr : 0); lcd_outdezAtt(lcdLastPos, y, moduleData.channelsStart+1, LEFT | (m_posHorz==0 ? attr : 0)); lcd_putc(lcdLastPos, y, '-'); lcd_outdezAtt(lcdLastPos + FW+1, y, moduleData.channelsStart+NUM_CHANNELS(moduleIdx), LEFT | (m_posHorz==1 ? attr : 0)); @@ -616,8 +616,8 @@ void menuModelSetup(uint8_t event) checkModelIdUnique(g_eeGeneral.currModel, moduleIdx); } } - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); uint8_t newFlag = 0; if (attr && l_posHorz>0 && s_editMode>0) { if (l_posHorz == 1) @@ -650,8 +650,8 @@ void menuModelSetup(uint8_t event) ModuleData & moduleData = g_model.moduleData[moduleIdx]; lcd_putsLeft(y, TR_FAILSAFE); if (IS_MODULE_XJT(moduleIdx)) { - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); - if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcd_putsAtt(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); + if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcdDrawText(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); if (attr) { if (moduleData.failsafeMode != FAILSAFE_CUSTOM) m_posHorz = 0; @@ -680,9 +680,9 @@ void menuModelSetup(uint8_t event) #if !defined(CPUARM) case ITEM_MODEL_PPM1_PROTOCOL: lcd_putsLeft(y, NO_INDENT(STR_PROTO)); - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VPROTOS, protocol, m_posHorz<=0 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VPROTOS, protocol, m_posHorz<=0 ? attr : 0); if (IS_PPM_PROTOCOL(protocol)) { - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN+7*FW, y, STR_NCHANNELS, g_model.ppmNCH+2, m_posHorz!=0 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN+7*FW, y, STR_NCHANNELS, g_model.ppmNCH+2, m_posHorz!=0 ? attr : 0); } else if (m_posHorz>0 && attr) { MOVE_CURSOR_FROM_HERE(); @@ -704,8 +704,8 @@ void menuModelSetup(uint8_t event) #if 0 case ITEM_MODEL_PPM2_PROTOCOL: lcd_putsLeft(y, PSTR("Port2")); - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VPROTOS, 0, 0); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+4*FW+3, y, STR_CH, m_posHorz<=0 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VPROTOS, 0, 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+4*FW+3, y, STR_CH, m_posHorz<=0 ? attr : 0); lcd_outdezAtt(lcdLastPos, y, g_model.moduleData[1].channelsStart+1, LEFT | (m_posHorz<=0 ? attr : 0)); lcd_putc(lcdLastPos, y, '-'); lcd_outdezAtt(lcdLastPos + FW+1, y, g_model.moduleData[1].channelsStart+8+g_model.moduleData[1].channelsCount, LEFT | (m_posHorz!=0 ? attr : 0)); @@ -781,7 +781,7 @@ void menuModelSetup(uint8_t event) } #if defined(PXX) if (protocol == PROTO_PXX) { - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+4*FW, y, STR_SYNCMENU, m_posHorz!=0 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+4*FW, y, STR_SYNCMENU, m_posHorz!=0 ? attr : 0); uint8_t newFlag = 0; if (attr && m_posHorz>0 && editMode>0) { // send reset code @@ -792,7 +792,7 @@ void menuModelSetup(uint8_t event) #endif #if defined(DSM2) if (IS_DSM2_PROTOCOL(protocol)) { - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+4*FW, y, STR_MODULE_RANGE, m_posHorz!=0 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+4*FW, y, STR_MODULE_RANGE, m_posHorz!=0 ? attr : 0); moduleFlag[0] = (attr && m_posHorz>0 && editMode>0) ? MODULE_RANGECHECK : 0; // [MENU] key toggles range check mode } #endif diff --git a/radio/src/gui/9X/menu_model_telemetry.cpp b/radio/src/gui/9X/menu_model_telemetry.cpp index 70fb23f12..98007d3c5 100644 --- a/radio/src/gui/9X/menu_model_telemetry.cpp +++ b/radio/src/gui/9X/menu_model_telemetry.cpp @@ -355,7 +355,7 @@ void menuModelSensor(uint8_t event) case SENSOR_FIELD_UNIT: lcd_putsLeft(y, "Unit"); // TODO flash saving with selectMenuItem where I copied those 2 lines? - lcd_putsiAtt(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); + lcdDrawTextAtIndex(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); if (attr) { CHECK_INCDEC_MODELVAR_ZERO(event, sensor->unit, UNIT_MAX); if (checkIncDec_Ret) { @@ -570,7 +570,7 @@ void menuModelTelemetry(uint8_t event) int index = k-ITEM_TELEMETRY_SENSOR1; lcd_outdezAtt(INDENT_WIDTH, y, index+1, LEFT|attr); lcdDrawChar(lcdLastPos, y, ':', attr); - lcd_putsnAtt(3*FW, y, g_model.telemetrySensors[index].label, TELEM_LABEL_LEN, ZCHAR); + lcdDrawTextWithLen(3*FW, y, g_model.telemetrySensors[index].label, TELEM_LABEL_LEN, ZCHAR); if (telemetryItems[index].isFresh()) { lcd_putc(16*FW, y, '*'); } @@ -583,7 +583,7 @@ void menuModelTelemetry(uint8_t event) if (isOld) lcd_putc(lcdLastPos, y, ']'); } else { - lcd_putsAtt(TELEM_COL2, y, "---", 0); // TODO shortcut + lcdDrawText(TELEM_COL2, y, "---", 0); // TODO shortcut } if (attr) { s_editMode = 0; @@ -616,7 +616,7 @@ void menuModelTelemetry(uint8_t event) break; case ITEM_TELEMETRY_DISCOVER_SENSORS: - lcd_putsAtt(0, y, allowNewSensors ? STR_STOP_DISCOVER_SENSORS : STR_DISCOVER_SENSORS, attr); + lcdDrawText(0, y, allowNewSensors ? STR_STOP_DISCOVER_SENSORS : STR_DISCOVER_SENSORS, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; allowNewSensors = !allowNewSensors; @@ -624,7 +624,7 @@ void menuModelTelemetry(uint8_t event) break; case ITEM_TELEMETRY_NEW_SENSOR: - lcd_putsAtt(0, y, STR_TELEMETRY_NEWSENSOR, attr); + lcdDrawText(0, y, STR_TELEMETRY_NEWSENSOR, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; int res = availableTelemetryIndex(); @@ -639,7 +639,7 @@ void menuModelTelemetry(uint8_t event) break; case ITEM_TELEMETRY_DELETE_ALL_SENSORS: - lcd_putsAtt(0, y, STR_DELETE_ALL_SENSORS, attr); + lcdDrawText(0, y, STR_DELETE_ALL_SENSORS, attr); s_editMode = 0; if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { killEvents(KEY_ENTER); @@ -664,7 +664,7 @@ void menuModelTelemetry(uint8_t event) case ITEM_TELEMETRY_A2_RANGE: lcd_putsLeft(y, STR_RANGE); putsTelemetryChannelValue(TELEM_COL2, y, dest, 255-channel.offset, (m_posHorz<=0 ? attr : 0)|NO_UNIT|LEFT); - lcd_putsiAtt(lcdLastPos, y, STR_VTELEMUNIT, channel.type, m_posHorz!=0 ? attr : 0); + lcdDrawTextAtIndex(lcdLastPos, y, STR_VTELEMUNIT, channel.type, m_posHorz!=0 ? attr : 0); if (attr && (s_editMode>0 || p1valdiff)) { if (m_posHorz == 0) { uint16_t ratio = checkIncDec(event, channel.ratio, 0, 256, EE_MODEL); @@ -700,8 +700,8 @@ void menuModelTelemetry(uint8_t event) { uint8_t alarm = ((k==ITEM_TELEMETRY_A1_ALARM1 || k==ITEM_TELEMETRY_A2_ALARM1) ? 0 : 1); lcd_putsLeft(y, STR_ALARM); - lcd_putsiAtt(TELEM_COL2, y, STR_VALARM, ALARM_LEVEL(ch, alarm), m_posHorz<=0 ? attr : 0); - lcd_putsiAtt(TELEM_COL2+4*FW, y, STR_VALARMFN, ALARM_GREATER(ch, alarm), (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0); + lcdDrawTextAtIndex(TELEM_COL2, y, STR_VALARM, ALARM_LEVEL(ch, alarm), m_posHorz<=0 ? attr : 0); + lcdDrawTextAtIndex(TELEM_COL2+4*FW, y, STR_VALARMFN, ALARM_GREATER(ch, alarm), (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0); putsTelemetryChannelValue(TELEM_COL2+6*FW, y, dest, channel.alarms_value[alarm], ((CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0) | LEFT); if (attr && (s_editMode>0 || p1valdiff)) { @@ -735,7 +735,7 @@ void menuModelTelemetry(uint8_t event) case ITEM_TELEMETRY_RSSI_ALARM2: { uint8_t alarm = k-ITEM_TELEMETRY_RSSI_ALARM1; lcd_putsLeft(y, STR_ALARM); - lcd_putsiAtt(TELEM_COL2, y, STR_VALARM, ((2+alarm+g_model.frsky.rssiAlarms[alarm].level)%4), m_posHorz<=0 ? attr : 0); + lcdDrawTextAtIndex(TELEM_COL2, y, STR_VALARM, ((2+alarm+g_model.frsky.rssiAlarms[alarm].level)%4), m_posHorz<=0 ? attr : 0); lcd_putc(TELEM_COL2+4*FW, y, '<'); lcd_outdezNAtt(TELEM_COL2+6*FW, y, getRssiAlarmValue(alarm), LEFT|(m_posHorz!=0 ? attr : 0), 3); @@ -760,7 +760,7 @@ void menuModelTelemetry(uint8_t event) case ITEM_TELEMETRY_USR_PROTO: lcd_putsLeft(y, STR_PROTO); - lcd_putsiAtt(TELEM_COL2, y, STR_VTELPROTO, g_model.frsky.usrProto, attr); + lcdDrawTextAtIndex(TELEM_COL2, y, STR_VTELPROTO, g_model.frsky.usrProto, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.frsky.usrProto, USR_PROTO_LAST); break; @@ -773,13 +773,13 @@ void menuModelTelemetry(uint8_t event) case ITEM_TELEMETRY_USR_VOLTAGE_SOURCE: lcd_putsLeft(y, STR_VOLTAGE); - lcd_putsiAtt(TELEM_COL2, y, STR_AMPSRC, g_model.frsky.voltsSource+1, attr); + lcdDrawTextAtIndex(TELEM_COL2, y, STR_AMPSRC, g_model.frsky.voltsSource+1, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.frsky.voltsSource, FRSKY_VOLTS_SOURCE_LAST); break; case ITEM_TELEMETRY_USR_CURRENT_SOURCE: lcd_putsLeft(y, STR_CURRENT); - lcd_putsiAtt(TELEM_COL2, y, STR_AMPSRC, g_model.frsky.currentSource, attr); + lcdDrawTextAtIndex(TELEM_COL2, y, STR_AMPSRC, g_model.frsky.currentSource, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.frsky.currentSource, FRSKY_CURRENT_SOURCE_LAST); break; @@ -807,7 +807,7 @@ void menuModelTelemetry(uint8_t event) g_model.frsky.varioSource = checkIncDec(event, g_model.frsky.varioSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); } #else - lcd_putsiAtt(TELEM_COL2, y, STR_VARIOSRC, g_model.frsky.varioSource, attr); + lcdDrawTextAtIndex(TELEM_COL2, y, STR_VARIOSRC, g_model.frsky.varioSource, attr); if (attr) CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioSource, 0, VARIO_SOURCE_LAST); #endif break; @@ -938,7 +938,7 @@ void menuModelTelemetry(uint8_t event) } } #else - lcd_putsiAtt(TELEM_COL1, y, STR_VTELEMCHNS, barSource, m_posHorz==0 ? attr : 0); + lcdDrawTextAtIndex(TELEM_COL1, y, STR_VTELEMCHNS, barSource, m_posHorz==0 ? attr : 0); if (barSource) { putsTelemetryChannelValue(TELEM_BARS_COLMIN, y, barSource-1, convertBarTelemValue(barSource, bar.barMin), (m_posHorz==1 ? attr : 0) | LEFT); putsTelemetryChannelValue(TELEM_BARS_COLMAX, y, barSource-1, convertBarTelemValue(barSource, 255-bar.barMax), (m_posHorz==2 ? attr : 0) | LEFT); @@ -994,7 +994,7 @@ void menuModelTelemetry(uint8_t event) CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, value, MIXSRC_LAST_TELEM, isSourceAvailable); } #else - lcd_putsiAtt(pos[c], y, STR_VTELEMCHNS, value, cellAttr); + lcdDrawTextAtIndex(pos[c], y, STR_VTELEMCHNS, value, cellAttr); if (cellAttr && (s_editMode>0 || p1valdiff)) { CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, value, (lineIndex==3 && c==0) ? TELEM_STATUS_MAX : TELEM_DISPLAY_MAX, isTelemetrySourceAvailable); } diff --git a/radio/src/gui/9X/menu_model_templates.cpp b/radio/src/gui/9X/menu_model_templates.cpp index d14118e71..c150b52d7 100644 --- a/radio/src/gui/9X/menu_model_templates.cpp +++ b/radio/src/gui/9X/menu_model_templates.cpp @@ -59,7 +59,7 @@ void menuModelTemplates(uint8_t event) for (uint8_t i=0; i= 0x0e) + #define CASE_EVT_ROTARY_BREAK case EVT_ROTARY_BREAK: + #define CASE_EVT_ROTARY_LONG case EVT_ROTARY_LONG: + #define CASE_EVT_ROTARY_LEFT case EVT_ROTARY_LEFT: + #define CASE_EVT_ROTARY_RIGHT case EVT_ROTARY_RIGHT: void repeatLastCursorMove(uint8_t event); #define REPEAT_LAST_CURSOR_MOVE() { if (EVT_KEY_MASK(event) >= 0x0e) putEvent(event); else repeatLastCursorMove(event); } #define MOVE_CURSOR_FROM_HERE() if (m_posHorz > 0) REPEAT_LAST_CURSOR_MOVE() #else + #define IS_ROTARY_LEFT(evt) (0) + #define IS_ROTARY_RIGHT(evt) (0) + #define IS_ROTARY_BREAK(evt) (0) + #define IS_ROTARY_LONG(evt) (0) + #define IS_ROTARY_EVENT(evt) (0) + #define CASE_EVT_ROTARY_BREAK + #define CASE_EVT_ROTARY_LONG + #define CASE_EVT_ROTARY_LEFT + #define CASE_EVT_ROTARY_RIGHT void repeatLastCursorMove(uint8_t event); #define REPEAT_LAST_CURSOR_MOVE() repeatLastCursorMove(event) #define MOVE_CURSOR_FROM_HERE() REPEAT_LAST_CURSOR_MOVE() diff --git a/radio/src/gui/9X/navigation.cpp b/radio/src/gui/9X/navigation.cpp index 5ef575b67..98cb316a8 100644 --- a/radio/src/gui/9X/navigation.cpp +++ b/radio/src/gui/9X/navigation.cpp @@ -551,7 +551,7 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t if (!horTab || s_editMode>0) break; #if defined(ROTARY_ENCODER_NAVIGATION) - CASE_EVT_ROTARY_MOVE_RIGHT + CASE_EVT_ROTARY_RIGHT if (s_editMode != 0) break; if (l_posHorz < maxcol) { l_posHorz++; @@ -559,7 +559,7 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t } else { l_posHorz = 0; - if (!IS_ROTARY_MOVE_RIGHT(event)) + if (!IS_ROTARY_RIGHT(event)) break; } #else @@ -592,13 +592,13 @@ void check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t if (!horTab || s_editMode>0) break; #if defined(ROTARY_ENCODER_NAVIGATION) - CASE_EVT_ROTARY_MOVE_LEFT + CASE_EVT_ROTARY_LEFT if (s_editMode != 0) break; if (l_posHorz > 0) { l_posHorz--; break; } - else if (IS_ROTARY_MOVE_LEFT(event) && s_editMode == 0) { + else if (IS_ROTARY_LEFT(event) && s_editMode == 0) { l_posHorz = 0xff; } else { diff --git a/radio/src/gui/9X/popups.cpp b/radio/src/gui/9X/popups.cpp index 2da1eaa1a..83c541aa8 100644 --- a/radio/src/gui/9X/popups.cpp +++ b/radio/src/gui/9X/popups.cpp @@ -77,11 +77,11 @@ void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SO #define MESSAGE_LCD_OFFSET 6*FW #if defined(TRANSLATIONS_FR) || defined(TRANSLATIONS_IT) || defined(TRANSLATIONS_CZ) - lcd_putsAtt(MESSAGE_LCD_OFFSET, 0, STR_WARNING, DBLSIZE); - lcd_putsAtt(MESSAGE_LCD_OFFSET, 2*FH, title, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 0, STR_WARNING, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 2*FH, title, DBLSIZE); #else - lcd_putsAtt(MESSAGE_LCD_OFFSET, 0, title, DBLSIZE); - lcd_putsAtt(MESSAGE_LCD_OFFSET, 2*FH, STR_WARNING, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 0, title, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 2*FH, STR_WARNING, DBLSIZE); #endif drawFilledRect(0, 0, LCD_W, 32); @@ -103,7 +103,7 @@ void displayWarning(uint8_t event) s_warning_result = false; displayBox(); if (s_warning_info) { - lcd_putsnAtt(WARNING_LINE_X, WARNING_LINE_Y+FH, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); + lcdDrawTextWithLen(WARNING_LINE_X, WARNING_LINE_Y+FH, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); } lcd_puts(WARNING_LINE_X, WARNING_LINE_Y+2*FH, s_warning_type == WARNING_TYPE_ASTERISK ? STR_EXIT : STR_POPUPS); switch (event) { @@ -155,20 +155,20 @@ const char * displayMenu(uint8_t event) lcdDrawRect(MENU_X, y, MENU_W, display_count * (FH+1) + 2); for (uint8_t i=0; i display_count) { - lcdDrawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); + drawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); } switch(event) { #if defined(ROTARY_ENCODER_NAVIGATION) CASE_EVT_ROTARY_LEFT #endif - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): if (s_menu_item > 0) { s_menu_item--; } @@ -192,8 +192,8 @@ const char * displayMenu(uint8_t event) #if defined(ROTARY_ENCODER_NAVIGATION) CASE_EVT_ROTARY_RIGHT #endif - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_menu_item < display_count - 1 && s_menu_offset + s_menu_item + 1 < s_menu_count) { s_menu_item++; } diff --git a/radio/src/gui/9X/view_about.cpp b/radio/src/gui/9X/view_about.cpp index 67b0254f2..bdf22b007 100644 --- a/radio/src/gui/9X/view_about.cpp +++ b/radio/src/gui/9X/view_about.cpp @@ -82,7 +82,7 @@ void menuAboutView(uint8_t event) break; } - lcd_putsAtt(17, 0, STR_ABOUTUS, DBLSIZE|INVERS); + lcdDrawText(17, 0, STR_ABOUTUS, DBLSIZE|INVERS); lcd_hline(17, 16, LCD_W-17); lcd_img(8, 0, about_bmp, 0); LcdFlags att = 0; @@ -92,67 +92,67 @@ void menuAboutView(uint8_t event) switch (screenIndex) { case ABOUT_OPENTX: case ABOUT_END: - lcd_putsAtt(ABOUT_X, 22, STR_ABOUT_OPENTX_1, SMLSIZE); - lcd_putsAtt(ABOUT_X, 30, STR_ABOUT_OPENTX_2, SMLSIZE); - lcd_putsAtt(ABOUT_X, 38, STR_ABOUT_OPENTX_3, SMLSIZE); - lcd_putsAtt(ABOUT_X, 46, STR_ABOUT_OPENTX_4, SMLSIZE); - lcd_putsAtt(ABOUT_X, 54, STR_ABOUT_OPENTX_5, SMLSIZE); + lcdDrawText(ABOUT_X, 22, STR_ABOUT_OPENTX_1, SMLSIZE); + lcdDrawText(ABOUT_X, 30, STR_ABOUT_OPENTX_2, SMLSIZE); + lcdDrawText(ABOUT_X, 38, STR_ABOUT_OPENTX_3, SMLSIZE); + lcdDrawText(ABOUT_X, 46, STR_ABOUT_OPENTX_4, SMLSIZE); + lcdDrawText(ABOUT_X, 54, STR_ABOUT_OPENTX_5, SMLSIZE); screenDuration = 255; break; case ABOUT_BERTRAND: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_BERTRAND_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_BERTRAND_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_BERTRAND_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_BERTRAND_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_BERTRAND_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_BERTRAND_3, att|SMLSIZE); break; case ABOUT_ANDRE: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_ANDRE_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ANDRE_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ANDRE_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_ANDRE_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ANDRE_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ANDRE_3, att|SMLSIZE); break; case ABOUT_MIKE: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_MIKE_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MIKE_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_MIKE_3, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_MIKE_4, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_MIKE_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MIKE_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_MIKE_3, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_MIKE_4, att|SMLSIZE); break; case ABOUT_KJELL: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_KJELL_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_KJELL_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_KJELL_3, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_KJELL_4, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_KJELL_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_KJELL_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_KJELL_3, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_KJELL_4, att|SMLSIZE); break; case ABOUT_MARTIN: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_MARTIN_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MARTIN_2, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_MARTIN_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MARTIN_2, att|SMLSIZE); break; case ABOUT_ROMOLO: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_ROMOLO_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROMOLO_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ROMOLO_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_ROMOLO_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROMOLO_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ROMOLO_3, att|SMLSIZE); break; case ABOUT_ROB: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_ROB_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROB_2, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_ROB_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROB_2, att|SMLSIZE); break; case ABOUT_HARDWARE: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_HARDWARE_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_HARDWARE_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_HARDWARE_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_HARDWARE_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_HARDWARE_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_HARDWARE_3, att|SMLSIZE); break; case ABOUT_PARENTS: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_PARENTS_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_PARENTS_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_PARENTS_3, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_PARENTS_4, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_PARENTS_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_PARENTS_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_PARENTS_3, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_PARENTS_4, att|SMLSIZE); screenDuration = 255; break; } diff --git a/radio/src/gui/9X/view_main.cpp b/radio/src/gui/9X/view_main.cpp index 5541227c7..4e0343530 100644 --- a/radio/src/gui/9X/view_main.cpp +++ b/radio/src/gui/9X/view_main.cpp @@ -191,7 +191,7 @@ void displayTimers() uint8_t len = zlen(g_model.timers[0].name, LEN_TIMER_NAME); if (len > 0) { xLabel += (LEN_TIMER_NAME-len)*FW; - lcd_putsnAtt(xLabel, FH*3, g_model.timers[0].name, len, ZCHAR); + lcdDrawTextWithLen(xLabel, FH*3, g_model.timers[0].name, len, ZCHAR); } else { putsTimerMode(xLabel, FH*3, g_model.timers[0].mode); @@ -434,7 +434,7 @@ void menuMainView(uint8_t event) { // Flight Mode Name uint8_t mode = mixerCurrentFlightMode; - lcd_putsnAtt(PHASE_X, PHASE_Y, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|PHASE_FLAGS); + lcdDrawTextWithLen(PHASE_X, PHASE_Y, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|PHASE_FLAGS); // Model Name putsModelName(MODELNAME_X, MODELNAME_Y, g_model.header.name, g_eeGeneral.currModel, BIGSIZE); @@ -575,8 +575,8 @@ void menuMainView(uint8_t event) s_gvar_timer--; s_warning = STR_GLOBAL_VAR; displayBox(); - lcd_putsnAtt(16, 5*FH, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, ZCHAR); - lcd_putsAtt(16+7*FW, 5*FH, PSTR("[\010]"), BOLD); + lcdDrawTextWithLen(16, 5*FH, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, ZCHAR); + lcdDrawText(16+7*FW, 5*FH, PSTR("[\010]"), BOLD); lcd_outdezAtt(16+7*FW+4*FW+FW/2, 5*FH, GVAR_VALUE(s_gvar_last, getGVarFlightPhase(mixerCurrentFlightMode, s_gvar_last)), BOLD); s_warning = NULL; } @@ -585,7 +585,7 @@ void menuMainView(uint8_t event) #if defined(DSM2) if (moduleFlag[0] == MODULE_BIND) { // Issue 98 - lcd_putsAtt(15*FW, 0, PSTR("BIND"), 0); + lcdDrawText(15*FW, 0, PSTR("BIND"), 0); } #endif } diff --git a/radio/src/gui/9X/view_mavlink.cpp b/radio/src/gui/9X/view_mavlink.cpp index 4bd6f7d4a..935690c03 100644 --- a/radio/src/gui/9X/view_mavlink.cpp +++ b/radio/src/gui/9X/view_mavlink.cpp @@ -182,13 +182,13 @@ void print_mav_mode(uint8_t x, uint8_t y, uint32_t custom_mode, uint8_t attr) // uint8_t mode = (uint8_t) custom_mode; switch (telemetry_data.type_autopilot) { case MAVLINK_ARDUCOPTER: - lcd_putsiAtt(x,y,STR_MAVLINK_AC_MODES,mode,attr); + lcdDrawTextAtIndex(x,y,STR_MAVLINK_AC_MODES,mode,attr); break; case MAVLINK_ARDUPLANE: - lcd_putsiAtt(x,y,STR_MAVLINK_AP_MODES,ap_modes_lut[custom_mode],attr); + lcdDrawTextAtIndex(x,y,STR_MAVLINK_AP_MODES,ap_modes_lut[custom_mode],attr); break; default: - lcd_putsAtt (FW, y, PSTR("INV. MAV TYPE"), attr); + lcdDrawText (FW, y, PSTR("INV. MAV TYPE"), attr); break; } } @@ -198,7 +198,7 @@ void print_mav_mode(uint8_t x, uint8_t y, uint32_t custom_mode, uint8_t attr) // */ void mav_title(const pm_char * s, uint8_t index) { - lcd_putsAtt(0, 0, PSTR("MAVLINK"), INVERS); + lcdDrawText(0, 0, PSTR("MAVLINK"), INVERS); lcd_puts(10 * FW, 0, s); displayScreenIndex(index, MAX_MAVLINK_MENU, INVERS); lcd_putc(7 * FW, 0, (mav_heartbeat > 0) ? '*' : ' '); @@ -236,9 +236,9 @@ void menuTelemetryMavlinkInfos(void) { */ if (telemetry_data.status) { - lcd_putsnAtt(x1, y, STR_MAVLINK_MODE, 4, 0); + lcdDrawTextWithLen(x1, y, STR_MAVLINK_MODE, 4, 0); if (telemetry_data.active) - lcd_putsnAtt(x2, y, PSTR("A"), 1, 0); + lcdDrawTextWithLen(x2, y, PSTR("A"), 1, 0); lcd_outdezAtt(xnum, y, telemetry_data.mode, 0); y += FH; @@ -298,7 +298,7 @@ void menuTelemetryMavlinkFlightMode(void) { x = 0; if (telemetry_data.active) - lcd_putsAtt (FW, y, STR_MAVLINK_ARMED, DBLSIZE); + lcdDrawText (FW, y, STR_MAVLINK_ARMED, DBLSIZE); } /*! \brief Batterystatus dislplay @@ -400,9 +400,9 @@ void menuTelemetryMavlinkGPS(void) { xnum2 = xnum + 11 * FWNUM; y = FH; - lcd_putsnAtt(x1, y, STR_MAVLINK_GPS, 3, 0); + lcdDrawTextWithLen(x1, y, STR_MAVLINK_GPS, 3, 0); if (telemetry_data.fix_type < 2) { - lcd_putsnAtt(xnum, y, STR_MAVLINK_NO_FIX, 6, 0); + lcdDrawTextWithLen(xnum, y, STR_MAVLINK_NO_FIX, 6, 0); } else { lcd_outdezNAtt(xnum, y, telemetry_data.fix_type, 0, 3); lcd_puts(xnum, y, PSTR("D")); @@ -419,19 +419,19 @@ void menuTelemetryMavlinkGPS(void) { lcd_puts(x1, y, STR_MAVLINK_LAT); lcd_outdezFloat(xnum, y, telemetry_data.loc_current.lat, 2); - lcd_putsnAtt(x2, y, STR_MAVLINK_LON, 3, 0); + lcdDrawTextWithLen(x2, y, STR_MAVLINK_LON, 3, 0); lcd_outdezFloat(xnum2, y, telemetry_data.loc_current.lon, 2); y += FH; - lcd_putsnAtt(x1, y, STR_MAVLINK_ALTITUDE, 3, 0); + lcdDrawTextWithLen(x1, y, STR_MAVLINK_ALTITUDE, 3, 0); lcd_outdezAtt(xnum, y, telemetry_data.loc_current.gps_alt, 0); y += FH; - lcd_putsnAtt(x1, y, STR_MAVLINK_COURSE, 6, 0); + lcdDrawTextWithLen(x1, y, STR_MAVLINK_COURSE, 6, 0); lcd_outdezFloat(xnum, y, telemetry_data.course, 2); y += FH; - lcd_putsnAtt(x1, y, PSTR("V"), 1, 0); + lcdDrawTextWithLen(x1, y, PSTR("V"), 1, 0); lcd_outdezAtt(xnum, y, telemetry_data.v, 0); //} } diff --git a/radio/src/gui/9X/view_statistics.cpp b/radio/src/gui/9X/view_statistics.cpp index b8de64ba0..09a1dca45 100644 --- a/radio/src/gui/9X/view_statistics.cpp +++ b/radio/src/gui/9X/view_statistics.cpp @@ -174,13 +174,13 @@ void menuStatisticsDebug(uint8_t event) lcd_putsLeft(MENU_DEBUG_Y_COPROC, STR_COPROC_TEMP); if (Coproc_read==0) { - lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_COPROC, PSTR("Co Proc NACK"),INVERS); + lcdDrawText(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_COPROC, PSTR("Co Proc NACK"),INVERS); } else if (Coproc_read==0x81) { - lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_COPROC, PSTR("Inst.TinyApp"),INVERS); + lcdDrawText(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_COPROC, PSTR("Inst.TinyApp"),INVERS); } else if (Coproc_read<3) { - lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_COPROC, PSTR("Upgr.TinyApp"),INVERS); + lcdDrawText(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_COPROC, PSTR("Upgr.TinyApp"),INVERS); } else { putsValueWithUnit(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_COPROC, Coproc_temp, UNIT_TEMPERATURE, LEFT); diff --git a/radio/src/gui/9X/view_telemetry.cpp b/radio/src/gui/9X/view_telemetry.cpp index e1599e59e..4f1c0c0c9 100644 --- a/radio/src/gui/9X/view_telemetry.cpp +++ b/radio/src/gui/9X/view_telemetry.cpp @@ -66,7 +66,7 @@ void displayRssiLine() drawFilledRect(66+36-v, 58, v, 5, (rssi < getRssiAlarmValue(0)) ? DOTTED : SOLID); } else { - lcd_putsAtt(7*FW, STATUS_BAR_Y, STR_NODATA, BLINK); + lcdDrawText(7*FW, STATUS_BAR_Y, STR_NODATA, BLINK); lcd_status_line(); } } @@ -154,7 +154,7 @@ void displayVoltagesScreen() { // Volts / Amps / Watts / mAh uint8_t analog = 0; - lcd_putsiAtt(0, 2*FH, STR_AMPSRC, g_model.frsky.voltsSource+1, 0); + lcdDrawTextAtIndex(0, 2*FH, STR_AMPSRC, g_model.frsky.voltsSource+1, 0); switch (g_model.frsky.voltsSource) { case FRSKY_VOLTS_SOURCE_A1: case FRSKY_VOLTS_SOURCE_A2: @@ -172,7 +172,7 @@ void displayVoltagesScreen() } if (g_model.frsky.currentSource) { - lcd_putsiAtt(0, 4*FH, STR_AMPSRC, g_model.frsky.currentSource, 0); + lcdDrawTextAtIndex(0, 4*FH, STR_AMPSRC, g_model.frsky.currentSource, 0); switch(g_model.frsky.currentSource) { case FRSKY_CURRENT_SOURCE_A1: case FRSKY_CURRENT_SOURCE_A2: @@ -259,7 +259,7 @@ bool displayGaugesTelemetryScreen(FrSkyScreenData & screen) #if defined(CPUARM) putsMixerSource(0, y+barHeight-5, source, 0); #else - lcd_putsiAtt(0, y+barHeight-5, STR_VTELEMCHNS, source, 0); + lcdDrawTextAtIndex(0, y+barHeight-5, STR_VTELEMCHNS, source, 0); #endif lcdDrawRect(BAR_LEFT, y, BAR_WIDTH+1, barHeight+2); #if defined(CPUARM) @@ -422,7 +422,7 @@ bool displayNumbersTelemetryScreen(FrSkyScreenData & screen) field = field-TELEM_TIMER1+TELEM_T1; } - lcd_putsiAtt(pos[j], 1+FH+2*FH*i, STR_VTELEMCHNS, field, 0); + lcdDrawTextAtIndex(pos[j], 1+FH+2*FH*i, STR_VTELEMCHNS, field, 0); } } } diff --git a/radio/src/gui/9X/view_text.cpp b/radio/src/gui/9X/view_text.cpp index 8a58dfa89..6000a9f82 100644 --- a/radio/src/gui/9X/view_text.cpp +++ b/radio/src/gui/9X/view_text.cpp @@ -138,7 +138,7 @@ void menuTextView(uint8_t event) } for (int i=0; i LCD_LINES-1) { - lcdDrawScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); + drawScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); } } diff --git a/radio/src/gui/9X/widgets.cpp b/radio/src/gui/9X/widgets.cpp index f2ada57cf..a3abec3f6 100644 --- a/radio/src/gui/9X/widgets.cpp +++ b/radio/src/gui/9X/widgets.cpp @@ -59,7 +59,7 @@ void menu_lcd_onoff(coord_t x, coord_t y, uint8_t value, LcdFlags attr) lcd_square(x, y, 7); #else /* ON / OFF version */ - lcd_putsiAtt(x, y, STR_OFFON, value, attr ? INVERS:0) ; + lcdDrawTextAtIndex(x, y, STR_OFFON, value, attr ? INVERS:0) ; #endif } @@ -72,7 +72,7 @@ void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr) } #if !defined(CPUM64) -void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) +void drawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { lcdDrawVerticalLine(x, y, h, DOTTED); coord_t yofs = (h * offset) / count; @@ -85,13 +85,13 @@ void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t void title(const pm_char * s) { - lcd_putsAtt(0, 0, s, INVERS); + lcdDrawText(0, 0, s, INVERS); } select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, uint8_t event) { lcd_putsColumnLeft(x, y, label); - if (values) lcd_putsiAtt(x, y, values, value-min, attr); + if (values) lcdDrawTextAtIndex(x, y, values, value-min, attr); if (attr) value = checkIncDec(event, value, min, max, (g_menuPos[0] == 0) ? EE_MODEL : EE_GENERAL); return value; } @@ -115,7 +115,7 @@ int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, uint8_t } #if !defined(CPUM64) -void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) +void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) { lcd_putc(x+(value*4*FW)/max, y, '$'); lcd_hline(x, y+3, 5*FW-1, FORCE); @@ -256,7 +256,7 @@ void drawStatusLine() } drawFilledRect(0, LCD_H-statusLineHeight, LCD_W, FH, SOLID, ERASE); - lcd_putsAtt(5, LCD_H+1-statusLineHeight, statusLineMsg, BSS); + lcdDrawText(5, LCD_H+1-statusLineHeight, statusLineMsg, BSS); drawFilledRect(0, LCD_H-statusLineHeight, LCD_W, FH, SOLID); } } diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index 7dd1e4036..9c4628839 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -68,11 +68,11 @@ void displaySplash(); void displayScreenIndex(uint8_t index, uint8_t count); -void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); +void drawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); void drawHeader(int index); void drawFooter(); #define drawStatusLine(...) -void displayProgressBar(const char *label); +void drawProgressBar(const char *label); void updateProgressBar(int num, int den); // Header bitmaps diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index ac0802476..f27968745 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -151,19 +151,21 @@ int getTextWidth(const pm_char * s, int len, LcdFlags flags) { const uint16_t * specs = fontspecsTable[FONTSIZE(flags) >> 8]; int result = 0; - for (int i=0; (len==0 || i= MIXSRC_FIRST_SWITCH && idx < MIXSRC_FIRST_LOGICAL_SWITCH) { idx = idx-MIXSRC_FIRST_SWITCH; if (ZEXIST(g_eeGeneral.switchNames[idx])) - lcd_putsnAtt(x, y, g_eeGeneral.switchNames[idx], LEN_SWITCH_NAME, ZCHAR|att); + lcdDrawTextWithLen(x, y, g_eeGeneral.switchNames[idx], LEN_SWITCH_NAME, ZCHAR|att); else - lcd_putsiAtt(x, y, STR_VSRCRAW, idx+MIXSRC_FIRST_SWITCH-MIXSRC_Rud+1, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx+MIXSRC_FIRST_SWITCH-MIXSRC_Rud+1, att); } else if (idx < MIXSRC_SW1) - lcd_putsiAtt(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1, att); else if (idx <= MIXSRC_LAST_LOGICAL_SWITCH) putsSwitches(x, y, SWSRC_SW1+idx-MIXSRC_SW1, att); else if (idx < MIXSRC_CH1) @@ -527,14 +529,14 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) #if 0 if (ZEXIST(g_model.limitData[idx-MIXSRC_CH1].name) && (att & STREXPANDED)) { lcdDrawChar(lcdNextPos, y, ' ', att); - lcd_putsnAtt(lcdNextPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att); + lcdDrawTextWithLen(lcdNextPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att); } #endif } else if (idx <= MIXSRC_LAST_GVAR) putsStrIdx(x, y, STR_GV, idx-MIXSRC_GVAR1+1, att); else if (idx < MIXSRC_FIRST_TELEM) { - lcd_putsiAtt(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1-NUM_LOGICAL_SWITCH-NUM_TRAINER-NUM_CHNOUT-MAX_GVARS, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1-NUM_LOGICAL_SWITCH-NUM_TRAINER-NUM_CHNOUT-MAX_GVARS, att); } else { idx -= MIXSRC_FIRST_TELEM; @@ -543,13 +545,13 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) int pos = zchar2str(s, g_model.telemetrySensors[qr.quot].label, sizeof(g_model.telemetrySensors[qr.quot].label)); if (qr.rem) s[pos++] = (qr.rem==2 ? '+' : '-'); s[pos] = '\0'; - lcd_putsAtt(x, y, s, att); + lcdDrawText(x, y, s, att); } } void putsChnLetter(coord_t x, coord_t y, uint8_t idx, LcdFlags att) { - lcd_putsiAtt(x, y, STR_RETA123, idx-1, att); + lcdDrawTextAtIndex(x, y, STR_RETA123, idx-1, att); } void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) @@ -560,17 +562,17 @@ void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) putsStrIdx(x, y, STR_MODEL, id+1, att|LEADING0); } else { - lcd_putsnAtt(x, y, name, sizeof(g_model.header.name), ZCHAR|att); + lcdDrawTextWithLen(x, y, name, sizeof(g_model.header.name), ZCHAR|att); } } void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx == SWSRC_NONE) { - return lcd_putsiAtt(x, y, STR_VSWITCHES, 0, att); + return lcdDrawTextAtIndex(x, y, STR_VSWITCHES, 0, att); } else if (idx == SWSRC_OFF) { - return lcd_putsiAtt(x, y, STR_OFFON, 0, att); + return lcdDrawTextAtIndex(x, y, STR_OFFON, 0, att); } char s[8]; @@ -591,29 +593,29 @@ void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) } s[pos++] = "\300-\301"[swinfo.rem]; s[pos] = '\0'; - lcd_putsAtt(x, y, s, att); + lcdDrawText(x, y, s, att); } /*else if (idx <= SWSRC_LAST_MULTIPOS_SWITCH) { div_t swinfo = div(idx - SWSRC_FIRST_MULTIPOS_SWITCH, XPOTS_MULTIPOS_COUNT); putsStrIdx(x, y, "S", swinfo.quot*10+swinfo.rem+11, att); }*/ else if (idx <= SWSRC_LAST_TRIM) { - lcd_putsiAtt(x, y, STR_VSWITCHES, idx-SWSRC_FIRST_TRIM+1, att); + lcdDrawTextAtIndex(x, y, STR_VSWITCHES, idx-SWSRC_FIRST_TRIM+1, att); } else if (idx <= SWSRC_LAST_LOGICAL_SWITCH) { putsStrIdx(x, y, "L", idx-SWSRC_FIRST_LOGICAL_SWITCH+1, att); } else if (idx <= SWSRC_ONE) { - lcd_putsiAtt(x, y, STR_VSWITCHES, idx-SWSRC_ON+1+(2*NUM_STICKS), att); + lcdDrawTextAtIndex(x, y, STR_VSWITCHES, idx-SWSRC_ON+1+(2*NUM_STICKS), att); } else if (idx <= SWSRC_LAST_FLIGHT_MODE) { putsStrIdx(x, y, STR_FP, idx-SWSRC_FIRST_FLIGHT_MODE, att); } else if (idx == SWSRC_TELEMETRY_STREAMING) { - lcd_putsAtt(x, y, "Tele", att); + lcdDrawText(x, y, "Tele", att); } else { - lcd_putsnAtt(x, y, g_model.telemetrySensors[idx-SWSRC_FIRST_SENSOR].label, TELEM_LABEL_LEN, ZCHAR|att); + lcdDrawTextWithLen(x, y, g_model.telemetrySensors[idx-SWSRC_FIRST_SENSOR].label, TELEM_LABEL_LEN, ZCHAR|att); } } @@ -621,7 +623,7 @@ void putsSwitches(coord_t x, coord_t y, int8_t idx, LcdFlags att) void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx==0) { - lcd_putsiAtt(x, y, STR_MMMINV, 0, att); + lcdDrawTextAtIndex(x, y, STR_MMMINV, 0, att); } else { putsStrIdx(x, y, STR_FP, abs(idx)-1, att); @@ -634,17 +636,17 @@ void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att) if (curve.value != 0) { switch (curve.type) { case CURVE_REF_DIFF: - lcd_putsAtt(x, y, "D", att); + lcdDrawText(x, y, "D", att); GVAR_MENU_ITEM(lcdNextPos, y, curve.value, -100, 100, LEFT|att, 0, 0); break; case CURVE_REF_EXPO: - lcd_putsAtt(x, y, "E", att); + lcdDrawText(x, y, "E", att); GVAR_MENU_ITEM(lcdNextPos, y, curve.value, -100, 100, LEFT|att, 0, 0); break; case CURVE_REF_FUNC: - lcd_putsiAtt(x, y, STR_VCURVEFUNC, curve.value, att); + lcdDrawTextAtIndex(x, y, STR_VCURVEFUNC, curve.value, att); break; case CURVE_REF_CUSTOM: @@ -657,7 +659,7 @@ void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att) void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx == 0) { - return lcd_putsiAtt(x, y, STR_MMMINV, 0, att); + return lcdDrawTextAtIndex(x, y, STR_MMMINV, 0, att); } bool neg = false; if (idx < 0) { @@ -676,7 +678,7 @@ void putsTimerMode(coord_t x, coord_t y, int8_t mode, LcdFlags att) { if (mode >= 0) { if (mode < TMRMODE_COUNT) - return lcd_putsiAtt(x, y, STR_VTMRMODES, mode, att); + return lcdDrawTextAtIndex(x, y, STR_VTMRMODES, mode, att); else mode -= (TMRMODE_COUNT-1); } @@ -690,13 +692,13 @@ void putsTrimMode(coord_t x, coord_t y, uint8_t phase, uint8_t idx, LcdFlags att unsigned int p = mode >> 1; if (mode == TRIM_MODE_NONE) { - lcd_putsAtt(x, y, "--", att); + lcdDrawText(x, y, "--", att); } else { char s[2]; s[0] = (mode % 2 == 0) ? ':' : '+'; s[1] = '0'+p; - lcd_putsnAtt(x, y, s, 2, att); + lcdDrawTextWithLen(x, y, s, 2, att); } } @@ -908,7 +910,7 @@ void lcdDrawBitmap(coord_t x, coord_t y, const uint16_t * img, coord_t offset, c void lcdDrawBlackOverlay() { - lcdDrawFilledRect(0, 0, LCD_W, LCD_H, SOLID, TEXT_COLOR | (8<<24)); + // lcdDrawFilledRect(0, 0, LCD_W, LCD_H, SOLID, TEXT_COLOR | (8<<24)); } void lcdDrawCircle(int x0, int y0, int radius) diff --git a/radio/src/gui/Horus/lcd.h b/radio/src/gui/Horus/lcd.h index e9e8a22f6..9f6fea916 100644 --- a/radio/src/gui/Horus/lcd.h +++ b/radio/src/gui/Horus/lcd.h @@ -139,8 +139,8 @@ enum LcdColorIndex extern uint16_t lcdColorTable[LCD_COLOR_COUNT]; void lcdColorsInit(); -#define OPACITY_MAX 0x0F -#define OPACITY(x) ((x)<<24) +#define OPACITY_MAX 0x0F +#define OPACITY(x) ((x)<<24) #define COLOR(index) ((index) << 16) #define TEXT_COLOR COLOR(TEXT_COLOR_INDEX) @@ -163,7 +163,7 @@ void lcdColorsInit(); #define TITLE_BGCOLOR COLOR(TITLE_BGCOLOR_INDEX) #define HEADER_BGCOLOR COLOR(HEADER_BGCOLOR_INDEX) -#define COLOR_IDX(att) uint8_t((att) >> 16) +#define COLOR_IDX(att) uint8_t((att) >> 16) #define COLOR_SPLIT(color, r, g, b) \ uint16_t r = ((color) & 0xF800) >> 11; \ @@ -173,29 +173,28 @@ void lcdColorsInit(); #define COLOR_JOIN(r, g, b) \ (((r) << 11) + ((g) << 5) + (b)) -#define display_t uint16_t -#define DISPLAY_PIXELS_COUNT (LCD_W*LCD_H) -#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_PIXELS_COUNT) +#define display_t uint16_t +#define DISPLAY_PIXELS_COUNT (LCD_W*LCD_H) +#define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_PIXELS_COUNT) #if defined(SIMU) extern display_t displayBuf[DISPLAY_BUFFER_SIZE]; #else -extern uint32_t CurrentFrameBuffer; -#define displayBuf ((uint16_t *)CurrentFrameBuffer) +extern uint32_t CurrentFrameBuffer; +#define displayBuf ((uint16_t *)CurrentFrameBuffer) #endif #define lcdRefreshWait() - -#define DISPLAY_END (displayBuf + DISPLAY_BUFFER_SIZE) -#define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) +#define DISPLAY_END (displayBuf + DISPLAY_BUFFER_SIZE) +#define ASSERT_IN_DISPLAY(p) assert((p) >= displayBuf && (p) < DISPLAY_END) extern coord_t lcdNextPos; void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags attr=TEXT_COLOR); -void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags attr=TEXT_COLOR); -void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s, uint8_t idx, LcdFlags attr=TEXT_COLOR); -void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, unsigned char len, LcdFlags attr=TEXT_COLOR); +void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags attr=TEXT_COLOR); +void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s, uint8_t idx, LcdFlags attr=TEXT_COLOR); +void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s, unsigned char len, LcdFlags attr=TEXT_COLOR); void lcd_puts(coord_t x, coord_t y, const pm_char * s); void lcd_putsn(coord_t x, coord_t y, const pm_char * s, unsigned char len); void lcd_putsLeft(coord_t y, const pm_char * s); @@ -300,9 +299,9 @@ void lcdSetContrast(); const pm_char * bmpLoad(uint8_t *dest, const char *filename, const unsigned int width, const unsigned int height); #if defined(BOOT) - #define BLINK_ON_PHASE (0) + #define BLINK_ON_PHASE (0) #else - #define BLINK_ON_PHASE (g_blinkTmr10ms & (1<<6)) + #define BLINK_ON_PHASE (g_blinkTmr10ms & (1<<6)) #endif #ifdef SIMU diff --git a/radio/src/gui/Horus/menu_general.cpp b/radio/src/gui/Horus/menu_general.cpp index ac98ffe5c..959c7e5c0 100644 --- a/radio/src/gui/Horus/menu_general.cpp +++ b/radio/src/gui/Horus/menu_general.cpp @@ -82,8 +82,8 @@ enum EnumTabDiag { const MenuFuncP_PROGMEM menuTabGeneral[] PROGMEM = { menuGeneralSetup, - CASE_SDCARD(menuGeneralSdManager) - CASE_CPUARM(menuGeneralCustomFunctions) + menuGeneralSdManager, + menuGeneralCustomFunctions, menuGeneralTrainer, menuGeneralCalib, menuGeneralVersion, diff --git a/radio/src/gui/Horus/menu_general_sdmanager.cpp b/radio/src/gui/Horus/menu_general_sdmanager.cpp index 550e77425..f3f8021eb 100644 --- a/radio/src/gui/Horus/menu_general_sdmanager.cpp +++ b/radio/src/gui/Horus/menu_general_sdmanager.cpp @@ -209,7 +209,13 @@ void menuGeneralSdManager(evt_t _event) break; case EVT_KEY_BREAK(KEY_ENTER): - if (s_editMode > 0) { + if (m_posVert < 0) { + if (reusableBuffer.sdmanager.count > 0) { + m_posVert = 0; + } + break; + } + else if (s_editMode > 0) { break; } else { @@ -227,6 +233,9 @@ void menuGeneralSdManager(evt_t _event) case EVT_KEY_LONG(KEY_ENTER): killEvents(_event); + if (m_posVert < 0) { + break; + } if (s_editMode == 0) { char *line = reusableBuffer.sdmanager.lines[index]; char *ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); @@ -348,10 +357,10 @@ void menuGeneralSdManager(evt_t _event) if (!reusableBuffer.sdmanager.lines[i][SD_SCREEN_FILE_LENGTH+1]) { char s[SD_SCREEN_FILE_LENGTH+1+2]; sprintf(s, "[%s]", reusableBuffer.sdmanager.lines[i]); - lcd_putsAtt(MENUS_MARGIN_LEFT, y, s, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, s, attr); } else { - lcd_putsAtt(MENUS_MARGIN_LEFT, y, reusableBuffer.sdmanager.lines[i], attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, reusableBuffer.sdmanager.lines[i], attr); } } } diff --git a/radio/src/gui/Horus/menu_general_setup.cpp b/radio/src/gui/Horus/menu_general_setup.cpp index 1e7046ff6..abb443876 100644 --- a/radio/src/gui/Horus/menu_general_setup.cpp +++ b/radio/src/gui/Horus/menu_general_setup.cpp @@ -48,7 +48,7 @@ #define SLIDER_5POS(y, value, label, event, attr) { \ int8_t tmp = value; \ - displaySlider(RADIO_SETUP_2ND_COLUMN, y, 2+tmp, 4, attr); \ + drawSlider(RADIO_SETUP_2ND_COLUMN, y, 2+tmp, 4, attr); \ value = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, label, NULL, tmp, -2, +2, attr, event); \ } @@ -70,11 +70,11 @@ enum menuGeneralSetupItems { ITEM_SETUP_SPEAKER_PITCH, ITEM_SETUP_WAV_VOLUME, ITEM_SETUP_BACKGROUND_VOLUME, - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_LABEL) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_VOLUME) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_PITCH) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_RANGE) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_REPEAT) + CASE_VARIO(ITEM_SETUP_VARIO_LABEL) + CASE_VARIO(ITEM_SETUP_VARIO_VOLUME) + CASE_VARIO(ITEM_SETUP_VARIO_PITCH) + CASE_VARIO(ITEM_SETUP_VARIO_RANGE) + CASE_VARIO(ITEM_SETUP_VARIO_REPEAT) CASE_HAPTIC(ITEM_SETUP_HAPTIC_LABEL) CASE_HAPTIC(ITEM_SETUP_HAPTIC_MODE) CASE_HAPTIC(ITEM_SETUP_HAPTIC_LENGTH) @@ -112,7 +112,7 @@ void menuGeneralSetup(evt_t event) if ((m_posVert==ITEM_SETUP_DATE || m_posVert==ITEM_SETUP_TIME) && (s_editMode>0) && - (event==EVT_KEY_FIRST(KEY_ENTER) || event==EVT_KEY_FIRST(KEY_EXIT) || IS_ROTARY_BREAK(event) || IS_ROTARY_LONG(event))) { + (event==EVT_KEY_FIRST(KEY_ENTER) || event==EVT_KEY_BREAK(KEY_ENTER) || event==EVT_KEY_LONG(KEY_ENTER) || event==EVT_KEY_FIRST(KEY_EXIT))) { // set the date and time into RTC chip rtcSetTime(&t); } @@ -126,7 +126,7 @@ void menuGeneralSetup(evt_t event) } #endif - MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 2|NAVIGATION_LINE_BY_LINE, 2|NAVIGATION_LINE_BY_LINE, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO_CPUARM(LABEL(VARIO)) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) LABEL(ALARMS), 0, 0, 0, CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, CASE_MAVLINK(0) 0, 0, 0, 0, 1/*to force edit mode*/ }); + MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, DEFAULT_SCROLLBAR_X, { 2|NAVIGATION_LINE_BY_LINE, 2|NAVIGATION_LINE_BY_LINE, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO(LABEL(VARIO)) CASE_VARIO(0) CASE_VARIO(0) CASE_VARIO(0) CASE_VARIO(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) LABEL(ALARMS), 0, 0, 0, CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, CASE_MAVLINK(0) 0, 0, 0, 0, 1/*to force edit mode*/ }); int sub = m_posVert; @@ -145,8 +145,8 @@ void menuGeneralSetup(evt_t event) flags |= INVERS; lcdDrawSolidFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+YEAR_SEPARATOR_OFFSET, y, "-", flags); - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+MONTH_SEPARATOR_OFFSET, y, "-", flags); + lcdDrawText(RADIO_SETUP_2ND_COLUMN+YEAR_SEPARATOR_OFFSET, y, "-", flags); + lcdDrawText(RADIO_SETUP_2ND_COLUMN+MONTH_SEPARATOR_OFFSET, y, "-", flags); for (uint8_t j=0; j<3; j++) { uint8_t rowattr = (m_posHorz==j ? attr : 0); switch (j) { @@ -184,22 +184,22 @@ void menuGeneralSetup(evt_t event) flags |= INVERS; lcdDrawSolidFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 85, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+HOUR_SEPARATOR_OFFSET, y, ":", flags); - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN+MINUTE_SEPARATOR_OFFSET, y, ":", flags); + lcdDrawText(RADIO_SETUP_2ND_COLUMN+HOUR_SEPARATOR_OFFSET, y, ":", flags); + lcdDrawText(RADIO_SETUP_2ND_COLUMN+MINUTE_SEPARATOR_OFFSET, y, ":", flags); for (uint8_t j=0; j<3; j++) { uint8_t rowattr = (m_posHorz==j ? attr : 0); switch (j) { case 0: - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN, y, t.tm_hour, flags|rowattr|LEFT|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_hour = checkIncDec(event, t.tm_hour, 0, 23, 0); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN, y, t.tm_hour, flags|rowattr|LEFT|LEADING0, 2); break; case 1: - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+MINUTE_OFFSET, y, t.tm_min, flags|rowattr|LEFT|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_min = checkIncDec(event, t.tm_min, 0, 59, 0); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+MINUTE_OFFSET, y, t.tm_min, flags|rowattr|LEFT|LEADING0, 2); break; case 2: - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+SECOND_OFFSET, y, t.tm_sec, flags|rowattr|LEFT|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_sec = checkIncDec(event, t.tm_sec, 0, 59, 0); + lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+SECOND_OFFSET, y, t.tm_sec, flags|rowattr|LEFT|LEADING0, 2); break; } } @@ -245,7 +245,7 @@ void menuGeneralSetup(evt_t event) { lcd_putsLeft(y, STR_SPEAKER_VOLUME); uint8_t b = g_eeGeneral.speakerVolume+VOLUME_LEVEL_DEF; - displaySlider(RADIO_SETUP_2ND_COLUMN, y, b, VOLUME_LEVEL_MAX, attr); + drawSlider(RADIO_SETUP_2ND_COLUMN, y, b, VOLUME_LEVEL_MAX, attr); if (attr) { CHECK_INCDEC_GENVAR(event, b, 0, VOLUME_LEVEL_MAX); if (checkIncDec_Ret) { @@ -409,7 +409,7 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_BACKLIGHT_COLOR: lcd_putsLeft(y, STR_BLCOLOR); - displaySlider(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.backlightColor, 20, attr); + drawSlider(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.backlightColor, 20, attr); if (attr) g_eeGeneral.backlightColor = checkIncDec(event, g_eeGeneral.backlightColor, 0, 20, EE_GENERAL | NO_INCDEC_MARKS); break; #endif @@ -422,7 +422,7 @@ void menuGeneralSetup(evt_t event) lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, SPLASH_TIMEOUT/100, attr|LEFT, "s"); } else { - lcd_putsiAtt(RADIO_SETUP_2ND_COLUMN, y, STR_MMMINV, 0, attr); // TODO define + lcdDrawTextAtIndex(RADIO_SETUP_2ND_COLUMN, y, STR_MMMINV, 0, attr); // TODO define } if (attr) g_eeGeneral.splashMode = -checkIncDecGen(event, -g_eeGeneral.splashMode, -3, 4); break; @@ -445,7 +445,7 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_LANGUAGE: lcd_putsLeft(y, STR_VOICELANG); - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, currentLanguagePack->name, attr); + lcdDrawText(RADIO_SETUP_2ND_COLUMN, y, currentLanguagePack->name, attr); if (attr) { currentLanguagePackIdx = checkIncDec(event, currentLanguagePackIdx, 0, DIM(languagePacks)-2, EE_GENERAL); if (checkIncDec_Ret) { @@ -491,7 +491,7 @@ void menuGeneralSetup(evt_t event) s[i] = STR_RETA123[channel_order(i+1)]; } s[4] = '\0'; - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, s, attr); + lcdDrawText(RADIO_SETUP_2ND_COLUMN, y, s, attr); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.templateSetup, 0, 23); break; } @@ -501,7 +501,7 @@ void menuGeneralSetup(evt_t event) lcd_putsLeft(y, NO_INDENT(STR_MODE)); char s[2] = " "; s[0] = '1'+g_eeGeneral.stickMode; - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, s, attr); + lcdDrawText(RADIO_SETUP_2ND_COLUMN, y, s, attr); for (uint8_t i=0; i<4; i++) { putsMixerSource(RADIO_SETUP_2ND_COLUMN + 40 + 50*i, y, MIXSRC_Rud + pgm_read_byte(modn12x3 + 4*g_eeGeneral.stickMode + i)); } diff --git a/radio/src/gui/Horus/menu_general_trainer.cpp b/radio/src/gui/Horus/menu_general_trainer.cpp index a399e9dc8..888ba9b30 100644 --- a/radio/src/gui/Horus/menu_general_trainer.cpp +++ b/radio/src/gui/Horus/menu_general_trainer.cpp @@ -58,9 +58,9 @@ void menuGeneralTrainer(evt_t event) uint8_t attr; uint8_t blink = ((s_editMode>0) ? BLINK|INVERS : INVERS); - /* lcd_putsAtt(TRAINER_COLUMN_1, MENU_HEADER_HEIGHT+1, "Mode", HEADER_COLOR); - lcd_putsAtt(TRAINER_COLUMN_2, MENU_HEADER_HEIGHT+1, "Weight", HEADER_COLOR); - lcd_putsAtt(TRAINER_COLUMN_3, MENU_HEADER_HEIGHT+1, "Source", HEADER_COLOR); + /* lcdDrawText(TRAINER_COLUMN_1, MENU_HEADER_HEIGHT+1, "Mode", HEADER_COLOR); + lcdDrawText(TRAINER_COLUMN_2, MENU_HEADER_HEIGHT+1, "Weight", HEADER_COLOR); + lcdDrawText(TRAINER_COLUMN_3, MENU_HEADER_HEIGHT+1, "Source", HEADER_COLOR); */ y = MENU_CONTENT_TOP + FH; @@ -78,7 +78,7 @@ void menuGeneralTrainer(evt_t event) switch(j) { case 0: - lcd_putsiAtt(TRAINER_COLUMN_1, y, STR_TRNMODE, td->mode, attr); + lcdDrawTextAtIndex(TRAINER_COLUMN_1, y, STR_TRNMODE, td->mode, attr); if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->mode, 0, 2); break; @@ -88,7 +88,7 @@ void menuGeneralTrainer(evt_t event) break; case 2: - lcd_putsiAtt(TRAINER_COLUMN_3, y, STR_TRNCHN, td->srcChn, attr); + lcdDrawTextAtIndex(TRAINER_COLUMN_3, y, STR_TRNCHN, td->srcChn, attr); if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->srcChn, 0, 3); break; } @@ -103,7 +103,7 @@ void menuGeneralTrainer(evt_t event) attr = (sub==6) ? INVERS : 0; if (attr) s_editMode = 0; - lcd_putsAtt(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 6*FH, STR_CAL, attr); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 6*FH, STR_CAL, attr); for (int i=0; i<4; i++) { #if defined (PPM_UNIT_PERCENT_PREC1) lcd_outdezAtt(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])*2, LEFT|PREC1); diff --git a/radio/src/gui/Horus/menu_model.cpp b/radio/src/gui/Horus/menu_model.cpp index 2cf16ada2..971ec92cf 100644 --- a/radio/src/gui/Horus/menu_model.cpp +++ b/radio/src/gui/Horus/menu_model.cpp @@ -79,7 +79,7 @@ void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t a uint8_t editDelay(const coord_t x, const coord_t y, const evt_t event, const uint8_t attr, const pm_char *str, uint8_t delay) { - lcd_putsAtt(x+MENUS_MARGIN_LEFT, y, str, TEXT_COLOR); + lcdDrawText(x+MENUS_MARGIN_LEFT, y, str, TEXT_COLOR); lcd_outdezAtt(x+MIXES_2ND_COLUMN, y, (10/DELAY_STEP)*delay, attr|PREC1|LEFT); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, delay, DELAY_MAX); return delay; @@ -118,18 +118,19 @@ static uint8_t editNameCursorPos = 0; void editName(coord_t x, coord_t y, char * name, uint8_t size, evt_t event, uint8_t active) { - uint8_t mode = 0; + uint8_t flags = 0; if (active && s_editMode <= 0) { - mode = INVERS; + flags = INVERS; } if (!active || s_editMode <= 0) { - if (zlen(name, size) == 0) { + uint8_t len = zlen(name, size); + if (len == 0) { char tmp[] = "---"; - lcd_putsnAtt(x, y, tmp, size, mode); + lcdDrawTextWithLen(x, y, tmp, size, flags); } else { - lcd_putsnAtt(x, y, name, size, ZCHAR | mode); + lcdDrawTextWithLen(x, y, name, len, ZCHAR | flags); } } @@ -139,13 +140,20 @@ void editName(coord_t x, coord_t y, char * name, uint8_t size, evt_t event, uint int8_t c = name[cur]; int8_t v = c; - if (IS_ROTARY_RIGHT(event) || IS_ROTARY_LEFT(event) || event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_FIRST(KEY_UP) - || event==EVT_KEY_REPT(KEY_DOWN) || event==EVT_KEY_REPT(KEY_UP)) { - v = checkIncDec(event, abs(v), 0, ZCHAR_MAX, 0); - if (c <= 0) v = -v; + if (event==EVT_ROTARY_RIGHT || event==EVT_ROTARY_LEFT) { + v = checkIncDec(event, abs(v), 0, ZCHAR_MAX, 0); + if (c <= 0) v = -v; } switch (event) { + case EVT_KEY_BREAK(KEY_LEFT): + if (cur>0) cur--; + break; + + case EVT_KEY_BREAK(KEY_RIGHT): + if (cur=-26 && v<=26) { v = -v; // toggle case if (event==EVT_KEY_LONG(KEY_LEFT)) @@ -176,11 +188,11 @@ void editName(coord_t x, coord_t y, char * name, uint8_t size, evt_t event, uint storageDirty(g_menuPos[0] == 0 ? EE_MODEL : EE_GENERAL); } - lcd_putsnAtt(x, y, name, size, ZCHAR | mode); + lcdDrawTextWithLen(x, y, name, size, ZCHAR | flags); coord_t w = (editNameCursorPos == 0 ? 0 : getTextWidth(name, editNameCursorPos, ZCHAR)); char s[] = { idx2char(v), '\0' }; lcdDrawSolidFilledRect(x+w-1, y-INVERT_VERT_MARGIN, getTextWidth(s, 1)+1, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); - lcd_putsAtt(x+w, y, s, TEXT_INVERTED_COLOR); + lcdDrawText(x+w, y, s, TEXT_INVERTED_COLOR); } else { cur = 0; diff --git a/radio/src/gui/Horus/menu_model_curves.cpp b/radio/src/gui/Horus/menu_model_curves.cpp index 90ec83179..b89ac5085 100644 --- a/radio/src/gui/Horus/menu_model_curves.cpp +++ b/radio/src/gui/Horus/menu_model_curves.cpp @@ -150,7 +150,7 @@ void menuModelCurveOne(evt_t event) LcdFlags attr = (m_posVert==1 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); lcd_putsLeft(MENU_CONTENT_TOP + 2*FH, "Type"); - lcd_putsiAtt(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 3*FH, STR_CURVE_TYPES, crv.type, attr); + lcdDrawTextAtIndex(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 3*FH, STR_CURVE_TYPES, crv.type, attr); if (attr) { uint8_t newType = checkIncDecModelZero(event, crv.type, CURVE_TYPE_LAST); if (newType != crv.type) { @@ -187,7 +187,7 @@ void menuModelCurveOne(evt_t event) } lcd_putsLeft(MENU_CONTENT_TOP + 6*FH, STR_SMOOTH); - lcdDrawCheckBox(lcdNextPos + 10, MENU_CONTENT_TOP + 6*FH, crv.smooth, m_posVert==3 ? INVERS : 0); + drawCheckBox(lcdNextPos + 10, MENU_CONTENT_TOP + 6*FH, crv.smooth, m_posVert==3 ? INVERS : 0); if (m_posVert==3) crv.smooth = checkIncDecModel(event, crv.smooth, 0, 1); switch(event) { @@ -212,8 +212,8 @@ void menuModelCurveOne(evt_t event) DrawCurve(6); } - // lcd_putsAtt(115, MENU_FOOTER_TOP, "X", HEADER_COLOR); - // lcd_putsAtt(145, MENU_FOOTER_TOP, "Y", HEADER_COLOR); + // lcdDrawText(115, MENU_FOOTER_TOP, "X", HEADER_COLOR); + // lcdDrawText(145, MENU_FOOTER_TOP, "Y", HEADER_COLOR); coord_t posY = MENU_CONTENT_TOP; attr = (s_editMode > 0 ? INVERS|BLINK : INVERS); @@ -256,13 +256,13 @@ void menuModelCurveOne(evt_t event) } if (5+crv.points > NUM_BODY_LINES) { - lcdDrawScrollbar(250, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); + drawScrollbar(250, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); } } void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t attr) { - lcd_putsiAtt(x, y, "\004DiffExpoFuncCstm", curve.type, (m_posHorz==0 ? attr : 0)); + lcdDrawTextAtIndex(x, y, "\004DiffExpoFuncCstm", curve.type, (m_posHorz==0 ? attr : 0)); if (attr && m_posHorz==0) { CHECK_INCDEC_MODELVAR_ZERO(event, curve.type, CURVE_REF_CUSTOM); if (checkIncDec_Ret) curve.value = 0; @@ -273,7 +273,7 @@ void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t a curve.value = GVAR_MENU_ITEM(lcdNextPos+10, y, curve.value, -100, 100, m_posHorz==1 ? LEFT|attr : LEFT, 0, event); break; case CURVE_REF_FUNC: - lcd_putsiAtt(lcdNextPos+10, y, STR_VCURVEFUNC, curve.value, (m_posHorz==1 ? attr : 0)); + lcdDrawTextAtIndex(lcdNextPos+10, y, STR_VCURVEFUNC, curve.value, (m_posHorz==1 ? attr : 0)); if (attr && m_posHorz==1) CHECK_INCDEC_MODELVAR_ZERO(event, curve.value, CURVE_BASE-1); break; case CURVE_REF_CUSTOM: diff --git a/radio/src/gui/Horus/menu_model_custom_functions.cpp b/radio/src/gui/Horus/menu_model_custom_functions.cpp index 4f7af3ce8..6f94ec96a 100644 --- a/radio/src/gui/Horus/menu_model_custom_functions.cpp +++ b/radio/src/gui/Horus/menu_model_custom_functions.cpp @@ -150,17 +150,17 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc LcdFlags active = (attr && s_editMode>0); switch (j) { case 0: - putsSwitches(MODEL_CUSTOM_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | ((functionsContext.activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); if (active || AUTOSWITCH_ENTER_LONG()) CHECK_INCDEC_SWITCH(event, CFN_SWITCH(cfn), SWSRC_FIRST, SWSRC_LAST, eeFlags, isSwitchAvailableInCustomFunctions); + putsSwitches(MODEL_CUSTOM_FUNC_1ST_COLUMN, y, CFN_SWITCH(cfn), attr | ((functionsContext.activeSwitches & ((MASK_CFN_TYPE)1 << k)) ? BOLD : 0)); break; case 1: if (CFN_SWITCH(cfn)) { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); if (active) { func = CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); if (checkIncDec_Ret) CFN_RESET(cfn); } + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); } else { j = 4; // skip other fields @@ -212,7 +212,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc int16_t val_max = 255; if (func == FUNC_RESET) { val_max = FUNC_RESET_PARAM_LAST; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, CFN_PARAM(cfn), attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, CFN_PARAM(cfn), attr); } #if defined(OVERRIDE_CHANNEL_FUNCTION) else if (func == FUNC_OVERRIDE_CHANNEL) { @@ -223,7 +223,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc #if defined(DANGEROUS_MODULE_FUNCTIONS) else if (func >= FUNC_RANGECHECK && func <= FUNC_MODULE_OFF) { val_max = NUM_MODULES-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); } #endif else if (func == FUNC_SET_TIMER) { @@ -232,7 +232,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc } else if (func == FUNC_PLAY_SOUND) { val_max = AU_FRSKY_LAST-AU_FRSKY_FIRST-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); } #if defined(HAPTIC) else if (func == FUNC_HAPTIC) { @@ -244,9 +244,9 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT) { coord_t x = MODEL_CUSTOM_FUNC_3RD_COLUMN; if (ZEXIST(cfn->play.name)) - lcd_putsnAtt(x, y, cfn->play.name, sizeof(cfn->play.name), attr); + lcdDrawTextWithLen(x, y, cfn->play.name, sizeof(cfn->play.name), attr); else - lcd_putsiAtt(x, y, STR_VCSWFUNC, 0, attr); + lcdDrawTextAtIndex(x, y, STR_VCSWFUNC, 0, attr); if (active && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; char directory[256]; @@ -284,12 +284,12 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|PREC1|LEFT, "s"); } else { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); } } #if defined(REVPLUS) else if (func == FUNC_BACKLIGHT) { - displaySlider(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, CFN_PARAM(cfn), 100, attr); + drawSlider(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, CFN_PARAM(cfn), 100, attr); INCDEC_SET_FLAG(eeFlags | NO_INCDEC_MARKS); val_min = 0; val_max = 100; @@ -315,7 +315,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc break; default: // FUNC_ADJUST_GVAR_INC val_max = 1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); break; } @@ -341,20 +341,20 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc case 4: if (HAS_ENABLE_PARAM(func)) { - lcdDrawCheckBox(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); if (active) CFN_ACTIVE(cfn) = checkIncDec(event, CFN_ACTIVE(cfn), 0, 1, eeFlags); + drawCheckBox(MODEL_CUSTOM_FUNC_4TH_COLUMN_ONOFF, y, CFN_ACTIVE(cfn), attr); } else if (HAS_REPEAT_PARAM(func)) { + if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); if (CFN_PLAY_REPEAT(cfn) == 0) { - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); + lcdDrawText(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, "!1x", attr); + lcdDrawText(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, "!1x", attr); } else { lcd_outdezAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+12, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr, "s"); } - if (active) CFN_PLAY_REPEAT(cfn) = checkIncDec(event, CFN_PLAY_REPEAT(cfn)==CFN_PLAY_REPEAT_NOSTART?-1:CFN_PLAY_REPEAT(cfn), -1, 60/CFN_PLAY_REPEAT_MUL, eeFlags); } else if (attr) { REPEAT_LAST_CURSOR_MOVE(); diff --git a/radio/src/gui/Horus/menu_model_flightmodes.cpp b/radio/src/gui/Horus/menu_model_flightmodes.cpp index 313c1799d..938c696df 100644 --- a/radio/src/gui/Horus/menu_model_flightmodes.cpp +++ b/radio/src/gui/Horus/menu_model_flightmodes.cpp @@ -62,12 +62,15 @@ bool isTrimModeAvailable(int mode) void menuModelFlightModesAll(evt_t event) { - MENU(STR_MENUFLIGHTPHASES, menuTabModel, e_FlightModesAll, MAX_FLIGHT_MODES+1, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|(ITEM_FLIGHT_MODES_LAST-1), NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, 0}); + MENU(STR_MENUFLIGHTPHASES, menuTabModel, e_FlightModesAll, MAX_FLIGHT_MODES+1, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, 0}); int sub = m_posVert; horzpos_t posHorz = m_posHorz; - if (sub==0 && posHorz > 0) { posHorz += 1; } + if (sub==0 && posHorz == 1) { + REPEAT_LAST_CURSOR_MOVE(); + posHorz = m_posHorz; + } if (sub=0) { displayColumnHeader(STR_PHASES_HEADERS, posHorz); @@ -119,11 +122,11 @@ void menuModelFlightModesAll(evt_t event) break; case ITEM_FLIGHT_MODES_SWITCH: + if (active) CHECK_INCDEC_MODELSWITCH(event, p->swtch, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); if (k == 0) lcd_puts(FLIGHT_MODES_SWITCH_COLUMN, y, "N/A"); else putsSwitches(FLIGHT_MODES_SWITCH_COLUMN, y, p->swtch, attr); - if (active) CHECK_INCDEC_MODELSWITCH(event, p->swtch, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); break; case ITEM_FLIGHT_MODES_TRIM_RUD: @@ -132,22 +135,22 @@ void menuModelFlightModesAll(evt_t event) case ITEM_FLIGHT_MODES_TRIM_AIL: { uint8_t t = j-ITEM_FLIGHT_MODES_TRIM_RUD; - putsTrimMode(FLIGHT_MODES_TRIMS_COLUMN+j*FLIGHT_MODES_TRIM_WIDTH, y, k, t, attr); if (active) { trim_t & v = p->trim[t]; v.mode = checkIncDec(event, v.mode==TRIM_MODE_NONE ? -1 : v.mode, -1, k==0 ? 0 : 2*MAX_FLIGHT_MODES-1, EE_MODEL, isTrimModeAvailable); } + putsTrimMode(FLIGHT_MODES_TRIMS_COLUMN+j*FLIGHT_MODES_TRIM_WIDTH, y, k, t, attr); break; } case ITEM_FLIGHT_MODES_FADE_IN: - lcd_outdezAtt(FLIGHT_MODES_FADEIN_COLUMN, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); if (active) p->fadeIn = checkIncDec(event, p->fadeIn, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); + lcd_outdezAtt(FLIGHT_MODES_FADEIN_COLUMN, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); break; case ITEM_FLIGHT_MODES_FADE_OUT: - lcd_outdezAtt(FLIGHT_MODES_FADEOUT_COLUMN, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); if (active) p->fadeOut = checkIncDec(event, p->fadeOut, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); + lcd_outdezAtt(FLIGHT_MODES_FADEOUT_COLUMN, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); break; } diff --git a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp index 6b9dd58f4..4695f83fe 100644 --- a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp @@ -46,7 +46,7 @@ void displayFlightModes(coord_t x, coord_t y, FlightModesType value, uint8_t att if (attr && m_posHorz < 0) flags |= INVERS; char s[] = " "; s[0] = '0' + i; - lcd_putsAtt(x, y, s, flags); + lcdDrawText(x, y, s, flags); x += 12; } } @@ -438,7 +438,7 @@ void menuModelExpoOne(evt_t event) uint8_t not_stick = (ed->srcRaw > MIXSRC_Ail); int8_t carryTrim = -ed->carryTrim; lcd_putsLeft(y, STR_TRIM); - lcd_putsiAtt(EXPO_ONE_2ND_COLUMN, y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); + lcdDrawTextAtIndex(EXPO_ONE_2ND_COLUMN, y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); if (attr) ed->carryTrim = -checkIncDecModel(event, carryTrim, not_stick ? TRIM_ON : -TRIM_OFF, -TRIM_AIL); break; } @@ -571,7 +571,7 @@ void menuModelMixOne(evt_t event) } case MIX_FIELD_TRIM: lcd_putsColumnLeft(x, y, STR_TRIM); - lcdDrawCheckBox(x+MIXES_2ND_COLUMN, y, !md2->carryTrim, attr); + drawCheckBox(x+MIXES_2ND_COLUMN, y, !md2->carryTrim, attr); if (attr) md2->carryTrim = !checkIncDecModel(event, !md2->carryTrim, 0, 1); break; #if defined(CURVES) @@ -596,7 +596,7 @@ void menuModelMixOne(evt_t event) if (md2->mixWarn) lcd_outdezAtt(x+MIXES_2ND_COLUMN, y, md2->mixWarn, attr|LEFT); else - lcd_putsAtt(x+MIXES_2ND_COLUMN, y, STR_OFF, attr); + lcdDrawText(x+MIXES_2ND_COLUMN, y, STR_OFF, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, md2->mixWarn, 3); break; #endif @@ -681,7 +681,7 @@ void displayHeaderChannelName(uint8_t ch) { uint8_t len = zlen(g_model.limitData[ch-1].name, sizeof(g_model.limitData[ch-1].name)); if (len) { - lcd_putsnAtt(COLUMN_HEADER_X, MENU_FOOTER_TOP, g_model.limitData[ch-1].name, len, HEADER_COLOR|ZCHAR); + lcdDrawTextWithLen(COLUMN_HEADER_X, MENU_FOOTER_TOP, g_model.limitData[ch-1].name, len, HEADER_COLOR|ZCHAR); } } @@ -697,7 +697,7 @@ void displayMixInfos(coord_t y, MixData *md) void displayMixLine(coord_t y, MixData *md) { if (md->name[0]) - lcd_putsnAtt(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); + lcdDrawTextWithLen(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); displayMixInfos(y, md); displayFlightModes(MIX_LINE_FM_POS, y, md->flightModes, 0); } @@ -721,7 +721,7 @@ void displayExpoLine(coord_t y, ExpoData *ed) displayFlightModes(EXPO_LINE_FM_POS, y, ed->flightModes, 0); if (ed->name[0]) { - lcd_putsnAtt(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); + lcdDrawTextWithLen(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); } } @@ -782,7 +782,6 @@ void menuModelExpoMix(uint8_t expo, evt_t event) } // no break - CASE_EVT_ROTARY_BREAK case EVT_KEY_LONG(KEY_ENTER): killEvents(event); if (s_copyTgtOfs) { @@ -829,29 +828,29 @@ void menuModelExpoMix(uint8_t expo, evt_t event) killEvents(event); } break; - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_copyMode) { uint8_t key = (event & 0x1f); - uint8_t next_ofs = ((IS_ROTARY_UP(event) || key==KEY_MOVE_UP) ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); + uint8_t next_ofs = ((event==EVT_ROTARY_LEFT || key==KEY_UP) ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); if (s_copyTgtOfs==0 && s_copyMode==COPY_MODE) { // insert a mix on the same channel (just above / just below) if (reachExpoMixCountLimit(expo)) break; copyExpoMix(expo, s_currIdx); - if (IS_ROTARY_DOWN(event) || key==KEY_MOVE_DOWN) s_currIdx++; + if (event==EVT_ROTARY_RIGHT || key==KEY_DOWN) s_currIdx++; else if (sub-s_pgOfs >= 6) s_pgOfs++; } else if (next_ofs==0 && s_copyMode==COPY_MODE) { // delete the mix deleteExpoMix(expo, s_currIdx); - if (IS_ROTARY_UP(event) || key==KEY_MOVE_UP) s_currIdx--; + if (event==EVT_ROTARY_LEFT || key==KEY_UP) s_currIdx--; } else { // only swap the mix with its neighbor - if (!swapExpoMix(expo, s_currIdx, IS_ROTARY_UP(event) || key==KEY_MOVE_UP)) break; + if (!swapExpoMix(expo, s_currIdx, event==EVT_ROTARY_LEFT || key==KEY_UP)) break; storageDirty(EE_MODEL); } @@ -862,7 +861,7 @@ void menuModelExpoMix(uint8_t expo, evt_t event) char str[6]; sprintf(str, "%d/%d", getExpoMixCount(expo), expo ? MAX_EXPOS : MAX_MIXERS); - lcd_putsAtt(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, str, HEADER_COLOR); + lcdDrawText(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, str, HEADER_COLOR); sub = m_posVert; s_currCh = 0; @@ -907,7 +906,7 @@ void menuModelExpoMix(uint8_t expo, evt_t event) displayHeaderChannelName(ch); } - if (mixCnt > 0) lcd_putsiAtt(6, y, STR_VMLTPX2, md->mltpx, 0); + if (mixCnt > 0) lcdDrawTextAtIndex(6, y, STR_VMLTPX2, md->mltpx, 0); putsMixerSource(MIX_LINE_SRC_POS, y, md->srcRaw); diff --git a/radio/src/gui/Horus/menu_model_limits.cpp b/radio/src/gui/Horus/menu_model_limits.cpp index 77df8b0fc..27c3f46c1 100644 --- a/radio/src/gui/Horus/menu_model_limits.cpp +++ b/radio/src/gui/Horus/menu_model_limits.cpp @@ -71,9 +71,9 @@ enum LimitsItems { #define LIMITS_DIRECTION_POS 240 #define LIMITS_MAX_POS 300 #define LIMITS_REVERT_POS 320 - #define LIMITS_CURVE_POS 360 - #define LIMITS_PPM_CENTER_POS 450 - #define LIMITS_SYMETRICAL_POS 460 + #define LIMITS_CURVE_POS 350 + #define LIMITS_PPM_CENTER_POS 440 + #define LIMITS_SYMETRICAL_POS 450 #else #define LIMITS_NAME_POS 44 #define LIMITS_OFFSET_POS 136 @@ -155,7 +155,7 @@ void menuModelLimits(evt_t event) // last line available - add the "copy trim menu" line uint8_t attr = (sub==NUM_CHNOUT) ? INVERS : 0; // TODO CENTER attribute - lcd_putsAtt(100, y, STR_TRIMS2OFFSETS, s_noHi ? 0 : attr); + lcdDrawText(100, y, STR_TRIMS2OFFSETS, s_noHi ? 0 : attr); if (attr) { s_editMode = 0; if (event==EVT_KEY_LONG(KEY_ENTER)) { @@ -238,9 +238,9 @@ void menuModelLimits(evt_t event) { uint8_t revert = ld->revert; #if defined(PPM_CENTER_ADJUSTABLE) - lcd_putsAtt(LIMITS_REVERT_POS, y, revert ? "\177" : "\176", attr); + lcdDrawText(LIMITS_REVERT_POS, y, revert ? "\177" : "\176", attr); #else - lcd_putsiAtt(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); + lcdDrawTextAtIndex(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); #endif if (active) { uint8_t revert_new = checkIncDecModel(event, revert, 0, 1); @@ -278,7 +278,7 @@ void menuModelLimits(evt_t event) #if defined(PPM_LIMITS_SYMETRICAL) case ITEM_LIMITS_SYMETRICAL: - lcd_putsAtt(LIMITS_SYMETRICAL_POS, y, ld->symetrical ? "=" : "\306", attr); + lcdDrawText(LIMITS_SYMETRICAL_POS, y, ld->symetrical ? "=" : "\306", attr); if (active) { CHECK_INCDEC_MODELVAR_ZERO(event, ld->symetrical, 1); } diff --git a/radio/src/gui/Horus/menu_model_logical_switches.cpp b/radio/src/gui/Horus/menu_model_logical_switches.cpp index 23f4a88d9..17179a60c 100644 --- a/radio/src/gui/Horus/menu_model_logical_switches.cpp +++ b/radio/src/gui/Horus/menu_model_logical_switches.cpp @@ -71,9 +71,9 @@ void putsEdgeDelayParam(coord_t x, coord_t y, LogicalSwitchData *cs, uint8_t lat sprintf(s, "[%s:%s]", sleft, sright); lcd_puts(x-4, y, s); /* if (cs->v3 < 0) - lcd_putsAtt(lcdLastPos+3, y, "<<", rattr); + lcdDrawText(lcdLastPos+3, y, "<<", rattr); else if (cs->v3 == 0) - lcd_putsAtt(lcdLastPos+3, y, "--", rattr); */ + lcdDrawText(lcdLastPos+3, y, "--", rattr); */ } void onLogicalSwitchesMenu(const char *result) @@ -134,7 +134,7 @@ void menuModelLogicalSwitches(evt_t event) putsSwitches(MENUS_MARGIN_LEFT, y, sw, (getSwitch(sw) ? 0/*BOLD*/ : 0) | ((sub==k && CURSOR_ON_LINE()) ? INVERS : 0)); // CSW func - lcd_putsiAtt(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); + lcdDrawTextAtIndex(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); // CSW params unsigned int cstate = lswFamily(cs->func); @@ -203,7 +203,7 @@ void menuModelLogicalSwitches(evt_t event) if (cs->duration > 0) lcd_outdezAtt(CSW_5TH_COLUMN, y, cs->duration, (horz==LS_FIELD_DURATION ? attr : 0)|PREC1|LEFT); else - lcd_putsiAtt(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); + lcdDrawTextAtIndex(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); // CSW delay if (cstate == LS_FAMILY_EDGE) { @@ -216,7 +216,7 @@ void menuModelLogicalSwitches(evt_t event) lcd_outdezAtt(CSW_6TH_COLUMN, y, cs->delay, (horz==LS_FIELD_DELAY ? attr : 0)|PREC1|LEFT); } else { - lcd_putsiAtt(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); + lcdDrawTextAtIndex(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); } if (attr && horz == LS_FIELD_V3 && cstate != LS_FAMILY_EDGE) { diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp index 62cfb8f1e..4162a1edb 100644 --- a/radio/src/gui/Horus/menu_model_select.cpp +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -85,209 +85,22 @@ char *strAppendModelSize(char *dest, int size) return strAppend(dest, s); } +#define CATEGORIES_WIDTH 140 + void menuModelSelect(evt_t event) { - if (s_warning_result) { - s_warning_result = 0; - eeDeleteModel(m_posVert); // delete file - s_copyMode = 0; - event = EVT_ENTRY_UP; - } + // Header + lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); + lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); + // lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); + drawTopmenuDatetime(); - evt_t _event_ = event; - event = (IS_ROTARY_BREAK(event) || IS_ROTARY_LONG(event) ? 0 : event); - if ((s_copyMode && EVT_KEY_MASK(event) == KEY_EXIT) || event == EVT_KEY_BREAK(KEY_EXIT)) { - event = 0; - } + // Categories + lcdDrawSolidFilledRect(0, MENU_HEADER_HEIGHT, CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TITLE_BGCOLOR); - int8_t oldSub = m_posVert; + // Models + lcdDrawSolidFilledRect(CATEGORIES_WIDTH, MENU_HEADER_HEIGHT, LCD_W-CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TEXT_BGCOLOR); - // TODO SIMPLE_MENU(STR_MENUMODELSEL, menuTabModel, e_ModelSelect, MAX_MODELS, DEFAULT_SCROLLBAR_X); - - if (s_editMode > 0) s_editMode = 0; - - int8_t sub = m_posVert; - - switch (_event_) - { - case EVT_ENTRY: - m_posVert = sub = g_eeGeneral.currModel; - if (sub >= NUM_BODY_LINES) s_pgOfs = sub-(NUM_BODY_LINES-1); - s_copyMode = 0; - s_editMode = EDIT_MODE_INIT; - storageCheck(true); - break; - case EVT_KEY_LONG(KEY_EXIT): - if (s_copyMode && s_copyTgtOfs == 0 && g_eeGeneral.currModel != sub && eeModelExists(sub)) { - POPUP_CONFIRMATION(STR_DELETEMODEL); - // SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); - killEvents(_event_); - break; - } - // no break - case EVT_KEY_BREAK(KEY_EXIT): - if (s_copyMode) { - sub = m_posVert = (s_copyMode == MOVE_MODE || s_copySrcRow<0) ? (MAX_MODELS+sub+s_copyTgtOfs) % MAX_MODELS : s_copySrcRow; - s_copyMode = 0; - } - else { - if (m_posVert != g_eeGeneral.currModel) { - m_posVert = g_eeGeneral.currModel; - s_pgOfs = 0; - } - else if (_event_ != EVT_KEY_LONG(KEY_EXIT)) { - popMenu(); - } - } - break; - case EVT_KEY_LONG(KEY_ENTER): - case EVT_KEY_BREAK(KEY_ENTER): - s_editMode = 0; - if (READ_ONLY()) { - if (g_eeGeneral.currModel != sub && eeModelExists(sub)) { - selectModel(sub); - } - } - else if (s_copyMode && (s_copyTgtOfs || s_copySrcRow>=0)) { - storageCheck(true); // force writing of current model data before this is changed - uint8_t cur = (MAX_MODELS + sub + s_copyTgtOfs) % MAX_MODELS; - if (s_copyMode == COPY_MODE) { - if (!eeCopyModel(cur, s_copySrcRow)) { - cur = sub; - } - } - - s_copySrcRow = g_eeGeneral.currModel; // to update the currModel value - while (sub != cur) { - uint8_t src = cur; - cur = (s_copyTgtOfs > 0 ? cur+MAX_MODELS-1 : cur+1) % MAX_MODELS; - eeSwapModels(src, cur); - if (src == s_copySrcRow) - s_copySrcRow = cur; - else if (cur == s_copySrcRow) - s_copySrcRow = src; - } - - if (s_copySrcRow != g_eeGeneral.currModel) { - g_eeGeneral.currModel = s_copySrcRow; - storageDirty(EE_GENERAL); - } - - s_copyMode = 0; - _event_ = EVT_ENTRY_UP; - } - else if (_event_ == EVT_KEY_LONG(KEY_ENTER)) { - s_copyMode = 0; - killEvents(_event_); - if (g_eeGeneral.currModel != sub) { - if (eeModelExists(sub)) { - MENU_ADD_ITEM(STR_SELECT_MODEL); - MENU_ADD_SD_ITEM(STR_BACKUP_MODEL); - MENU_ADD_ITEM(STR_COPY_MODEL); - MENU_ADD_ITEM(STR_MOVE_MODEL); - MENU_ADD_ITEM(STR_DELETE_MODEL); - } - else { - MENU_ADD_ITEM(STR_CREATE_MODEL); - MENU_ADD_ITEM(STR_RESTORE_MODEL); - } - } - else { - MENU_ADD_SD_ITEM(STR_BACKUP_MODEL); - MENU_ADD_ITEM(STR_COPY_MODEL); - MENU_ADD_ITEM(STR_MOVE_MODEL); - } - menuHandler = onModelSelectMenu; - } - else if (eeModelExists(sub)) { - s_copyMode = (s_copyMode == COPY_MODE ? MOVE_MODE : COPY_MODE); - s_copyTgtOfs = 0; - s_copySrcRow = -1; - } - break; - -#if 0 - case EVT_KEY_BREAK(KEY_PAGE): - case EVT_KEY_LONG(KEY_PAGE): - chainMenu(_event_ == EVT_KEY_BREAK(KEY_PAGE) ? menuModelSetup : menuTabModel[DIM(menuTabModel)-1]); - killEvents(_event_); - break; -#endif - - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): - if (s_copyMode) { - int8_t next_ofs = s_copyTgtOfs + oldSub - m_posVert; - if (next_ofs == MAX_MODELS || next_ofs == -MAX_MODELS) - next_ofs = 0; - - if (s_copySrcRow < 0 && s_copyMode==COPY_MODE) { - s_copySrcRow = oldSub; - // find a hole (in the first empty slot above / below) - sub = eeFindEmptyModel(s_copySrcRow, IS_ROTARY_DOWN(_event_) || _event_==EVT_KEY_FIRST(KEY_MOVE_DOWN)); - if (sub < 0) { - // no free room for duplicating the model - AUDIO_ERROR(); - sub = oldSub; - s_copyMode = 0; - } - next_ofs = 0; - m_posVert = sub; - } - s_copyTgtOfs = next_ofs; - } - break; - } - - for (int i=0; i= 0)) { - if (k == sub) { - if (s_copyMode == COPY_MODE) { - k = s_copySrcRow; - // lcdDrawRect(MENUS_MARGIN_LEFT-5, y-3, 170, 16, SOLID, YELLOW); - } - else { - k = sub + s_copyTgtOfs; - } - } - else if (s_copyTgtOfs < 0 && ((k < sub && k >= sub+s_copyTgtOfs) || (k-MAX_MODELS < sub && k-MAX_MODELS >= sub+s_copyTgtOfs))) - k += 1; - else if (s_copyTgtOfs > 0 && ((k > sub && k <= sub+s_copyTgtOfs) || (k+MAX_MODELS > sub && k+MAX_MODELS <= sub+s_copyTgtOfs))) - k += MAX_MODELS-1; - } - - k %= MAX_MODELS; - - if (eeModelExists(k)) { - // TODO strcat_modelname(str, k); - lcd_putsAtt(POS_MODEL_NAME, y, str, flags); - if (k==g_eeGeneral.currModel && (s_copyMode!=COPY_MODE || s_copySrcRow<0 || i+s_pgOfs!=(vertpos_t)sub)) { - lcd_puts(1, y, "*"); - } - } - else { - lcd_putsAtt(POS_MODEL_NAME, y, "---", flags); - } - - if (s_copyMode && (vertpos_t)sub==i+s_pgOfs) { - lcdDrawRect(MENUS_MARGIN_LEFT-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 150, INVERT_LINE_HEIGHT, s_copyMode == COPY_MODE ? SOLID : DOTTED, WARNING_COLOR); - } - } - - // TODO lcdDrawBitmap(200, 67, logos, 60*g_model.header.bitmap, 60); + // Footer + lcdDrawSolidFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); } diff --git a/radio/src/gui/Horus/menu_model_setup.cpp b/radio/src/gui/Horus/menu_model_setup.cpp index cb3ec8269..e96191e6c 100644 --- a/radio/src/gui/Horus/menu_model_setup.cpp +++ b/radio/src/gui/Horus/menu_model_setup.cpp @@ -102,11 +102,26 @@ void copySelection(char *dst, const char *src, uint8_t size) memcpy(dst, src, size); } +void onModelSetupBitmapMenu(const char *result) +{ + if (result == STR_UPDATE_LIST) { + if (!sdListFiles(BITMAPS_PATH, BITMAPS_EXT, sizeof(g_model.header.bitmap), NULL)) { + POPUP_WARNING(STR_NO_BITMAPS_ON_SD); + s_menu_flags = 0; + } + } + else { + // The user choosed a bmp file in the list + copySelection(g_model.header.bitmap, result, sizeof(g_model.header.bitmap)); + storageDirty(EE_MODEL); + } +} + void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, evt_t event) { TimerData * timer = &g_model.timers[timerIdx]; if (attr && m_posHorz < 0) { - lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 80+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 90+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } putsStrIdx(MENUS_MARGIN_LEFT, y, STR_TIMER, timerIdx+1); putsTimerMode(MODEL_SETUP_2ND_COLUMN, y, timer->mode, (m_posHorz<=0 ? attr : 0)); @@ -158,7 +173,7 @@ int getSwitchWarningsCount() void menuModelSetup(evt_t event) { horzpos_t l_posHorz = m_posHorz; - #define IF_EXTERNAL_MODULE_ON(x) (g_model.externalModule == MODULE_TYPE_NONE ? HIDDEN_ROW : (uint8_t)(x)) + #define IF_EXTERNAL_MODULE_ON(x) (g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_NONE ? HIDDEN_ROW : (uint8_t)(x)) #define IF_TRAINER_ON(x) (g_model.trainerMode == TRAINER_MODE_SLAVE ? (uint8_t)(x) : HIDDEN_ROW) #define IF_EXTERNAL_MODULE_XJT(x) (IS_MODULE_XJT(EXTERNAL_MODULE) ? (uint8_t)x : HIDDEN_ROW) #define IS_D8_RX(x) (g_model.moduleData[x].rfProtocol == RF_PROTO_D8) @@ -208,8 +223,20 @@ void menuModelSetup(evt_t event) case ITEM_MODEL_BITMAP: lcd_putsLeft(y, STR_BITMAP); - lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN, y, g_model.header.bitmap, LEFT|attr); - if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.bitmap, 3); + if (ZEXIST(g_model.header.bitmap)) + lcdDrawTextWithLen(MODEL_SETUP_2ND_COLUMN, y, g_model.header.bitmap, sizeof(g_model.header.bitmap), attr); + else + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VCSWFUNC, 0, attr); + if (attr && event==EVT_KEY_BREAK(KEY_ENTER) && READ_ONLY_UNLOCKED()) { + s_editMode = 0; + if (sdListFiles(BITMAPS_PATH, BITMAPS_EXT, sizeof(g_model.header.bitmap), g_model.header.bitmap, LIST_NONE_SD_FILE)) { + menuHandler = onModelSetupBitmapMenu; + } + else { + POPUP_WARNING(STR_NO_BITMAPS_ON_SD); + s_menu_flags = 0; + } + } break; case ITEM_MODEL_TIMER1: @@ -282,7 +309,7 @@ void menuModelSetup(evt_t event) case ITEM_MODEL_EXTENDED_TRIMS: ON_OFF_MENU_ITEM(g_model.extendedTrims, MODEL_SETUP_2ND_COLUMN, y, STR_ETRIMS, m_posHorz<=0 ? attr : 0, event==EVT_KEY_BREAK(KEY_ENTER) ? event : 0); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+18, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+18, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); if (attr && m_posHorz>0) { s_editMode = 0; if (event==EVT_KEY_LONG(KEY_ENTER)) { @@ -391,7 +418,7 @@ void menuModelSetup(evt_t event) max = 2; } s[2] = '\0'; - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+i*18, y, s, color|(m_posHorz==current ? attr : 0)); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+i*18, y, s, color|(m_posHorz==current ? attr : 0)); if (attr && m_posHorz==current) CHECK_INCDEC_MODELVAR_ZERO(event, state, max); newStates |= (state << (3*i)); ++current; @@ -423,13 +450,13 @@ void menuModelSetup(evt_t event) } } } - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, PSTR("\004""OFF\0""Man\0""Auto"), g_model.potsWarnMode, (m_posHorz == 0) ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, PSTR("\004""OFF\0""Man\0""Auto"), g_model.potsWarnMode, (m_posHorz == 0) ? attr : 0); if (g_model.potsWarnMode) { coord_t x = MODEL_SETUP_2ND_COLUMN+30; for (int i=0; i0) { switch (m_posHorz) { case 0: - g_model.externalModule = checkIncDec(event, g_model.externalModule, MODULE_TYPE_NONE, MODULE_TYPE_COUNT-1, EE_MODEL, isModuleAvailable); + g_model.moduleData[EXTERNAL_MODULE].type = checkIncDec(event, g_model.moduleData[EXTERNAL_MODULE].type, MODULE_TYPE_NONE, MODULE_TYPE_COUNT-1, EE_MODEL, isModuleAvailable); if (checkIncDec_Ret) { g_model.moduleData[EXTERNAL_MODULE].rfProtocol = 0; g_model.moduleData[EXTERNAL_MODULE].channelsStart = 0; - if (g_model.externalModule == MODULE_TYPE_PPM) + if (g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_PPM) g_model.moduleData[EXTERNAL_MODULE].channelsCount = 0; else g_model.moduleData[EXTERNAL_MODULE].channelsCount = MAX_EXTERNAL_MODULE_CHANNELS(); @@ -532,7 +559,7 @@ void menuModelSetup(evt_t event) break; case 1: CHECK_INCDEC_MODELVAR(event, moduleData.channelsCount, -4, min(MAX_CHANNELS(moduleIdx), 32-8-moduleData.channelsStart)); - if ((k == ITEM_MODEL_EXTERNAL_MODULE_CHANNELS && g_model.externalModule == MODULE_TYPE_PPM) + if ((k == ITEM_MODEL_EXTERNAL_MODULE_CHANNELS && g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_PPM) || (k == ITEM_MODEL_TRAINER_CHANNELS) ) SET_DEFAULT_PPM_FRAME_LENGTH(moduleIdx); @@ -552,7 +579,7 @@ void menuModelSetup(evt_t event) lcd_putsLeft(y, STR_PPMFRAME); lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN, y, (int16_t)moduleData.ppmFrameLength*5 + 225, (m_posHorz<=0 ? attr : 0) | PREC1|LEFT, STR_MS); lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN+70, y, (moduleData.ppmDelay*50)+300, (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0, "us"); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+90, y, moduleData.ppmPulsePol ? "+" : "-", (CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+90, y, moduleData.ppmPulsePol ? "+" : "-", (CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0); if (attr && s_editMode>0) { switch (m_posHorz) { @@ -584,8 +611,8 @@ void menuModelSetup(evt_t event) if (attr && l_posHorz==0 && s_editMode>0) { CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], IS_MODULE_DSM2(moduleIdx) ? 20 : 63); } - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); uint8_t newFlag = 0; if (attr && l_posHorz>0 && s_editMode>0) { if (l_posHorz == 1) @@ -606,8 +633,8 @@ void menuModelSetup(evt_t event) ModuleData & moduleData = g_model.moduleData[moduleIdx]; lcd_putsLeft(y, TR_FAILSAFE); if (IS_MODULE_XJT(moduleIdx)) { - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); - if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcd_putsAtt(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); + if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcdDrawText(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); if (attr) { if (moduleData.failsafeMode != FAILSAFE_CUSTOM) m_posHorz = 0; @@ -625,7 +652,7 @@ void menuModelSetup(evt_t event) } } else { - lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN, y, LCD_W-MODEL_SETUP_2ND_COLUMN-MENUS_SCROLLBAR_WIDTH, 8, TEXT_COLOR); + lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN, y, LCD_W-MODEL_SETUP_2ND_COLUMN-2, 8, TEXT_COLOR); } } } @@ -654,7 +681,7 @@ void menuModelFailsafe(evt_t event) SEND_FAILSAFE_NOW(g_moduleIdx); } - SIMPLE_SUBMENU_NOTITLE(NUM_CHNOUT, 0); + SIMPLE_SUBMENU_NOTITLE(NUM_CHNOUT); #define COL_W (LCD_W/2) const uint8_t SLIDER_W = 64; @@ -691,7 +718,7 @@ void menuModelFailsafe(evt_t event) } if (lenLabel > 0) - lcd_putsnAtt(x+1-ofs, y, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR | SMLSIZE); + lcdDrawTextWithLen(x+1-ofs, y, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR | SMLSIZE); else putsChn(x+1-ofs, y, ch+1, SMLSIZE); diff --git a/radio/src/gui/Horus/menu_model_telemetry.cpp b/radio/src/gui/Horus/menu_model_telemetry.cpp index e1e2c4504..6a4818c91 100644 --- a/radio/src/gui/Horus/menu_model_telemetry.cpp +++ b/radio/src/gui/Horus/menu_model_telemetry.cpp @@ -124,7 +124,7 @@ enum menuModelTelemetryItems { #endif #define VARIO_RANGE_ROWS 3 -#define TELEMETRY_TYPE_ROWS (g_model.externalModule == MODULE_TYPE_PPM ? (uint8_t)0 : HIDDEN_ROW), +#define TELEMETRY_TYPE_ROWS (g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_PPM ? (uint8_t)0 : HIDDEN_ROW), #if defined(LUA) #define TELEMETRY_SCREEN_LINE(x) ((TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_NONE || TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_SCRIPT) ? HIDDEN_ROW : (uint8_t)2) @@ -288,7 +288,7 @@ void menuModelSensor(evt_t event) case SENSOR_FIELD_UNIT: lcd_putsLeft(y, STR_UNIT); // TODO flash saving with selectMenuItem where I copied those 2 lines? - lcd_putsiAtt(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); + lcdDrawTextAtIndex(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); if (attr) { CHECK_INCDEC_MODELVAR_ZERO(event, sensor->unit, UNIT_MAX); if (checkIncDec_Ret) { @@ -335,7 +335,7 @@ void menuModelSensor(evt_t event) lcd_putsLeft(y, "Ratio"); if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.ratio, 0, 30000); if (sensor->custom.ratio == 0) - lcd_putsAtt(SENSOR_2ND_COLUMN, y, "-", attr); + lcdDrawText(SENSOR_2ND_COLUMN, y, "-", attr); else lcd_outdezAtt(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); break; @@ -377,7 +377,7 @@ void menuModelSensor(evt_t event) source = checkIncDec(event, source, -MAX_SENSORS, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); } if (source < 0) { - lcd_putsAtt(SENSOR_2ND_COLUMN, y, "-", attr); + lcdDrawText(SENSOR_2ND_COLUMN, y, "-", attr); putsMixerSource(SENSOR_2ND_COLUMN+5, y, MIXSRC_FIRST_TELEM+3*(-1-source), attr); } else { @@ -462,7 +462,7 @@ void menuModelTelemetry(evt_t event) if (k>=ITEM_TELEMETRY_SENSOR1 && k MENU_COLUMN2_X+MENUS_MARGIN_LEFT+68) ? MENU_COLUMN2_X+MENUS_MARGIN_LEFT : MENUS_MARGIN_LEFT, y, str) @@ -66,7 +65,7 @@ extern vertpos_t s_pgOfs; extern uint8_t s_noHi; extern uint8_t calibrationState; -void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr); +void drawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr); typedef void (*MenuFuncP)(evt_t event); typedef void (*MenuFuncP_PROGMEM)(evt_t event); @@ -107,7 +106,7 @@ void menuAboutView(evt_t event); void menuTraceBuffer(evt_t event); #endif -void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); +void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); void menuMainViewChannelsMonitor(evt_t event); void menuChannelsView(evt_t event); @@ -200,36 +199,36 @@ int8_t checkIncDecMovedSwitch(int8_t val); #define CURSOR_ON_LINE() (m_posHorz<0) #define CHECK_FLAG_NO_SCREEN_INDEX 1 -bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t maxrow, uint16_t scrollbar_X, uint8_t flags=0); -bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t maxrow, uint16_t scrollbar_X); -bool check_submenu_simple(check_event_t event, uint8_t maxrow, uint16_t scrollbar_X); +bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t maxrow, uint8_t flags=0); +bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t maxrow); +bool check_submenu_simple(check_event_t event, uint8_t maxrow); #define MENU_TAB(...) const uint8_t mstate_tab[] = __VA_ARGS__ -void drawMenuTemplate(const char *title, evt_t event); +void drawMenuTemplate(const char *title, evt_t event, uint16_t scrollbar_X=0); #define MENU(title, tab, menu, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ - if (!check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X)) return; \ - drawMenuTemplate(title, event); \ + if (!check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count)) return; \ + drawMenuTemplate(title, event, scrollbar_X); \ #define SIMPLE_MENU(title, tab, menu, lines_count, scrollbar_X) \ if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ - if (!check_simple(event, menu, tab, DIM(tab), lines_count, scrollbar_X)) return; \ - drawMenuTemplate(title, event); \ + if (!check_simple(event, menu, tab, DIM(tab), lines_count)) return; \ + drawMenuTemplate(title, event, scrollbar_X); \ #define SUBMENU(title, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ - if (!check(event, 0, NULL, 0, mstate_tab, DIM(mstate_tab)-1, lines_count, scrollbar_X)) return; \ - drawMenuTemplate(title, event); + if (!check(event, 0, NULL, 0, mstate_tab, DIM(mstate_tab)-1, lines_count)) return; \ + drawMenuTemplate(title, event, scrollbar_X); -#define SIMPLE_SUBMENU_NOTITLE(lines_count, scrollbar_X) \ - if (!check_submenu_simple(event, lines_count, scrollbar_X)) return +#define SIMPLE_SUBMENU_NOTITLE(lines_count) \ + if (!check_submenu_simple(event, lines_count)) return #define SIMPLE_SUBMENU(title, lines_count, scrollbar_X) \ - SIMPLE_SUBMENU_NOTITLE(lines_count, scrollbar_X); \ - drawMenuTemplate(title, event) + SIMPLE_SUBMENU_NOTITLE(lines_count); \ + drawMenuTemplate(title, event, scrollbar_X) typedef int select_menu_value_t; @@ -310,35 +309,21 @@ extern void (*menuHandler)(const char *result); extern char s_text_file[TEXT_FILENAME_MAXLEN]; void menuTextView(evt_t event); void pushMenuTextView(const char *filename); -bool modelHasNotes(); void pushModelNotes(); void menuChannelsView(evt_t event); #define LABEL(...) (uint8_t)-1 -#define KEY_MOVE_UP KEY_PLUS -#define KEY_MOVE_DOWN KEY_MINUS +#define CURSOR_MOVED_LEFT(event) (event==EVT_ROTARY_LEFT || EVT_KEY_MASK(event) == KEY_LEFT) +#define CURSOR_MOVED_RIGHT(event) (event==EVT_ROTARY_RIGHT || EVT_KEY_MASK(event) == KEY_RIGHT) -#if defined(REV9E) - #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_MINUS) - #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_PLUS) -#else - #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_PLUS) - #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_MINUS) -#endif +#define REPEAT_LAST_CURSOR_MOVE() { if (CURSOR_MOVED_LEFT(event) || CURSOR_MOVED_RIGHT(event)) putEvent(event); else m_posHorz = 0; } +#define MOVE_CURSOR_FROM_HERE() if (m_posHorz > 0) REPEAT_LAST_CURSOR_MOVE() -#define CASE_EVT_ROTARY_MOVE_RIGHT CASE_EVT_ROTARY_LEFT -#define CASE_EVT_ROTARY_MOVE_LEFT CASE_EVT_ROTARY_RIGHT -#define IS_ROTARY_MOVE_RIGHT IS_ROTARY_LEFT -#define IS_ROTARY_MOVE_LEFT IS_ROTARY_RIGHT - -#define REPEAT_LAST_CURSOR_MOVE() { if (CURSOR_MOVED_LEFT(event) || CURSOR_MOVED_RIGHT(event)) putEvent(event); else m_posHorz = 0; } -#define MOVE_CURSOR_FROM_HERE() if (m_posHorz > 0) REPEAT_LAST_CURSOR_MOVE() - -#define MENU_FIRST_LINE_EDIT (MAXCOL((uint16_t)0) >= HIDDEN_ROW ? (MAXCOL((uint16_t)1) >= HIDDEN_ROW ? 2 : 1) : 0) -#define POS_HORZ_INIT(posVert) ((COLATTR(posVert) & NAVIGATION_LINE_BY_LINE) ? -1 : 0) -#define EDIT_MODE_INIT 0 // TODO enum +#define MENU_FIRST_LINE_EDIT (MAXCOL((uint16_t)0) >= HIDDEN_ROW ? (MAXCOL((uint16_t)1) >= HIDDEN_ROW ? 2 : 1) : 0) +#define POS_HORZ_INIT(posVert) ((COLATTR(posVert) & NAVIGATION_LINE_BY_LINE) ? -1 : 0) +#define EDIT_MODE_INIT 0 // TODO enum typedef int (*FnFuncP) (int x); void DrawFunction(FnFuncP fn, int offset=0); diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp index dda8c5029..31d5c893b 100644 --- a/radio/src/gui/Horus/navigation.cpp +++ b/radio/src/gui/Horus/navigation.cpp @@ -114,7 +114,7 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags } #endif - if (s_editMode>0 && (IS_ROTARY_RIGHT(event) || event==EVT_KEY_FIRST(KEY_UP) || event==EVT_KEY_REPT(KEY_UP))) { + if (s_editMode>0 && event==EVT_ROTARY_RIGHT) { do { if (IS_KEY_REPT(event) && (i_flags & INCDEC_REP10)) { newval += min(10, i_max-val); @@ -133,7 +133,7 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags AUDIO_KEYPAD_UP(); } } - else if (s_editMode>0 && (IS_ROTARY_LEFT(event) || event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_REPT(KEY_DOWN))) { + else if (s_editMode>0 && event==EVT_ROTARY_LEFT) { do { if (IS_KEY_REPT(event) && (i_flags & INCDEC_REP10)) { newval -= min(10, val-i_min); @@ -153,7 +153,7 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags } } - if (!READ_ONLY() && i_min==0 && i_max==1 && (event==EVT_KEY_BREAK(KEY_ENTER) || IS_ROTARY_BREAK(event))) { + if (!READ_ONLY() && i_min==0 && i_max==1 && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; newval = !val; } @@ -190,6 +190,7 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags } if (newval != val) { +#if 0 if (!(i_flags & NO_INCDEC_MARKS) && (newval != i_max) && (newval != i_min) && stops.contains(newval) && !IS_ROTARY_EVENT(event)) { bool pause = (newval > val ? !stops.contains(newval+1) : !stops.contains(newval-1)); if (pause) { @@ -200,6 +201,7 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags AUDIO_KEYPAD_DOWN(); } } +#endif storageDirty(i_flags & (EE_GENERAL|EE_MODEL)); checkIncDec_Ret = (newval > val ? 1 : -1); } @@ -209,24 +211,22 @@ int checkIncDec(evt_t event, int val, int i_min, int i_max, unsigned int i_flags return newval; } -#define CURSOR_NOT_ALLOWED_IN_ROW(row) ((int8_t)MAXCOL(row) < 0) -#define MAXCOL_RAW(row) (horTab ? pgm_read_byte(horTab+min(row, (vertpos_t)horTabMax)) : (const uint8_t)0) -#define MAXCOL(row) (MAXCOL_RAW(row) >= HIDDEN_ROW ? MAXCOL_RAW(row) : (const uint8_t)(MAXCOL_RAW(row) & (~NAVIGATION_LINE_BY_LINE))) -#define COLATTR(row) (MAXCOL_RAW(row) == (uint8_t)-1 ? (const uint8_t)0 : (const uint8_t)(MAXCOL_RAW(row) & NAVIGATION_LINE_BY_LINE)) -#define INC(val, min, max) if (valmin) {val--;} else {val=max;} +#define CURSOR_NOT_ALLOWED_IN_ROW(row) ((int8_t)MAXCOL(row) < 0) +#define MAXCOL_RAW(row) (horTab ? pgm_read_byte(horTab+min(row, (vertpos_t)horTabMax)) : (const uint8_t)0) +#define MAXCOL(row) (MAXCOL_RAW(row) >= HIDDEN_ROW ? MAXCOL_RAW(row) : (const uint8_t)(MAXCOL_RAW(row) & (~NAVIGATION_LINE_BY_LINE))) +#define COLATTR(row) (MAXCOL_RAW(row) == (uint8_t)-1 ? (const uint8_t)0 : (const uint8_t)(MAXCOL_RAW(row) & NAVIGATION_LINE_BY_LINE)) +#define INC(val, min, max) if (valmin) {val--;} else {val=max;} uint8_t menuPageIndex; uint8_t menuPageCount; +uint16_t linesCount; -bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t rowcount, uint16_t scrollbar_X, uint8_t flags) +bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t rowcount, uint8_t flags) { - vertpos_t l_posVert = m_posVert; - horzpos_t l_posHorz = m_posHorz; + uint8_t maxcol = MAXCOL(m_posVert); - uint8_t maxcol = MAXCOL(l_posVert); - - if (menuTab && !calibrationState && l_posVert<0) { + if (menuTab && !calibrationState && m_posVert<0) { int cc = curr; switch (event) { case EVT_KEY_BREAK(KEY_RIGHT): @@ -243,10 +243,15 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_BREAK(KEY_ENTER): if (rowcount > 0) { - l_posVert = MENU_FIRST_LINE_EDIT; + m_posVert = MENU_FIRST_LINE_EDIT; event = 0; } break; + + case EVT_KEY_BREAK(KEY_DOWN): + case EVT_KEY_BREAK(KEY_UP): + m_posHorz = -1; + break; } if (cc != curr) { @@ -261,20 +266,20 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t switch(event) { case EVT_ENTRY: - l_posVert = (menuTab ? -1 : 0); - l_posHorz = POS_HORZ_INIT(l_posVert); + m_posVert = (menuTab ? -1 : MENU_FIRST_LINE_EDIT); + m_posHorz = POS_HORZ_INIT(0); s_editMode = EDIT_MODE_INIT; break; case EVT_ENTRY_UP: s_editMode = 0; - l_posHorz = POS_HORZ_INIT(l_posVert); + m_posHorz = POS_HORZ_INIT(m_posVert); break; case EVT_ROTARY_BREAK: if (s_editMode > 1) break; if (m_posHorz < 0 && maxcol > 0 && READ_ONLY_UNLOCKED()) { - l_posHorz = 0; + m_posHorz = 0; } else if (READ_ONLY_UNLOCKED()) { s_editMode = (s_editMode<=0); @@ -292,18 +297,18 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t break; } - if (l_posHorz >= 0 && (COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { - l_posHorz = -1; + if (m_posHorz >= 0 && (COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) { + m_posHorz = -1; } - else if (menuTab && l_posVert >= 0) { - l_posVert = -1; - l_posHorz = 0; + else if (menuTab && m_posVert >= 0) { + m_posVert = -1; + m_posHorz = 0; #if 0 int posVertInit = -1; - if (s_pgOfs != 0 || l_posVert != posVertInit) { + if (s_pgOfs != 0 || m_posVert != posVertInit) { s_pgOfs = 0; - l_posVert = posVertInit; - l_posHorz = POS_HORZ_INIT(l_posVert); + m_posVert = posVertInit; + m_posHorz = POS_HORZ_INIT(m_posVert); } #endif } @@ -315,88 +320,94 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t case EVT_KEY_FIRST(KEY_RIGHT): case EVT_KEY_REPT(KEY_RIGHT): if (s_editMode != 0) break; - INC(l_posHorz, 0, maxcol); + INC(m_posHorz, 0, maxcol); break; case EVT_KEY_FIRST(KEY_LEFT): case EVT_KEY_REPT(KEY_LEFT): if (s_editMode != 0) break; - DEC(l_posHorz, 0, maxcol); + DEC(m_posHorz, 0, maxcol); break; case EVT_ROTARY_RIGHT: if (s_editMode != 0) break; - if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { - if (l_posHorz >= 0) { - INC(l_posHorz, 0, maxcol); + if ((COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) { + if (m_posHorz >= 0) { + INC(m_posHorz, 0, maxcol); break; } } - else - { - if (l_posHorz < maxcol) { - l_posHorz++; - break; - } - else { - l_posHorz = 0; - } + else if (m_posHorz < maxcol) { + m_posHorz++; + break; } - // no break + do { + INC(m_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); + } while (CURSOR_NOT_ALLOWED_IN_ROW(m_posVert)); + m_posHorz = POS_HORZ_INIT(m_posVert); + break; case EVT_KEY_FIRST(KEY_DOWN): case EVT_KEY_REPT(KEY_DOWN): + { do { - INC(l_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); - } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); + INC(m_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); + } while (CURSOR_NOT_ALLOWED_IN_ROW(m_posVert)); s_editMode = 0; // if we go down, we must be in this mode - l_posHorz = POS_HORZ_INIT(l_posVert); + uint8_t newmaxcol = MAXCOL(m_posVert); + if (m_posHorz < 0 || (m_posHorz==0 && maxcol==0) || m_posHorz > newmaxcol) + m_posHorz = POS_HORZ_INIT(m_posVert); break; + } case EVT_ROTARY_LEFT: if (s_editMode != 0) break; - if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { - if (l_posHorz >= 0) { - DEC(l_posHorz, 0, maxcol); + if ((COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) { + if (m_posHorz >= 0) { + DEC(m_posHorz, 0, maxcol); break; } } + else if (m_posHorz > 0) { + m_posHorz--; + break; + } + do { + DEC(m_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); + } while (CURSOR_NOT_ALLOWED_IN_ROW(m_posVert)); + if ((COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) + m_posHorz = -1; else - { - if (l_posHorz > 0) { - l_posHorz--; - break; - } - else { - l_posHorz = 0xff; - } - } - // no break + m_posHorz = min((uint8_t)m_posHorz, MAXCOL(m_posVert)); + break; case EVT_KEY_FIRST(KEY_UP): case EVT_KEY_REPT(KEY_UP): + { do { - DEC(l_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); - } while (CURSOR_NOT_ALLOWED_IN_ROW(l_posVert)); - + DEC(m_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); + } while (CURSOR_NOT_ALLOWED_IN_ROW(m_posVert)); s_editMode = 0; // if we go up, we must be in this mode - - if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) - l_posHorz = -1; - else - l_posHorz = min((uint8_t)l_posHorz, MAXCOL(l_posVert)); + uint8_t newmaxcol = MAXCOL(m_posVert); + if (m_posHorz < 0 || (m_posHorz==0 && maxcol==0) || m_posHorz > newmaxcol) { + if ((COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) + m_posHorz = -1; + else + m_posHorz = min((uint8_t)m_posHorz, newmaxcol); + } break; + } } - int linesCount = rowcount; + linesCount = rowcount; - if (l_posVert <= 0 || (l_posVert==1 && MAXCOL(vertpos_t(0)) >= HIDDEN_ROW) || (l_posVert==2 && MAXCOL(vertpos_t(0)) >= HIDDEN_ROW && MAXCOL(vertpos_t(1)) >= HIDDEN_ROW)) { + if (m_posVert <= MENU_FIRST_LINE_EDIT) { s_pgOfs = 0; if (horTab) { linesCount = 0; for (int i=0; i=horTabMax || horTab[i] != HIDDEN_ROW) { + if (i>horTabMax || horTab[i] != HIDDEN_ROW) { linesCount++; } } @@ -411,7 +422,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t numLines++; } } - if (l_posVert < firstLine) { + if (m_posVert < firstLine) { s_pgOfs--; } else { @@ -421,13 +432,13 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t numLines++; } } - if (l_posVert >= lastLine) { + if (m_posVert >= lastLine) { s_pgOfs++; } else { linesCount = s_pgOfs + NUM_BODY_LINES; for (int i=lastLine; i=horTabMax || horTab[i] != HIDDEN_ROW) { + if (i>horTabMax || horTab[i] != HIDDEN_ROW) { linesCount++; } } @@ -438,32 +449,25 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t } } else { - if (l_posVert>=NUM_BODY_LINES+s_pgOfs) { - s_pgOfs = l_posVert-NUM_BODY_LINES+1; + if (m_posVert>=NUM_BODY_LINES+s_pgOfs) { + s_pgOfs = m_posVert-NUM_BODY_LINES+1; } - else if (l_posVert NUM_BODY_LINES) { - lcdDrawScrollbar(scrollbar_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, s_pgOfs, linesCount, NUM_BODY_LINES); - } - - m_posVert = l_posVert; - m_posHorz = l_posHorz; - return true; } -bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount, uint16_t scrollbar_X) +bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t rowcount) { - return check(event, curr, menuTab, menuTabSize, 0, 0, rowcount, scrollbar_X); + return check(event, curr, menuTab, menuTabSize, NULL, 0, rowcount); } -bool check_submenu_simple(check_event_t event, uint8_t rowcount, uint16_t scrollbar_X) +bool check_submenu_simple(check_event_t event, uint8_t rowcount) { - return check_simple(event, 0, 0, 0, rowcount, scrollbar_X); + return check_simple(event, 0, NULL, 0, rowcount); } void repeatLastCursorMove(evt_t event) diff --git a/radio/src/gui/Horus/popups.cpp b/radio/src/gui/Horus/popups.cpp index daa9fc83d..fdeb79ef1 100644 --- a/radio/src/gui/Horus/popups.cpp +++ b/radio/src/gui/Horus/popups.cpp @@ -78,11 +78,11 @@ void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SO displayAlertBox(); #if defined(TRANSLATIONS_FR) || defined(TRANSLATIONS_IT) || defined(TRANSLATIONS_CZ) - lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y, STR_WARNING, ALARM_COLOR|DBLSIZE); - lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y+15, title, ALARM_COLOR|DBLSIZE); + lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y, STR_WARNING, ALARM_COLOR|DBLSIZE); + lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y+15, title, ALARM_COLOR|DBLSIZE); #else - lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y, title, ALARM_COLOR|DBLSIZE); - lcd_putsAtt(WARNING_LINE_X, WARNING_LINE_Y+15, STR_WARNING, ALARM_COLOR|DBLSIZE); + lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y, title, ALARM_COLOR|DBLSIZE); + lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y+15, STR_WARNING, ALARM_COLOR|DBLSIZE); #endif if (t) lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y, t); @@ -99,7 +99,7 @@ void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SO void displayPopup(const char *title) { displayMessageBox(); - lcd_putsnAtt(WARNING_LINE_X, WARNING_LINE_Y, title, WARNING_LINE_LEN, DBLSIZE|WARNING_COLOR); + lcdDrawTextWithLen(WARNING_LINE_X, WARNING_LINE_Y, title, WARNING_LINE_LEN, DBLSIZE|WARNING_COLOR); lcdRefresh(); } @@ -110,24 +110,17 @@ void displayWarning(evt_t event) displayMessageBox(); else displayWarningBox(); - lcd_putsnAtt(WARNING_LINE_X, WARNING_LINE_Y, s_warning, WARNING_LINE_LEN, DBLSIZE | (s_warning_type == WARNING_TYPE_INPUT ? WARNING_COLOR : ALARM_COLOR)); + lcdDrawTextWithLen(WARNING_LINE_X, WARNING_LINE_Y, s_warning, WARNING_LINE_LEN, DBLSIZE | (s_warning_type == WARNING_TYPE_INPUT ? WARNING_COLOR : ALARM_COLOR)); if (s_warning_info) { - lcd_putsnAtt(WARNING_LINE_X, WARNING_INFOLINE_Y, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); + lcdDrawTextWithLen(WARNING_LINE_X, WARNING_INFOLINE_Y, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); } lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y+12, s_warning_type == WARNING_TYPE_ASTERISK ? STR_EXIT : STR_POPUPS); switch (event) { -#if defined(ROTARY_ENCODER_NAVIGATION) - case EVT_ROTARY_BREAK: -#endif case EVT_KEY_BREAK(KEY_ENTER): if (s_warning_type == WARNING_TYPE_ASTERISK) break; s_warning_result = true; // no break -#if defined(ROTARY_ENCODER_NAVIGATION) - case EVT_ROTARY_LONG: - killEvents(event); -#endif case EVT_KEY_BREAK(KEY_EXIT): s_warning = NULL; s_warning_type = WARNING_TYPE_ASTERISK; @@ -162,23 +155,21 @@ const char * displayMenu(evt_t event) for (uint8_t i=0; i display_count) { - lcdDrawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); + drawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); } - switch(event) { -#if defined(ROTARY_ENCODER_NAVIGATION) - CASE_EVT_ROTARY_LEFT -#endif - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): + switch (event) { + case EVT_ROTARY_LEFT: + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): if (s_menu_item > 0) { s_menu_item--; } @@ -195,11 +186,9 @@ const char * displayMenu(evt_t event) } break; -#if defined(ROTARY_ENCODER_NAVIGATION) - CASE_EVT_ROTARY_RIGHT -#endif - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_ROTARY_RIGHT: + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_menu_item < display_count - 1 && s_menu_offset + s_menu_item + 1 < s_menu_count) { s_menu_item++; } @@ -218,10 +207,6 @@ const char * displayMenu(evt_t event) case EVT_KEY_BREAK(KEY_ENTER): result = s_menu[s_menu_item]; // no break -#if defined(ROTARY_ENCODER_NAVIGATION) - CASE_EVT_ROTARY_LONG - killEvents(event); -#endif case EVT_KEY_BREAK(KEY_EXIT): s_menu_count = 0; s_menu_item = 0; diff --git a/radio/src/gui/Horus/view_about.cpp b/radio/src/gui/Horus/view_about.cpp index 2ac6ad383..a204fbf35 100644 --- a/radio/src/gui/Horus/view_about.cpp +++ b/radio/src/gui/Horus/view_about.cpp @@ -94,55 +94,55 @@ void menuAboutView(evt_t event) break; case ABOUT_BERTRAND: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_BERTRAND_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_BERTRAND_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_BERTRAND_2); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_BERTRAND_3); break; case ABOUT_ANDRE: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ANDRE_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ANDRE_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ANDRE_2); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ANDRE_3); break; case ABOUT_MIKE: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MIKE_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MIKE_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MIKE_2); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_MIKE_3); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_MIKE_4); break; case ABOUT_KJELL: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_KJELL_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_KJELL_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_KJELL_2); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_KJELL_3); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_KJELL_4); break; case ABOUT_MARTIN: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MARTIN_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MARTIN_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MARTIN_2); break; case ABOUT_ROMOLO: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROMOLO_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROMOLO_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROMOLO_2); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ROMOLO_3); break; case ABOUT_ROB: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROB_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROB_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROB_2); break; case ABOUT_HARDWARE: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, "FrSky", INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, "FrSky", INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_HARDWARE_2); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_HARDWARE_3); break; case ABOUT_PARENTS: - lcd_putsAtt(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_PARENTS_1, INVERS); + lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_PARENTS_1, INVERS); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_PARENTS_2); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_PARENTS_3); lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_PARENTS_4); diff --git a/radio/src/gui/Horus/view_channels.cpp b/radio/src/gui/Horus/view_channels.cpp index d87bb93b2..a3df50232 100644 --- a/radio/src/gui/Horus/view_channels.cpp +++ b/radio/src/gui/Horus/view_channels.cpp @@ -78,7 +78,7 @@ void menuChannelsView(evt_t event) // The label unsigned int lenLabel = ZLEN(g_model.limitData[ch].name); if (lenLabel > 0) - lcd_putsnAtt(x+CHANNEL_PADDING_HORZ, y+CHANNEL_PADDING_VERT, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR); + lcdDrawTextWithLen(x+CHANNEL_PADDING_HORZ, y+CHANNEL_PADDING_VERT, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR); else putsChn(x+CHANNEL_PADDING_HORZ, y+CHANNEL_PADDING_VERT, ch+1, 0); diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index e5e2a261d..a97b21862 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -168,7 +168,7 @@ void drawTimer(coord_t x, coord_t y, int index) } putsTimer(x+74, y+31, abs(timerState.val), TEXT_COLOR|DBLSIZE|LEFT); if (ZLEN(timerData.name) > 0) { - lcd_putsnAtt(x+74, y+20, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE|TEXT_COLOR); + lcdDrawTextWithLen(x+74, y+20, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE|TEXT_COLOR); } putsStrIdx(x+137, y+17, "TMR", 1, SMLSIZE|TEXT_COLOR); } @@ -198,7 +198,7 @@ void displayMainTelemetryFields() } att |= (item.isOld() ? ALARM_COLOR : TEXT_COLOR); lcdDrawSolidFilledRect(ALTITUDE_X, VOLTS_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); - lcd_putsAtt(ALTITUDE_X+PADDING, VOLTS_Y+2, "Voltage", att); + lcdDrawText(ALTITUDE_X+PADDING, VOLTS_Y+2, "Voltage", att); putsValueWithUnit(ALTITUDE_X+PADDING, VOLTS_Y+12, value, UNIT_VOLTS, DBLSIZE|LEFT|att); } } @@ -211,7 +211,7 @@ void displayMainTelemetryFields() if (sensor.prec) value /= sensor.prec == 2 ? 100 : 10; LcdFlags att = (item.isOld() ? ALARM_COLOR : TEXT_COLOR); lcdDrawSolidFilledRect(ALTITUDE_X, ALTITUDE_Y, ALTITUDE_W, ALTITUDE_H, TEXT_BGCOLOR); - lcd_putsAtt(ALTITUDE_X+PADDING, ALTITUDE_Y+2, "Alt", att); + lcdDrawText(ALTITUDE_X+PADDING, ALTITUDE_Y+2, "Alt", att); putsValueWithUnit(ALTITUDE_X+PADDING, ALTITUDE_Y+12, value, UNIT_METERS, DBLSIZE|LEFT|att); } } @@ -244,13 +244,15 @@ void displayMainViewIndex() void onMainViewMenu(const char *result) { - if (result == STR_RESET_TIMER1) { + if (result == STR_MODEL_SELECT) { + chainMenu(menuModelSelect); + } + else if (result == STR_RESET_TIMER1) { timerReset(0); } else if (result == STR_RESET_TIMER2) { timerReset(1); } -#if defined(CPUARM) else if (result == STR_RESET_TIMER3) { timerReset(2); } @@ -262,27 +264,20 @@ void onMainViewMenu(const char *result) MENU_ADD_ITEM(STR_RESET_TIMER1); MENU_ADD_ITEM(STR_RESET_TIMER2); MENU_ADD_ITEM(STR_RESET_TIMER3); -#if defined(FRSKY) MENU_ADD_ITEM(STR_RESET_TELEMETRY); -#endif } -#endif -#if defined(FRSKY) else if (result == STR_RESET_TELEMETRY) { telemetryReset(); } -#endif else if (result == STR_RESET_FLIGHT) { flightReset(); } else if (result == STR_STATISTICS) { chainMenu(menuStatisticsView); } -#if defined(CPUARM) else if (result == STR_ABOUT_US) { chainMenu(menuAboutView); } -#endif } const uint16_t LBM_MAINVIEW_FLAT[] = { @@ -296,9 +291,6 @@ const uint16_t LBM_CORSAIR[] = { void menuMainView(evt_t event) { - // clear the screen - lcdDrawSolidFilledRect(0, 0, LCD_W, LCD_H, TEXT_BGCOLOR); - switch (event) { case EVT_ENTRY: killEvents(KEY_EXIT); @@ -308,6 +300,7 @@ void menuMainView(evt_t event) case EVT_KEY_LONG(KEY_ENTER): killEvents(event); + MENU_ADD_ITEM(STR_MODEL_SELECT); if (modelHasNotes()) { MENU_ADD_ITEM(STR_VIEW_NOTES); } @@ -354,7 +347,7 @@ void menuMainView(evt_t event) lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); - lcdDrawTopmenuDatetime(); + drawTopmenuDatetime(); if (1 || usbPlugged()) { lcdDrawBitmapPattern(378, 8, LBM_TOPMENU_USB, MENU_TITLE_COLOR); } @@ -373,7 +366,7 @@ void menuMainView(evt_t event) } drawTrimSquare(LCD_W/2-25+5*mixerCurrentFlightMode, 253); lcdDrawChar(LCD_W/2-23+5*mixerCurrentFlightMode, 254, '0'+mixerCurrentFlightMode, SMLSIZE|TEXT_INVERTED_COLOR); - lcd_putsnAtt(LCD_W/2-getTextWidth(g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|SMLSIZE)/2, 237, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|SMLSIZE); + lcdDrawTextWithLen(LCD_W/2-getTextWidth(g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|SMLSIZE)/2, 237, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|SMLSIZE); // Sticks drawSticks(); @@ -384,7 +377,7 @@ void menuMainView(evt_t event) // Model panel lcdDrawFilledRect(248, 58, 188, 158, SOLID, TEXT_BGCOLOR | OPACITY(5)); lcdDrawBitmapPattern(256, 62, LBM_MODEL_ICON, TITLE_BGCOLOR); - lcd_putsnAtt(293, 68, g_model.header.name, LEN_MODEL_NAME, ZCHAR|SMLSIZE); + lcdDrawTextWithLen(293, 68, g_model.header.name, LEN_MODEL_NAME, ZCHAR|SMLSIZE); lcdDrawSolidHorizontalLine(287, 85, 140, TITLE_BGCOLOR); lcdDrawBitmap(256, 104, LBM_CORSAIR); @@ -398,7 +391,7 @@ void menuMainView(evt_t event) s_gvar_timer--; displayMessageBox(); putsStrIdx(WARNING_LINE_X, WARNING_LINE_Y, STR_GV, s_gvar_last+1, DBLSIZE|YELLOW); - lcd_putsnAtt(WARNING_LINE_X+45, WARNING_LINE_Y, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, DBLSIZE|YELLOW|ZCHAR); + lcdDrawTextWithLen(WARNING_LINE_X+45, WARNING_LINE_Y, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, DBLSIZE|YELLOW|ZCHAR); lcd_outdezAtt(WARNING_LINE_X, WARNING_INFOLINE_Y, GVAR_VALUE(s_gvar_last, getGVarFlightPhase(mixerCurrentFlightMode, s_gvar_last)), DBLSIZE|LEFT); } #endif diff --git a/radio/src/gui/Horus/view_statistics.cpp b/radio/src/gui/Horus/view_statistics.cpp index e0aeda81f..dee6c55ed 100644 --- a/radio/src/gui/Horus/view_statistics.cpp +++ b/radio/src/gui/Horus/view_statistics.cpp @@ -57,9 +57,9 @@ void menuStatisticsView(evt_t event) break; } - lcd_putsAtt( 10, MENU_CONTENT_TOP + FH*0, "\037\145TOT:\037\317BATT:", HEADER_COLOR); - lcd_putsAtt( 10, MENU_CONTENT_TOP + FH*1, "TM1:\037\145TM2:", HEADER_COLOR); - lcd_putsAtt( 10, MENU_CONTENT_TOP + FH*2, "THR:\037\145TH%:", HEADER_COLOR); + lcdDrawText( 10, MENU_CONTENT_TOP + FH*0, "\037\145TOT:\037\317BATT:", HEADER_COLOR); + lcdDrawText( 10, MENU_CONTENT_TOP + FH*1, "TM1:\037\145TM2:", HEADER_COLOR); + lcdDrawText( 10, MENU_CONTENT_TOP + FH*2, "THR:\037\145TH%:", HEADER_COLOR); putsTimer( 45, MENU_CONTENT_TOP + FH*1, timersStates[0].val); putsTimer( 140, MENU_CONTENT_TOP + FH*1, timersStates[1].val); @@ -136,9 +136,9 @@ void menuStatisticsDebug(evt_t event) #if defined(LUA) lcd_putsLeft(MENU_DEBUG_Y_LUA, "Lua scripts"); - lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_LUA+1, "[Duration]", HEADER_COLOR|SMLSIZE); + lcdDrawText(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_LUA+1, "[Duration]", HEADER_COLOR|SMLSIZE); lcd_outdezAtt(MENU_DEBUG_COL1_OFS+30, MENU_DEBUG_Y_LUA, 10*maxLuaDuration, LEFT); - lcd_putsAtt(MENU_DEBUG_COL1_OFS+60, MENU_DEBUG_Y_LUA+1, "[Interval]", HEADER_COLOR|SMLSIZE); + lcdDrawText(MENU_DEBUG_COL1_OFS+60, MENU_DEBUG_Y_LUA+1, "[Interval]", HEADER_COLOR|SMLSIZE); lcd_outdezAtt(MENU_DEBUG_COL1_OFS+90, MENU_DEBUG_Y_LUA, 10*maxLuaInterval, LEFT); #endif @@ -146,11 +146,11 @@ void menuStatisticsDebug(evt_t event) lcd_outdezAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_MIXMAX, DURATION_MS_PREC2(maxMixerDuration), PREC2|LEFT, "ms"); lcd_putsLeft(MENU_DEBUG_Y_RTOS, STR_FREESTACKMINB); - lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_RTOS+1, "[Menus]", HEADER_COLOR|SMLSIZE); + lcdDrawText(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_RTOS+1, "[Menus]", HEADER_COLOR|SMLSIZE); lcd_outdezAtt(MENU_DEBUG_COL1_OFS+30, MENU_DEBUG_Y_RTOS, menusStack.available(), LEFT); - lcd_putsAtt(MENU_DEBUG_COL1_OFS+60, MENU_DEBUG_Y_RTOS+1, "[Mix]", HEADER_COLOR|SMLSIZE); + lcdDrawText(MENU_DEBUG_COL1_OFS+60, MENU_DEBUG_Y_RTOS+1, "[Mix]", HEADER_COLOR|SMLSIZE); lcd_outdezAtt(MENU_DEBUG_COL1_OFS+90, MENU_DEBUG_Y_RTOS, mixerStack.available(), LEFT); - lcd_putsAtt(MENU_DEBUG_COL1_OFS+120, MENU_DEBUG_Y_RTOS+1, "[Audio]", HEADER_COLOR|SMLSIZE); + lcdDrawText(MENU_DEBUG_COL1_OFS+120, MENU_DEBUG_Y_RTOS+1, "[Audio]", HEADER_COLOR|SMLSIZE); lcd_outdezAtt(MENU_DEBUG_COL1_OFS+150, MENU_DEBUG_Y_RTOS, audioStack.available(), LEFT); lcd_putsCenter(7*FH+1, STR_MENUTORESET); diff --git a/radio/src/gui/Horus/view_text.cpp b/radio/src/gui/Horus/view_text.cpp index 83a70d033..f9a036441 100644 --- a/radio/src/gui/Horus/view_text.cpp +++ b/radio/src/gui/Horus/view_text.cpp @@ -148,7 +148,7 @@ void menuTextView(evt_t event) lcd_putsCenter(MENU_FOOTER_TOP, title, HEADER_COLOR); if (lines_count > NUM_BODY_LINES) { - lcdDrawScrollbar(LCD_W-5, 30, MENU_FOOTER_TOP-34, s_pgOfs, lines_count, NUM_BODY_LINES); + drawScrollbar(LCD_W-5, 30, MENU_FOOTER_TOP-34, s_pgOfs, lines_count, NUM_BODY_LINES); } } diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 7928d145c..867675ad8 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -41,7 +41,7 @@ void displayHeader(const char *header) { - // TODO ? lcd_putsAtt(COLUMN_HEADER_X, MENU_FOOTER_TOP, header, HEADER_COLOR); + // TODO ? lcdDrawText(COLUMN_HEADER_X, MENU_FOOTER_TOP, header, HEADER_COLOR); } void displayColumnHeader(const char * const *headers, uint8_t index) @@ -56,7 +56,7 @@ const char * STR_MONTHS[] = { "Jan", "Fev", "Mar", "Apr", "May", "Jun", "Jul", " #define DATETIME_LINE2 23 #define DATETIME_LEFT(s) (LCD_W+DATETIME_SEPARATOR_X+8-getTextWidth(s, SMLSIZE))/2 -void lcdDrawTopmenuDatetime() +void drawTopmenuDatetime() { lcdDrawSolidVerticalLine(DATETIME_SEPARATOR_X, 7, 31, TEXT_INVERTED_COLOR); @@ -64,10 +64,10 @@ void lcdDrawTopmenuDatetime() gettime(&t); char str[10]; sprintf(str, "%d %s", t.tm_mday, STR_MONTHS[t.tm_mon]); - lcd_putsAtt(DATETIME_LEFT(str), DATETIME_LINE1, str, SMLSIZE|TEXT_INVERTED_COLOR); + lcdDrawText(DATETIME_LEFT(str), DATETIME_LINE1, str, SMLSIZE|TEXT_INVERTED_COLOR); getTimerString(str, getValue(MIXSRC_TX_TIME)); - lcd_putsAtt(DATETIME_LEFT(str), DATETIME_LINE2, str, SMLSIZE|TEXT_INVERTED_COLOR); + lcdDrawText(DATETIME_LEFT(str), DATETIME_LINE2, str, SMLSIZE|TEXT_INVERTED_COLOR); } void drawStick(coord_t centrex, int16_t xval, int16_t yval) @@ -84,7 +84,7 @@ void drawStick(coord_t centrex, int16_t xval, int16_t yval) #undef MARKER_WIDTH } -void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) +void drawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) { if (attr) { lcdDrawSolidFilledRect(x-1, y+2, 13, 13, TEXT_INVERTED_BGCOLOR); @@ -104,7 +104,7 @@ void lcdDrawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) } } -void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) +void drawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { lcdDrawSolidVerticalLine(x, y, h, LINE_COLOR); coord_t yofs = (h*offset + count/2) / count; @@ -114,7 +114,7 @@ void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t lcdDrawSolidFilledRect(x-1, y + yofs, 3, yhgt, SCROLLBOX_COLOR); } -void displayProgressBar(const char *label) +void drawProgressBar(const char *label) { lcd_putsLeft(4*FH, label); lcdDrawRect(3, 6*FH+4, 204, 7); @@ -132,7 +132,7 @@ void updateProgressBar(int num, int den) #define MENU_ICONS_SPACING 31 -void drawMenuTemplate(const char * name, evt_t event) +void drawMenuTemplate(const char * name, evt_t event, uint16_t scrollbar_X) { // clear the screen lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); @@ -164,37 +164,41 @@ void drawMenuTemplate(const char * name, evt_t event) lcdDrawBitmapPattern(58+menuPageIndex*MENU_ICONS_SPACING, MENU_TITLE_TOP-9, LBM_CURRENT_DOT, MENU_TITLE_COLOR); } - lcdDrawTopmenuDatetime(); + drawTopmenuDatetime(); if (name) { // must be done at the end so that we can write something at the right of the menu title - lcd_putsAtt(MENUS_MARGIN_LEFT, MENU_TITLE_TOP+2, name, MENU_TITLE_COLOR); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_TITLE_TOP+2, name, MENU_TITLE_COLOR); + } + + if (scrollbar_X && linesCount > NUM_BODY_LINES) { + drawScrollbar(scrollbar_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, s_pgOfs, linesCount, NUM_BODY_LINES); } } select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, evt_t event) { lcd_putsColumnLeft(x, y, label); - if (values) lcd_putsiAtt(x, y, values, value-min, attr); if (attr) value = checkIncDec(event, value, min, max, (g_menuPos[0] == 0) ? EE_MODEL : EE_GENERAL); + if (values) lcdDrawTextAtIndex(x, y, values, value-min, attr); return value; } uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, const pm_char *label, LcdFlags attr, evt_t event ) { - lcdDrawCheckBox(x, y, value, attr); + drawCheckBox(x, y, value, attr); return selectMenuItem(x, y, label, NULL, value, 0, 1, attr, event); } int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, evt_t event) { lcd_putsColumnLeft(x, y, STR_SWITCH); - putsSwitches(x, y, value, attr); if (attr) CHECK_INCDEC_MODELSWITCH(event, value, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); + putsSwitches(x, y, value, attr); return value; } -void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) +void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) { const int width = 50; lcdDrawHorizontalLine(x, y+5, width, SOLID, TEXT_COLOR); @@ -261,8 +265,8 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m #else int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t max, LcdFlags attr, evt_t event) { + if (attr & INVERS) value = checkIncDec(event, value, min, max, EE_MODEL); lcd_outdezAtt(x, y, value, attr, "%"); - if (attr&INVERS) value = checkIncDec(event, value, min, max, EE_MODEL); return value; } #endif diff --git a/radio/src/gui/Taranis/gui.h b/radio/src/gui/Taranis/gui.h index a4fd2619a..c11c1662a 100644 --- a/radio/src/gui/Taranis/gui.h +++ b/radio/src/gui/Taranis/gui.h @@ -50,9 +50,9 @@ struct MenuItem { int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable=NULL); void displaySplash(); void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr); -void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); +void drawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); void displayMenuBar(const MenuItem *menu, int index); -void displayProgressBar(const char *label); +void drawProgressBar(const char *label); void updateProgressBar(int num, int den); void drawGauge(coord_t x, coord_t y, coord_t w, coord_t h, int32_t val, int32_t max); diff --git a/radio/src/gui/Taranis/lcd.cpp b/radio/src/gui/Taranis/lcd.cpp index 88c6ebeea..5d73bc284 100644 --- a/radio/src/gui/Taranis/lcd.cpp +++ b/radio/src/gui/Taranis/lcd.cpp @@ -218,7 +218,7 @@ void lcd_putc(coord_t x, coord_t y, const unsigned char c) lcdDrawChar(x, y, c, 0); } -void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) +void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) { const coord_t orig_x = x; const uint8_t orig_len = len; @@ -283,17 +283,17 @@ void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags void lcd_putsn(coord_t x, coord_t y, const pm_char * s, uint8_t len) { - lcd_putsnAtt(x, y, s, len, 0); + lcdDrawTextWithLen(x, y, s, len, 0); } -void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags flags) +void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags flags) { - lcd_putsnAtt(x, y, s, 255, flags); + lcdDrawTextWithLen(x, y, s, 255, flags); } void lcd_puts(coord_t x, coord_t y, const pm_char * s) { - lcd_putsAtt(x, y, s, 0); + lcdDrawText(x, y, s, 0); } void lcd_putsLeft(coord_t y, const pm_char * s) @@ -302,10 +302,10 @@ void lcd_putsLeft(coord_t y, const pm_char * s) } #if !defined(BOOT) -void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags) +void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags) { uint8_t length = pgm_read_byte(s++); - lcd_putsnAtt(x, y, s+length*idx, length, flags & ~(BSS|ZCHAR)); + lcdDrawTextWithLen(x, y, s+length*idx, length, flags & ~(BSS|ZCHAR)); } void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags flags) @@ -627,26 +627,26 @@ void putsVBat(coord_t x, coord_t y, LcdFlags att) void putsStrIdx(coord_t x, coord_t y, const pm_char *str, uint8_t idx, LcdFlags att) { - lcd_putsAtt(x, y, str, att & ~LEADING0); + lcdDrawText(x, y, str, att & ~LEADING0); lcd_outdezNAtt(lcdNextPos, y, idx, att|LEFT, 2); } void putsStickName(coord_t x, coord_t y, uint8_t idx, LcdFlags att) { uint8_t length = STR_VSRCRAW[0]; - lcd_putsnAtt(x, y, STR_VSRCRAW+2+length*(idx+1), length-1, att); + lcdDrawTextWithLen(x, y, STR_VSRCRAW+2+length*(idx+1), length-1, att); } void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) { if (idx == 0) { - lcd_putsiAtt(x, y, STR_VSRCRAW, 0, att); // TODO macro + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, 0, att); // TODO macro } else if (idx <= MIXSRC_LAST_INPUT) { lcdDrawChar(x+2, y+1, CHR_INPUT, TINSIZE); drawFilledRect(x, y, 7, 7); if (ZEXIST(g_model.inputNames[idx-MIXSRC_FIRST_INPUT])) - lcd_putsnAtt(x+8, y, g_model.inputNames[idx-MIXSRC_FIRST_INPUT], LEN_INPUT_NAME, ZCHAR|att); + lcdDrawTextWithLen(x+8, y, g_model.inputNames[idx-MIXSRC_FIRST_INPUT], LEN_INPUT_NAME, ZCHAR|att); else lcd_outdezNAtt(x+8, y, idx, att|LEADING0|LEFT, 2); } @@ -657,7 +657,7 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) if (qr.quot < MAX_SCRIPTS && qr.rem < scriptInputsOutputs[qr.quot].outputsCount) { lcdDrawChar(x+2, y+1, '1'+qr.quot, TINSIZE); drawFilledRect(x, y, 7, 7); - lcd_putsnAtt(x+8, y, scriptInputsOutputs[qr.quot].outputs[qr.rem].name, att & STREXPANDED ? 9 : 4, att); + lcdDrawTextWithLen(x+8, y, scriptInputsOutputs[qr.quot].outputs[qr.rem].name, att & STREXPANDED ? 9 : 4, att); } else #endif @@ -676,22 +676,22 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) lcdDrawChar(x, y, '\310', att); //pot symbol else lcdDrawChar(x, y, '\311', att); //slider symbol - lcd_putsnAtt(lcdNextPos, y, g_eeGeneral.anaNames[idx], LEN_ANA_NAME, ZCHAR|att); + lcdDrawTextWithLen(lcdNextPos, y, g_eeGeneral.anaNames[idx], LEN_ANA_NAME, ZCHAR|att); } else - lcd_putsiAtt(x, y, STR_VSRCRAW, idx+1, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx+1, att); } else if (idx >= MIXSRC_FIRST_SWITCH && idx <= MIXSRC_LAST_SWITCH) { idx = idx-MIXSRC_FIRST_SWITCH; if (ZEXIST(g_eeGeneral.switchNames[idx])) { lcdDrawChar(x, y, '\312', att); //switch symbol - lcd_putsnAtt(lcdNextPos, y, g_eeGeneral.switchNames[idx], LEN_SWITCH_NAME, ZCHAR|att); + lcdDrawTextWithLen(lcdNextPos, y, g_eeGeneral.switchNames[idx], LEN_SWITCH_NAME, ZCHAR|att); } else - lcd_putsiAtt(x, y, STR_VSRCRAW, idx+MIXSRC_FIRST_SWITCH-MIXSRC_Rud+1, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx+MIXSRC_FIRST_SWITCH-MIXSRC_Rud+1, att); } else if (idx < MIXSRC_SW1) - lcd_putsiAtt(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1, att); else if (idx <= MIXSRC_LAST_LOGICAL_SWITCH) putsSwitches(x, y, SWSRC_SW1+idx-MIXSRC_SW1, att); else if (idx < MIXSRC_CH1) @@ -700,26 +700,26 @@ void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) putsStrIdx(x, y, STR_CH, idx-MIXSRC_CH1+1, att); if (ZEXIST(g_model.limitData[idx-MIXSRC_CH1].name) && (att & STREXPANDED)) { lcdDrawChar(lcdLastPos, y, ' ', att|SMLSIZE); - lcd_putsnAtt(lcdLastPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att|SMLSIZE); + lcdDrawTextWithLen(lcdLastPos+3, y, g_model.limitData[idx-MIXSRC_CH1].name, LEN_CHANNEL_NAME, ZCHAR|att|SMLSIZE); } } else if (idx <= MIXSRC_LAST_GVAR) { putsStrIdx(x, y, STR_GV, idx-MIXSRC_GVAR1+1, att); } else if (idx < MIXSRC_FIRST_TELEM) { - lcd_putsiAtt(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1-NUM_LOGICAL_SWITCH-NUM_TRAINER-NUM_CHNOUT-MAX_GVARS, att); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, idx-MIXSRC_Rud+1-NUM_LOGICAL_SWITCH-NUM_TRAINER-NUM_CHNOUT-MAX_GVARS, att); } else { idx -= MIXSRC_FIRST_TELEM; div_t qr = div(idx, 3); - lcd_putsnAtt(x, y, g_model.telemetrySensors[qr.quot].label, TELEM_LABEL_LEN, ZCHAR|att); + lcdDrawTextWithLen(x, y, g_model.telemetrySensors[qr.quot].label, TELEM_LABEL_LEN, ZCHAR|att); if (qr.rem) lcdDrawChar(lcdLastPos, y, qr.rem==2 ? '+' : '-', att); } } void putsChnLetter(coord_t x, coord_t y, uint8_t idx, LcdFlags att) { - lcd_putsiAtt(x, y, STR_RETA123, idx-1, att); + lcdDrawTextAtIndex(x, y, STR_RETA123, idx-1, att); } void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) @@ -730,17 +730,17 @@ void putsModelName(coord_t x, coord_t y, char *name, uint8_t id, LcdFlags att) putsStrIdx(x, y, STR_MODEL, id+1, att|LEADING0); } else { - lcd_putsnAtt(x, y, name, sizeof(g_model.header.name), ZCHAR|att); + lcdDrawTextWithLen(x, y, name, sizeof(g_model.header.name), ZCHAR|att); } } void putsSwitches(coord_t x, coord_t y, int32_t idx, LcdFlags att) { if (idx == SWSRC_NONE) { - return lcd_putsiAtt(x, y, STR_VSWITCHES, 0, att); + return lcdDrawTextAtIndex(x, y, STR_VSWITCHES, 0, att); } else if (idx == SWSRC_OFF) { - return lcd_putsiAtt(x, y, STR_OFFON, 0, att); + return lcdDrawTextAtIndex(x, y, STR_OFFON, 0, att); } if (idx < 0) { @@ -751,7 +751,7 @@ void putsSwitches(coord_t x, coord_t y, int32_t idx, LcdFlags att) if (idx <= SWSRC_LAST_SWITCH) { div_t swinfo = switchInfo(idx); if (ZEXIST(g_eeGeneral.switchNames[swinfo.quot])) { - lcd_putsnAtt(x, y, g_eeGeneral.switchNames[swinfo.quot], LEN_SWITCH_NAME, ZCHAR|att); + lcdDrawTextWithLen(x, y, g_eeGeneral.switchNames[swinfo.quot], LEN_SWITCH_NAME, ZCHAR|att); } else { lcdDrawChar(x, y, 'S', att); @@ -765,29 +765,29 @@ void putsSwitches(coord_t x, coord_t y, int32_t idx, LcdFlags att) putsStrIdx(x, y, "S", swinfo.quot*10+swinfo.rem+11, att); } else if (idx <= SWSRC_LAST_TRIM) { - lcd_putsiAtt(x, y, STR_VSWITCHES, idx-SWSRC_FIRST_TRIM+1, att); + lcdDrawTextAtIndex(x, y, STR_VSWITCHES, idx-SWSRC_FIRST_TRIM+1, att); } else if (idx <= SWSRC_LAST_LOGICAL_SWITCH) { putsStrIdx(x, y, "L", idx-SWSRC_FIRST_LOGICAL_SWITCH+1, att); } else if (idx <= SWSRC_ONE) { - lcd_putsiAtt(x, y, STR_VSWITCHES, idx-SWSRC_ON+1+(2*NUM_STICKS), att); + lcdDrawTextAtIndex(x, y, STR_VSWITCHES, idx-SWSRC_ON+1+(2*NUM_STICKS), att); } else if (idx <= SWSRC_LAST_FLIGHT_MODE) { putsStrIdx(x, y, STR_FP, idx-SWSRC_FIRST_FLIGHT_MODE, att); } else if (idx == SWSRC_TELEMETRY_STREAMING) { - lcd_putsAtt(x, y, "Tele", att); + lcdDrawText(x, y, "Tele", att); } else { - lcd_putsnAtt(x, y, g_model.telemetrySensors[idx-SWSRC_FIRST_SENSOR].label, TELEM_LABEL_LEN, ZCHAR|att); + lcdDrawTextWithLen(x, y, g_model.telemetrySensors[idx-SWSRC_FIRST_SENSOR].label, TELEM_LABEL_LEN, ZCHAR|att); } } #if defined(FLIGHT_MODES) void putsFlightMode(coord_t x, coord_t y, int8_t idx, LcdFlags att) { - if (idx==0) { lcd_putsiAtt(x, y, STR_MMMINV, 0, att); return; } + if (idx==0) { lcdDrawTextAtIndex(x, y, STR_MMMINV, 0, att); return; } if (idx < 0) { lcdDrawChar(x-2, y, '!', att); idx = -idx; } if (att & CONDENSED) lcd_outdezNAtt(x+FW*1, y, idx-1, (att & ~CONDENSED), 1); @@ -811,7 +811,7 @@ void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att) break; case CURVE_REF_FUNC: - lcd_putsiAtt(x, y, STR_VCURVEFUNC, curve.value, att); + lcdDrawTextAtIndex(x, y, STR_VCURVEFUNC, curve.value, att); break; case CURVE_REF_CUSTOM: @@ -824,7 +824,7 @@ void putsCurveRef(coord_t x, coord_t y, CurveRef &curve, LcdFlags att) void putsCurve(coord_t x, coord_t y, int8_t idx, LcdFlags att) { if (idx == 0) { - return lcd_putsiAtt(x, y, STR_MMMINV, 0, att); + return lcdDrawTextAtIndex(x, y, STR_MMMINV, 0, att); } if (idx < 0) { @@ -839,7 +839,7 @@ void putsTimerMode(coord_t x, coord_t y, int32_t mode, LcdFlags att) { if (mode >= 0) { if (mode < TMRMODE_COUNT) - return lcd_putsiAtt(x, y, STR_VTMRMODES, mode, att); + return lcdDrawTextAtIndex(x, y, STR_VTMRMODES, mode, att); else mode -= (TMRMODE_COUNT-1); } @@ -853,7 +853,7 @@ void putsTrimMode(coord_t x, coord_t y, uint8_t phase, uint8_t idx, LcdFlags att unsigned int p = mode >> 1; if (mode == TRIM_MODE_NONE) { - lcd_putsAtt(x, y, "--", att); + lcdDrawText(x, y, "--", att); } else { if (mode % 2 == 0) @@ -880,7 +880,7 @@ void putsValueWithUnit(coord_t x, coord_t y, lcdint_t val, uint8_t unit, LcdFlag // convertUnit(val, unit); lcd_outdezAtt(x, y, val, att & (~NO_UNIT)); if (!(att & NO_UNIT) && unit != UNIT_RAW) { - lcd_putsiAtt(lcdLastPos/*+1*/, y, STR_VTELEMUNIT, unit, 0); + lcdDrawTextAtIndex(lcdLastPos/*+1*/, y, STR_VTELEMUNIT, unit, 0); } } diff --git a/radio/src/gui/Taranis/lcd.h b/radio/src/gui/Taranis/lcd.h index 003f32c5e..3138ea5b9 100644 --- a/radio/src/gui/Taranis/lcd.h +++ b/radio/src/gui/Taranis/lcd.h @@ -74,11 +74,7 @@ /* lcd puts flags */ /* no 0x80 here because of "GV"1 which is aligned LEFT */ /* no 0x10 here because of "MODEL"01 which uses LEADING0 */ -#if defined(CPUARM) - #define BSS 0x00 -#else - #define BSS 0x20 -#endif +#define BSS 0x00 #define ZCHAR 0x80 /* lcd outdez flags */ @@ -149,9 +145,9 @@ typedef const char pm_char; void lcd_putc(coord_t x, coord_t y, const unsigned char c); void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags mode); -void lcd_putsAtt(coord_t x, coord_t y, const pm_char * s, LcdFlags mode); -void lcd_putsiAtt(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags mode); -void lcd_putsnAtt(coord_t x, coord_t y, const pm_char * s,unsigned char len, LcdFlags mode); +void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags mode); +void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags mode); +void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s,unsigned char len, LcdFlags mode); void lcd_puts(coord_t x, coord_t y, const pm_char * s); void lcd_putsn(coord_t x, coord_t y, const pm_char * s, unsigned char len); void lcd_putsLeft(coord_t y, const pm_char * s); diff --git a/radio/src/gui/Taranis/menu_general_calib.cpp b/radio/src/gui/Taranis/menu_general_calib.cpp index 52b8876bd..1f0216e53 100644 --- a/radio/src/gui/Taranis/menu_general_calib.cpp +++ b/radio/src/gui/Taranis/menu_general_calib.cpp @@ -119,7 +119,7 @@ void menuCommonCalib(uint8_t event) case 1: // SET MIDPOINT - lcd_putsAtt(0*FW, MENU_HEADER_HEIGHT+FH, STR_SETMIDPOINT, INVERS); + lcdDrawText(0*FW, MENU_HEADER_HEIGHT+FH, STR_SETMIDPOINT, INVERS); lcd_putsLeft(MENU_HEADER_HEIGHT+2*FH, STR_MENUWHENDONE); for (uint8_t i=0; i> idx; potType = selectMenuItem(HW_SETTINGS_COLUMN+5*FW, y, "", STR_SLIDERTYPES, potType, SLIDER_NONE, SLIDER_WITH_DETENT, m_posHorz == 1 ? attr : 0, event); g_eeGeneral.slidersConfig &= ~mask; @@ -162,11 +162,11 @@ void menuGeneralHardware(uint8_t event) int idx = k - ITEM_SETUP_HW_POT1; uint8_t shift = (2*idx); uint8_t mask = (0x03 << shift); - lcd_putsiAtt(INDENT_WIDTH, y, STR_VSRCRAW, NUM_STICKS+idx+1, m_posHorz < 0 ? attr : 0); + lcdDrawTextAtIndex(INDENT_WIDTH, y, STR_VSRCRAW, NUM_STICKS+idx+1, m_posHorz < 0 ? attr : 0); if (ZEXIST(g_eeGeneral.anaNames[NUM_STICKS+idx]) || (attr && m_posHorz == 0)) editName(HW_SETTINGS_COLUMN, y, g_eeGeneral.anaNames[NUM_STICKS+idx], LEN_ANA_NAME, event, attr && m_posHorz == 0); else - lcd_putsiAtt(HW_SETTINGS_COLUMN, y, STR_MMMINV, 0, 0); + lcdDrawTextAtIndex(HW_SETTINGS_COLUMN, y, STR_MMMINV, 0, 0); uint8_t potType = (g_eeGeneral.potsConfig & mask) >> shift; potType = selectMenuItem(HW_SETTINGS_COLUMN+5*FW, y, "", STR_POTTYPES, potType, POT_NONE, POT_WITHOUT_DETENT, m_posHorz == 1 ? attr : 0, event); g_eeGeneral.potsConfig &= ~mask; @@ -199,11 +199,11 @@ void menuGeneralHardware(uint8_t event) { int index = k-ITEM_SETUP_HW_SA; int config = SWITCH_CONFIG(index); - lcd_putsiAtt(INDENT_WIDTH, y, STR_VSRCRAW, MIXSRC_FIRST_SWITCH-MIXSRC_Rud+index+1, m_posHorz < 0 ? attr : 0); + lcdDrawTextAtIndex(INDENT_WIDTH, y, STR_VSRCRAW, MIXSRC_FIRST_SWITCH-MIXSRC_Rud+index+1, m_posHorz < 0 ? attr : 0); if (ZEXIST(g_eeGeneral.switchNames[index]) || (attr && m_posHorz == 0)) editName(HW_SETTINGS_COLUMN, y, g_eeGeneral.switchNames[index], LEN_SWITCH_NAME, event, m_posHorz == 0 ? attr : 0); else - lcd_putsiAtt(HW_SETTINGS_COLUMN, y, STR_MMMINV, 0, 0); + lcdDrawTextAtIndex(HW_SETTINGS_COLUMN, y, STR_MMMINV, 0, 0); config = selectMenuItem(HW_SETTINGS_COLUMN+5*FW, y, "", STR_SWTYPES, config, SWITCH_NONE, SWITCH_TYPE_MAX(index), m_posHorz == 1 ? attr : 0, event); if (attr && checkIncDec_Ret) { swconfig_t mask = (swconfig_t)0x03 << (2*index); diff --git a/radio/src/gui/Taranis/menu_general_sdmanager.cpp b/radio/src/gui/Taranis/menu_general_sdmanager.cpp index ab12ac9fd..d5727fa52 100644 --- a/radio/src/gui/Taranis/menu_general_sdmanager.cpp +++ b/radio/src/gui/Taranis/menu_general_sdmanager.cpp @@ -99,7 +99,7 @@ void flashBootloader(const char * filename) UINT count; lcdClear(); - displayProgressBar(STR_WRITING); + drawProgressBar(STR_WRITING); static uint8_t unlocked = 0; if (!unlocked) { @@ -138,7 +138,7 @@ void flashSportDevice(ModuleIndex module, const char *filename) watchdogSetTimeout(60*60*100/*1h*/); lcdClear(); - displayProgressBar(STR_WRITING); + drawProgressBar(STR_WRITING); sportFirmwareUpdate(module, filename); @@ -465,7 +465,7 @@ void menuGeneralSdManager(uint8_t _event) } } else { - lcd_putsAtt(lcdNextPos, y, reusableBuffer.sdmanager.lines[i], attr); + lcdDrawText(lcdNextPos, y, reusableBuffer.sdmanager.lines[i], attr); } if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[i])) { lcdDrawChar(lcdNextPos, y, ']', s_editMode == EDIT_MODIFY_STRING ? 0 : attr); } } diff --git a/radio/src/gui/Taranis/menu_general_setup.cpp b/radio/src/gui/Taranis/menu_general_setup.cpp index 85b07064f..ea645d275 100644 --- a/radio/src/gui/Taranis/menu_general_setup.cpp +++ b/radio/src/gui/Taranis/menu_general_setup.cpp @@ -46,7 +46,7 @@ const pm_uchar sticks[] PROGMEM = { #define SLIDER_5POS(y, value, label, event, attr) { \ int8_t tmp = value; \ - displaySlider(RADIO_SETUP_2ND_COLUMN, y, 2+tmp, 4, attr); \ + drawSlider(RADIO_SETUP_2ND_COLUMN, y, 2+tmp, 4, attr); \ value = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, label, NULL, tmp, -2, +2, attr, event); \ } @@ -68,11 +68,11 @@ enum menuGeneralSetupItems { ITEM_SETUP_SPEAKER_PITCH, ITEM_SETUP_WAV_VOLUME, ITEM_SETUP_BACKGROUND_VOLUME, - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_LABEL) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_VOLUME) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_PITCH) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_RANGE) - CASE_VARIO_CPUARM(ITEM_SETUP_VARIO_REPEAT) + CASE_VARIO(ITEM_SETUP_VARIO_LABEL) + CASE_VARIO(ITEM_SETUP_VARIO_VOLUME) + CASE_VARIO(ITEM_SETUP_VARIO_PITCH) + CASE_VARIO(ITEM_SETUP_VARIO_RANGE) + CASE_VARIO(ITEM_SETUP_VARIO_REPEAT) CASE_HAPTIC(ITEM_SETUP_HAPTIC_LABEL) CASE_HAPTIC(ITEM_SETUP_HAPTIC_MODE) CASE_HAPTIC(ITEM_SETUP_HAPTIC_LENGTH) @@ -114,7 +114,7 @@ void menuGeneralSetup(uint8_t event) if ((m_posVert==ITEM_SETUP_DATE || m_posVert==ITEM_SETUP_TIME) && (s_editMode>0) && - (event==EVT_KEY_FIRST(KEY_ENTER) || event==EVT_KEY_FIRST(KEY_EXIT) || IS_ROTARY_BREAK(event) || IS_ROTARY_LONG(event))) { + (event==EVT_KEY_FIRST(KEY_ENTER) || event==EVT_KEY_BREAK(KEY_ENTER) || event==EVT_KEY_LONG(KEY_ENTER) || event==EVT_KEY_FIRST(KEY_EXIT))) { // set the date and time into RTC chip rtcSetTime(&t); } @@ -128,7 +128,7 @@ void menuGeneralSetup(uint8_t event) } #endif - MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, { 2, 2, 1, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO_CPUARM(LABEL(VARIO)) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_VARIO_CPUARM(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) 0, LABEL(ALARMS), 0, 0, 0, 0, IF_ROTARY_ENCODERS(0) LABEL(BACKLIGHT), 0, 0, 0, CASE_REVPLUS(0) CASE_PWM_BACKLIGHT(0) CASE_PWM_BACKLIGHT(0) 0, CASE_SPLASH_PARAM(0) CASE_GPS(LABEL(GPS)) CASE_GPS(0) CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, IF_FAI_CHOICE(0) CASE_MAVLINK(0) 0, 0, LABEL(TX_MODE), 0, 1/*to force edit mode*/ }); + MENU(STR_MENURADIOSETUP, menuTabGeneral, e_Setup, ITEM_SETUP_MAX, { 2, 2, 1, LABEL(SOUND), 0, 0, 0, 0, 0, 0, 0, CASE_VARIO(LABEL(VARIO)) CASE_VARIO(0) CASE_VARIO(0) CASE_VARIO(0) CASE_VARIO(0) CASE_HAPTIC(LABEL(HAPTIC)) CASE_HAPTIC(0) CASE_HAPTIC(0) CASE_HAPTIC(0) 0, LABEL(ALARMS), 0, 0, 0, 0, IF_ROTARY_ENCODERS(0) LABEL(BACKLIGHT), 0, 0, 0, CASE_REVPLUS(0) CASE_PWM_BACKLIGHT(0) CASE_PWM_BACKLIGHT(0) 0, CASE_SPLASH_PARAM(0) CASE_GPS(LABEL(GPS)) CASE_GPS(0) CASE_GPS(0) CASE_GPS(0) CASE_PXX(0) 0, 0, IF_FAI_CHOICE(0) CASE_MAVLINK(0) 0, 0, LABEL(TX_MODE), 0, 1/*to force edit mode*/ }); int sub = m_posVert; @@ -225,7 +225,7 @@ void menuGeneralSetup(uint8_t event) { lcd_putsLeft(y, STR_SPEAKER_VOLUME); uint8_t b = g_eeGeneral.speakerVolume+VOLUME_LEVEL_DEF; - displaySlider(RADIO_SETUP_2ND_COLUMN, y, b, VOLUME_LEVEL_MAX, attr); + drawSlider(RADIO_SETUP_2ND_COLUMN, y, b, VOLUME_LEVEL_MAX, attr); if (attr) { CHECK_INCDEC_GENVAR(event, b, 0, VOLUME_LEVEL_MAX); if (checkIncDec_Ret) { @@ -253,7 +253,7 @@ void menuGeneralSetup(uint8_t event) lcd_putsLeft( y, STR_SPKRPITCH); lcdDrawChar(RADIO_SETUP_2ND_COLUMN, y, '+', attr); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN+FW, y, g_eeGeneral.speakerPitch*15, attr|LEFT); - lcd_putsAtt(lcdLastPos, y, "Hz", attr); + lcdDrawText(lcdLastPos, y, "Hz", attr); if (attr) { CHECK_INCDEC_GENVAR(event, g_eeGeneral.speakerPitch, 0, 20); } @@ -269,19 +269,19 @@ void menuGeneralSetup(uint8_t event) case ITEM_SETUP_VARIO_PITCH: lcd_putsLeft(y, STR_PITCH_AT_ZERO); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10), attr|LEFT); - lcd_putsAtt(lcdLastPos, y, "Hz", attr); + lcdDrawText(lcdLastPos, y, "Hz", attr); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioPitch, -40, 40); break; case ITEM_SETUP_VARIO_RANGE: lcd_putsLeft(y, STR_PITCH_AT_MAX); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10)+VARIO_FREQUENCY_RANGE+(g_eeGeneral.varioRange*10), attr|LEFT); - lcd_putsAtt(lcdLastPos, y, "Hz", attr); + lcdDrawText(lcdLastPos, y, "Hz", attr); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRange, -80, 80); break; case ITEM_SETUP_VARIO_REPEAT: lcd_putsLeft(y, STR_REPEAT_AT_ZERO); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_REPEAT_ZERO+(g_eeGeneral.varioRepeat*10), attr|LEFT); - lcd_putsAtt(lcdLastPos, y, STR_MS, attr); + lcdDrawText(lcdLastPos, y, STR_MS, attr); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRepeat, -30, 50); break; #endif @@ -376,7 +376,7 @@ void menuGeneralSetup(uint8_t event) #if defined(REVPLUS) case ITEM_SETUP_BACKLIGHT_COLOR: lcd_putsLeft(y, STR_BLCOLOR); - displaySlider(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.backlightColor, 20, attr); + drawSlider(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.backlightColor, 20, attr); if (attr) g_eeGeneral.backlightColor = checkIncDec(event, g_eeGeneral.backlightColor, 0, 20, EE_GENERAL | NO_INCDEC_MARKS); break; #endif @@ -390,7 +390,7 @@ void menuGeneralSetup(uint8_t event) lcd_putc(lcdLastPos, y, 's'); } else { - lcd_putsiAtt(RADIO_SETUP_2ND_COLUMN, y, STR_MMMINV, 0, attr); // TODO define + lcdDrawTextAtIndex(RADIO_SETUP_2ND_COLUMN, y, STR_MMMINV, 0, attr); // TODO define } if (attr) g_eeGeneral.splashMode = -checkIncDecGen(event, -g_eeGeneral.splashMode, -3, 4); break; @@ -425,7 +425,7 @@ void menuGeneralSetup(uint8_t event) case ITEM_SETUP_LANGUAGE: lcd_putsLeft(y, STR_VOICELANG); - lcd_putsAtt(RADIO_SETUP_2ND_COLUMN, y, currentLanguagePack->name, attr); + lcdDrawText(RADIO_SETUP_2ND_COLUMN, y, currentLanguagePack->name, attr); if (attr) { currentLanguagePackIdx = checkIncDec(event, currentLanguagePackIdx, 0, DIM(languagePacks)-2, EE_GENERAL); if (checkIncDec_Ret) { @@ -460,7 +460,7 @@ void menuGeneralSetup(uint8_t event) case ITEM_SETUP_SWITCHES_DELAY: lcd_putsLeft(y, STR_SWITCHES_DELAY); lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, 10*SWITCHES_DELAY(), attr|LEFT); - lcd_putsAtt(lcdLastPos, y, STR_MS, attr); + lcdDrawText(lcdLastPos, y, STR_MS, attr); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.switchesDelay, -15, 100-15); break; diff --git a/radio/src/gui/Taranis/menu_general_trainer.cpp b/radio/src/gui/Taranis/menu_general_trainer.cpp index 12984b398..81e0d0ae2 100644 --- a/radio/src/gui/Taranis/menu_general_trainer.cpp +++ b/radio/src/gui/Taranis/menu_general_trainer.cpp @@ -70,7 +70,7 @@ void menuGeneralTrainer(uint8_t event) switch(j) { case 0: - lcd_putsiAtt(4*FW, y, STR_TRNMODE, td->mode, attr); + lcdDrawTextAtIndex(4*FW, y, STR_TRNMODE, td->mode, attr); if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->mode, 0, 2); break; @@ -80,7 +80,7 @@ void menuGeneralTrainer(uint8_t event) break; case 2: - lcd_putsiAtt(12*FW, y, STR_TRNCHN, td->srcChn, attr); + lcdDrawTextAtIndex(12*FW, y, STR_TRNCHN, td->srcChn, attr); if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->srcChn, 0, 3); break; } @@ -95,7 +95,7 @@ void menuGeneralTrainer(uint8_t event) attr = (sub==6) ? INVERS : 0; if (attr) s_editMode = 0; - lcd_putsAtt(0*FW, MENU_HEADER_HEIGHT+1+6*FH, STR_CAL, attr); + lcdDrawText(0*FW, MENU_HEADER_HEIGHT+1+6*FH, STR_CAL, attr); for (int i=0; i<4; i++) { uint8_t x = (i*TRAINER_CALIB_POS+16)*FW/2; #if defined (PPM_UNIT_PERCENT_PREC1) diff --git a/radio/src/gui/Taranis/menu_general_version.cpp b/radio/src/gui/Taranis/menu_general_version.cpp index 3151f8761..b321b9962 100644 --- a/radio/src/gui/Taranis/menu_general_version.cpp +++ b/radio/src/gui/Taranis/menu_general_version.cpp @@ -43,7 +43,7 @@ void backupEeprom() FIL file; lcdClear(); - displayProgressBar(STR_WRITING); + drawProgressBar(STR_WRITING); // reset unexpectedShutdown to prevent warning when user restores EEPROM backup g_eeGeneral.unexpectedShutdown = 0; diff --git a/radio/src/gui/Taranis/menu_model.cpp b/radio/src/gui/Taranis/menu_model.cpp index ee095d5ce..7955c4913 100644 --- a/radio/src/gui/Taranis/menu_model.cpp +++ b/radio/src/gui/Taranis/menu_model.cpp @@ -148,7 +148,7 @@ void editName(coord_t x, coord_t y, char *name, uint8_t size, uint8_t event, uin mode = FIXEDWIDTH; } - lcd_putsnAtt(x, y, name, size, attr | mode); + lcdDrawTextWithLen(x, y, name, size, attr | mode); coord_t backupNextPos = lcdNextPos; if (active) { @@ -157,8 +157,7 @@ void editName(coord_t x, coord_t y, char *name, uint8_t size, uint8_t event, uin int8_t c = name[cur]; int8_t v = c; - if (IS_ROTARY_RIGHT(event) || IS_ROTARY_LEFT(event) || event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_FIRST(KEY_UP) - || event==EVT_KEY_REPT(KEY_DOWN) || event==EVT_KEY_REPT(KEY_UP)) { + if (event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_FIRST(KEY_UP) || event==EVT_KEY_REPT(KEY_DOWN) || event==EVT_KEY_REPT(KEY_UP)) { if (attr == ZCHAR) { v = checkIncDec(event, abs(v), 0, ZCHAR_MAX, 0); if (c <= 0) v = -v; diff --git a/radio/src/gui/Taranis/menu_model_curves.cpp b/radio/src/gui/Taranis/menu_model_curves.cpp index 89e5cf5d2..7ae939673 100644 --- a/radio/src/gui/Taranis/menu_model_curves.cpp +++ b/radio/src/gui/Taranis/menu_model_curves.cpp @@ -154,7 +154,7 @@ void menuModelCurveOne(uint8_t event) uint8_t attr = (m_posVert==1 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); lcd_putsLeft(3*FH+1, STR_TYPE); - lcd_putsiAtt(INDENT_WIDTH, 4*FH+1, STR_CURVE_TYPES, crv.type, attr); + lcdDrawTextAtIndex(INDENT_WIDTH, 4*FH+1, STR_CURVE_TYPES, crv.type, attr); if (attr) { uint8_t newType = checkIncDecModelZero(event, crv.type, CURVE_TYPE_LAST); if (newType != crv.type) { @@ -172,7 +172,7 @@ void menuModelCurveOne(uint8_t event) attr = (m_posVert==2 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); lcd_putsLeft(5*FH+1, STR_COUNT); lcd_outdezAtt(INDENT_WIDTH, 6*FH+1, 5+crv.points, LEFT|attr); - lcd_putsAtt(lcdLastPos, 6*FH+1, STR_PTS, attr); + lcdDrawText(lcdLastPos, 6*FH+1, STR_PTS, attr); if (attr) { int8_t count = checkIncDecModel(event, crv.points, -3, 12); // 2pts - 17pts if (checkIncDec_Ret) { @@ -260,7 +260,7 @@ void menuModelCurveOne(uint8_t event) void editCurveRef(coord_t x, coord_t y, CurveRef & curve, uint8_t event, uint8_t attr) { - lcd_putsiAtt(x, y, "\004DiffExpoFuncCstm", curve.type, m_posHorz==0 ? attr : 0); + lcdDrawTextAtIndex(x, y, "\004DiffExpoFuncCstm", curve.type, m_posHorz==0 ? attr : 0); if (attr && m_posHorz==0) { CHECK_INCDEC_MODELVAR_ZERO(event, curve.type, CURVE_REF_CUSTOM); if (checkIncDec_Ret) curve.value = 0; @@ -271,7 +271,7 @@ void editCurveRef(coord_t x, coord_t y, CurveRef & curve, uint8_t event, uint8_t curve.value = GVAR_MENU_ITEM(x+5*FW, y, curve.value, -100, 100, m_posHorz==1 ? LEFT|attr : LEFT, 0, event); break; case CURVE_REF_FUNC: - lcd_putsiAtt(x+5*FW, y, STR_VCURVEFUNC, curve.value, m_posHorz==1 ? attr : 0); + lcdDrawTextAtIndex(x+5*FW, y, STR_VCURVEFUNC, curve.value, m_posHorz==1 ? attr : 0); if (attr && m_posHorz==1) CHECK_INCDEC_MODELVAR_ZERO(event, curve.value, CURVE_BASE-1); break; case CURVE_REF_CUSTOM: @@ -313,7 +313,7 @@ void menuModelCurvesAll(uint8_t event) editName(4*FW, y, g_model.curveNames[k], sizeof(g_model.curveNames[k]), 0, 0); CurveInfo & crv = g_model.curves[k]; lcd_outdezAtt(11*FW, y, 5+crv.points, LEFT); - lcd_putsAtt(lcdLastPos, y, STR_PTS, 0); + lcdDrawText(lcdLastPos, y, STR_PTS, 0); } } diff --git a/radio/src/gui/Taranis/menu_model_custom_functions.cpp b/radio/src/gui/Taranis/menu_model_custom_functions.cpp index a7f3ef762..18fb74f9e 100644 --- a/radio/src/gui/Taranis/menu_model_custom_functions.cpp +++ b/radio/src/gui/Taranis/menu_model_custom_functions.cpp @@ -196,7 +196,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu case 1: if (CFN_SWITCH(cfn)) { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_2ND_COLUMN, y, STR_VFSWFUNC, func, attr); if (active) { func = CFN_FUNC(cfn) = checkIncDec(event, CFN_FUNC(cfn), 0, FUNC_MAX-1, eeFlags, isAssignableFunctionAvailable); if (checkIncDec_Ret) CFN_RESET(cfn); @@ -254,11 +254,11 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu val_max = FUNC_RESET_PARAM_FIRST_TELEM+lastUsedTelemetryIndex(); int param = CFN_PARAM(cfn); if (param < FUNC_RESET_PARAM_FIRST_TELEM) { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, param, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_VFSWRESET, param, attr); } else { TelemetrySensor * sensor = & g_model.telemetrySensors[param-FUNC_RESET_PARAM_FIRST_TELEM]; - lcd_putsnAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, sensor->label, TELEM_LABEL_LEN, attr|ZCHAR); + lcdDrawTextWithLen(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, sensor->label, TELEM_LABEL_LEN, attr|ZCHAR); } if (active) INCDEC_ENABLE_CHECK(functionsContext == &globalFunctionsContext ? isSourceAvailableInGlobalResetSpecialFunction : isSourceAvailableInResetSpecialFunction); } @@ -270,7 +270,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu #endif else if (func >= FUNC_SET_FAILSAFE && func <= FUNC_BIND) { val_max = NUM_MODULES-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "\004Int.Ext.", CFN_PARAM(cfn), attr); } else if (func == FUNC_SET_TIMER) { val_max = 59*60+59; @@ -278,7 +278,7 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu } else if (func == FUNC_PLAY_SOUND) { val_max = AU_FRSKY_LAST-AU_FRSKY_FIRST-1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_FUNCSOUNDS, val_displayed, attr); } #if defined(HAPTIC) else if (func == FUNC_HAPTIC) { @@ -290,9 +290,9 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT) { coord_t x = MODEL_CUSTOM_FUNC_3RD_COLUMN; if (ZEXIST(cfn->play.name)) - lcd_putsnAtt(x, y, cfn->play.name, sizeof(cfn->play.name), attr); + lcdDrawTextWithLen(x, y, cfn->play.name, sizeof(cfn->play.name), attr); else - lcd_putsiAtt(x, y, STR_VCSWFUNC, 0, attr); + lcdDrawTextAtIndex(x, y, STR_VCSWFUNC, 0, attr); if (active && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; char directory[256]; @@ -336,12 +336,12 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu lcd_putc(lcdLastPos, y, 's'); } else { - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); } } #if defined(REVPLUS) else if (func == FUNC_BACKLIGHT) { - displaySlider(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, CFN_PARAM(cfn), 100, attr); + drawSlider(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, CFN_PARAM(cfn), 100, attr); if (active) INCDEC_SET_FLAG(eeFlags | NO_INCDEC_MARKS); val_min = 0; val_max = 100; @@ -371,13 +371,13 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu #if 0 // TODO 2.2.X val_min = -100; val_max = +100; if (val_displayed < 0) - lcd_putsAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "-=", attr); + lcdDrawText(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "-=", attr); else - lcd_putsAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "+=", attr); + lcdDrawText(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, "+=", attr); lcd_outdezAtt(lcdNextPos, y, abs(val_displayed), attr|LEFT); #endif val_max = 1; - lcd_putsiAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); + lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, PSTR("\003-=1+=1"), val_displayed, attr); break; } } @@ -412,11 +412,11 @@ void menuCustomFunctions(uint8_t event, CustomFunctionData * functions, CustomFu } else if (HAS_REPEAT_PARAM(func)) { if (CFN_PLAY_REPEAT(cfn) == 0) { - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); + lcdDrawText(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); } else if (CFN_PLAY_REPEAT(cfn) == CFN_PLAY_REPEAT_NOSTART) { lcdDrawChar(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, '!', attr); - lcd_putsAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); + lcdDrawText(MODEL_CUSTOM_FUNC_4TH_COLUMN+2, y, "1x", attr); } else { lcd_outdezAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+2+FW, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr); diff --git a/radio/src/gui/Taranis/menu_model_custom_scripts.cpp b/radio/src/gui/Taranis/menu_model_custom_scripts.cpp index 216d84b9b..f2ce148b1 100644 --- a/radio/src/gui/Taranis/menu_model_custom_scripts.cpp +++ b/radio/src/gui/Taranis/menu_model_custom_scripts.cpp @@ -83,9 +83,9 @@ void menuModelCustomScriptOne(uint8_t event) if (i == ITEM_MODEL_CUSTOMSCRIPT_FILE) { lcd_putsLeft(y, STR_SCRIPT); if (ZEXIST(sd.file)) - lcd_putsnAtt(SCRIPT_ONE_2ND_COLUMN_POS, y, sd.file, sizeof(sd.file), attr); + lcdDrawTextWithLen(SCRIPT_ONE_2ND_COLUMN_POS, y, sd.file, sizeof(sd.file), attr); else - lcd_putsiAtt(SCRIPT_ONE_2ND_COLUMN_POS, y, STR_VCSWFUNC, 0, attr); + lcdDrawTextAtIndex(SCRIPT_ONE_2ND_COLUMN_POS, y, STR_VCSWFUNC, 0, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER) && !READ_ONLY()) { s_editMode = 0; if (sdListFiles(SCRIPTS_MIXES_PATH, SCRIPTS_EXT, sizeof(sd.file), sd.file, LIST_NONE_SD_FILE)) { @@ -106,7 +106,7 @@ void menuModelCustomScriptOne(uint8_t event) } else if (i <= ITEM_MODEL_CUSTOMSCRIPT_PARAMS_LABEL+scriptInputsOutputs[s_currIdx].inputsCount) { int inputIdx = i-ITEM_MODEL_CUSTOMSCRIPT_PARAMS_LABEL-1; - lcd_putsnAtt(INDENT_WIDTH, y, scriptInputsOutputs[s_currIdx].inputs[inputIdx].name, 10, 0); + lcdDrawTextWithLen(INDENT_WIDTH, y, scriptInputsOutputs[s_currIdx].inputs[inputIdx].name, 10, 0); if (scriptInputsOutputs[s_currIdx].inputs[inputIdx].type == 0) { lcd_outdezAtt(SCRIPT_ONE_2ND_COLUMN_POS, y, g_model.scriptsData[s_currIdx].inputs[inputIdx]+scriptInputsOutputs[s_currIdx].inputs[inputIdx].def, attr|LEFT); if (attr) { @@ -159,7 +159,7 @@ void menuModelCustomScripts(uint8_t event) // LUA script if (ZEXIST(sd.file)) { - lcd_putsnAtt(5*FW, y, sd.file, sizeof(sd.file), 0); + lcdDrawTextWithLen(5*FW, y, sd.file, sizeof(sd.file), 0); switch (scriptInternalData[scriptIndex].state) { case SCRIPT_SYNTAX_ERROR: lcd_puts(30*FW+2, y, "(error)"); @@ -175,10 +175,10 @@ void menuModelCustomScripts(uint8_t event) scriptIndex++; } else { - lcd_putsiAtt(5*FW, y, STR_VCSWFUNC, 0, 0); + lcdDrawTextAtIndex(5*FW, y, STR_VCSWFUNC, 0, 0); } // Script name - lcd_putsnAtt(16*FW, y, sd.name, sizeof(sd.name), ZCHAR); + lcdDrawTextWithLen(16*FW, y, sd.name, sizeof(sd.name), ZCHAR); } } diff --git a/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp b/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp index 16584f3d0..7750d6ee2 100644 --- a/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/Taranis/menu_model_inputs_mixes.cpp @@ -389,7 +389,7 @@ void menuModelExpoOne(uint8_t event) uint8_t not_stick = (ed->srcRaw > MIXSRC_Ail); int8_t carryTrim = -ed->carryTrim; lcd_putsLeft(y, STR_TRIM); - lcd_putsiAtt(EXPO_ONE_2ND_COLUMN, y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); + lcdDrawTextAtIndex(EXPO_ONE_2ND_COLUMN, y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); if (attr) ed->carryTrim = -checkIncDecModel(event, carryTrim, not_stick ? TRIM_ON : -TRIM_OFF, -TRIM_AIL); break; } @@ -576,7 +576,7 @@ void menuModelMixOne(uint8_t event) if (md2->mixWarn) lcd_outdezAtt(COLUMN_X+MIXES_2ND_COLUMN, y, md2->mixWarn, attr|LEFT); else - lcd_putsAtt(COLUMN_X+MIXES_2ND_COLUMN, y, STR_OFF, attr); + lcdDrawText(COLUMN_X+MIXES_2ND_COLUMN, y, STR_OFF, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, md2->mixWarn, 3); break; case MIX_FIELD_MLTPX: @@ -652,7 +652,7 @@ void displayHeaderChannelName(uint8_t ch) { uint8_t len = zlen(g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name)); if (len) { - lcd_putsnAtt(80, 1, g_model.limitData[ch].name, len, ZCHAR|SMLSIZE); + lcdDrawTextWithLen(80, 1, g_model.limitData[ch].name, len, ZCHAR|SMLSIZE); } } @@ -668,7 +668,7 @@ void displayMixInfos(coord_t y, MixData *md) void displayMixLine(coord_t y, MixData *md) { if (md->name[0]) - lcd_putsnAtt(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); + lcdDrawTextWithLen(EXPO_LINE_NAME_POS, y, md->name, sizeof(md->name), ZCHAR); if (!md->flightModes || ((md->curve.value || md->swtch) && ((get_tmr10ms() / 200) & 1))) displayMixInfos(y, md); else @@ -695,7 +695,7 @@ void displayExpoLine(coord_t y, ExpoData *ed) displayFlightModes(EXPO_LINE_FM_POS, y, ed->flightModes); if (ed->name[0]) { - lcd_putsnAtt(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); + lcdDrawTextWithLen(EXPO_LINE_NAME_POS, y, ed->name, sizeof(ed->name), ZCHAR); } } @@ -799,29 +799,29 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) killEvents(event); } break; - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_copyMode) { uint8_t key = (event & 0x1f); - uint8_t next_ofs = (key==KEY_MOVE_UP ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); + uint8_t next_ofs = (key==KEY_UP ? s_copyTgtOfs - 1 : s_copyTgtOfs + 1); if (s_copyTgtOfs==0 && s_copyMode==COPY_MODE) { // insert a mix on the same channel (just above / just below) if (reachExpoMixCountLimit(expo)) break; copyExpoMix(expo, s_currIdx); - if (key==KEY_MOVE_DOWN) s_currIdx++; + if (key==KEY_DOWN) s_currIdx++; else if (sub-s_pgOfs >= 6) s_pgOfs++; } else if (next_ofs==0 && s_copyMode==COPY_MODE) { // delete the mix deleteExpoMix(expo, s_currIdx); - if (key==KEY_MOVE_UP) s_currIdx--; + if (key==KEY_UP) s_currIdx--; } else { // only swap the mix with its neighbor - if (!swapExpoMix(expo, s_currIdx, key==KEY_MOVE_UP)) break; + if (!swapExpoMix(expo, s_currIdx, key==KEY_UP)) break; storageDirty(EE_MODEL); } @@ -908,7 +908,7 @@ void menuModelExpoMix(uint8_t expo, uint8_t event) } } else { - if (mixCnt > 0) lcd_putsiAtt(FW, y, STR_VMLTPX2, md->mltpx, 0); + if (mixCnt > 0) lcdDrawTextAtIndex(FW, y, STR_VMLTPX2, md->mltpx, 0); putsMixerSource(MIX_LINE_SRC_POS, y, md->srcRaw, 0); diff --git a/radio/src/gui/Taranis/menu_model_limits.cpp b/radio/src/gui/Taranis/menu_model_limits.cpp index f132b8ad0..0cdc8fbc3 100644 --- a/radio/src/gui/Taranis/menu_model_limits.cpp +++ b/radio/src/gui/Taranis/menu_model_limits.cpp @@ -146,7 +146,7 @@ void menuModelLimits(uint8_t event) if (k==NUM_CHNOUT) { // last line available - add the "copy trim menu" line uint8_t attr = (sub==NUM_CHNOUT) ? INVERS : 0; - lcd_putsAtt(CENTER_OFS, y, STR_TRIMS2OFFSETS, s_noHi ? 0 : attr); + lcdDrawText(CENTER_OFS, y, STR_TRIMS2OFFSETS, s_noHi ? 0 : attr); if (attr) { s_editMode = 0; if (event==EVT_KEY_LONG(KEY_ENTER)) { @@ -231,7 +231,7 @@ void menuModelLimits(uint8_t event) #if defined(PPM_CENTER_ADJUSTABLE) lcdDrawChar(LIMITS_REVERT_POS, y, revert ? 127 : 126, attr); #else - lcd_putsiAtt(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); + lcdDrawTextAtIndex(LIMITS_REVERT_POS, y, STR_MMMINV, revert, attr); #endif if (active) { uint8_t revert_new = checkIncDecModel(event, revert, 0, 1); diff --git a/radio/src/gui/Taranis/menu_model_logical_switches.cpp b/radio/src/gui/Taranis/menu_model_logical_switches.cpp index 996255ee2..64c825047 100644 --- a/radio/src/gui/Taranis/menu_model_logical_switches.cpp +++ b/radio/src/gui/Taranis/menu_model_logical_switches.cpp @@ -65,9 +65,9 @@ void putsEdgeDelayParam(coord_t x, coord_t y, LogicalSwitchData *cs, uint8_t lat lcd_outdezAtt(x, y, lswTimerValue(cs->v2), LEFT|PREC1|lattr); lcd_putc(lcdLastPos, y, ':'); if (cs->v3 < 0) - lcd_putsAtt(lcdLastPos+3, y, "<<", rattr); + lcdDrawText(lcdLastPos+3, y, "<<", rattr); else if (cs->v3 == 0) - lcd_putsAtt(lcdLastPos+3, y, "--", rattr); + lcdDrawText(lcdLastPos+3, y, "--", rattr); else lcd_outdezAtt(lcdLastPos+3, y, lswTimerValue(cs->v2+cs->v3), LEFT|PREC1|rattr); lcd_putc(lcdLastPos, y, ']'); @@ -131,7 +131,7 @@ void menuModelLogicalSwitches(uint8_t event) putsSwitches(0, y, sw, (getSwitch(sw) ? BOLD : 0) | ((sub==k && CURSOR_ON_LINE()) ? INVERS : 0)); // CSW func - lcd_putsiAtt(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); + lcdDrawTextAtIndex(CSW_1ST_COLUMN, y, STR_VCSWFUNC, cs->func, horz==0 ? attr : 0); // CSW params unsigned int cstate = lswFamily(cs->func); @@ -205,7 +205,7 @@ void menuModelLogicalSwitches(uint8_t event) if (cs->duration > 0) lcd_outdezAtt(CSW_5TH_COLUMN, y, cs->duration, (horz==LS_FIELD_DURATION ? attr : 0)|PREC1|LEFT); else - lcd_putsiAtt(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); + lcdDrawTextAtIndex(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); // CSW delay if (cstate == LS_FAMILY_EDGE) { @@ -218,7 +218,7 @@ void menuModelLogicalSwitches(uint8_t event) lcd_outdezAtt(CSW_6TH_COLUMN, y, cs->delay, (horz==LS_FIELD_DELAY ? attr : 0)|PREC1|LEFT); } else { - lcd_putsiAtt(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); + lcdDrawTextAtIndex(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); } if (attr && horz == LS_FIELD_V3 && cstate != LS_FAMILY_EDGE) { diff --git a/radio/src/gui/Taranis/menu_model_select.cpp b/radio/src/gui/Taranis/menu_model_select.cpp index 86091ee97..dec64bf3d 100644 --- a/radio/src/gui/Taranis/menu_model_select.cpp +++ b/radio/src/gui/Taranis/menu_model_select.cpp @@ -89,7 +89,7 @@ void menuModelSelect(uint8_t event) event = EVT_ENTRY_UP; } - uint8_t _event_ = (IS_ROTARY_BREAK(event) || IS_ROTARY_LONG(event) ? 0 : event); + uint8_t _event_ = ((event==EVT_KEY_BREAK(KEY_ENTER) || event==EVT_KEY_LONG(KEY_ENTER)) ? 0 : event); if ((s_copyMode && EVT_KEY_MASK(event) == KEY_EXIT) || event == EVT_KEY_BREAK(KEY_EXIT)) { _event_ -= KEY_EXIT; @@ -209,10 +209,10 @@ void menuModelSelect(uint8_t event) killEvents(event); break; - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_copyMode) { int8_t next_ofs = s_copyTgtOfs + oldSub - m_posVert; if (next_ofs == MAX_MODELS || next_ofs == -MAX_MODELS) @@ -221,7 +221,7 @@ void menuModelSelect(uint8_t event) if (s_copySrcRow < 0 && s_copyMode==COPY_MODE) { s_copySrcRow = oldSub; // find a hole (in the first empty slot above / below) - sub = eeFindEmptyModel(s_copySrcRow, IS_ROTARY_DOWN(event) || event==EVT_KEY_FIRST(KEY_MOVE_DOWN)); + sub = eeFindEmptyModel(s_copySrcRow, event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_REPT(KEY_DOWN)); if (sub < 0) { // no free room for duplicating the model AUDIO_ERROR(); diff --git a/radio/src/gui/Taranis/menu_model_setup.cpp b/radio/src/gui/Taranis/menu_model_setup.cpp index e52ca025d..7a5793fab 100644 --- a/radio/src/gui/Taranis/menu_model_setup.cpp +++ b/radio/src/gui/Taranis/menu_model_setup.cpp @@ -299,9 +299,9 @@ void menuModelSetup(uint8_t event) case ITEM_MODEL_BITMAP: lcd_putsLeft(y, STR_BITMAP); if (ZEXIST(g_model.header.bitmap)) - lcd_putsnAtt(MODEL_SETUP_2ND_COLUMN, y, g_model.header.bitmap, sizeof(g_model.header.bitmap), attr); + lcdDrawTextWithLen(MODEL_SETUP_2ND_COLUMN, y, g_model.header.bitmap, sizeof(g_model.header.bitmap), attr); else - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VCSWFUNC, 0, attr); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VCSWFUNC, 0, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER) && READ_ONLY_UNLOCKED()) { s_editMode = 0; if (sdListFiles(BITMAPS_PATH, BITMAPS_EXT, sizeof(g_model.header.bitmap), g_model.header.bitmap, LIST_NONE_SD_FILE)) { @@ -394,7 +394,7 @@ void menuModelSetup(uint8_t event) case ITEM_MODEL_EXTENDED_TRIMS: ON_OFF_MENU_ITEM(g_model.extendedTrims, MODEL_SETUP_2ND_COLUMN, y, STR_ETRIMS, m_posHorz<=0 ? attr : 0, event==EVT_KEY_BREAK(KEY_ENTER) ? event : 0); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+3*FW, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+3*FW, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); if (attr && m_posHorz>0) { s_editMode = 0; if (event==EVT_KEY_LONG(KEY_ENTER)) { @@ -484,7 +484,6 @@ void menuModelSetup(uint8_t event) s_editMode = 0; if (!READ_ONLY()) { switch (event) { - CASE_EVT_ROTARY_BREAK case EVT_KEY_BREAK(KEY_ENTER): break; @@ -542,7 +541,7 @@ void menuModelSetup(uint8_t event) #endif lcd_putsLeft(y, STR_POTWARNING); - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, PSTR("\004""OFF\0""Man\0""Auto"), g_model.potsWarnMode, (m_posHorz == 0) ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, PSTR("\004""OFF\0""Man\0""Auto"), g_model.potsWarnMode, (m_posHorz == 0) ? attr : 0); if (attr && (m_posHorz == 0)) { CHECK_INCDEC_MODELVAR(event, g_model.potsWarnMode, POTS_WARN_OFF, POTS_WARN_AUTO); storageDirty(EE_MODEL); @@ -586,7 +585,7 @@ void menuModelSetup(uint8_t event) } // TODO add a new function - lcd_putsnAtt(x, y, STR_VSRCRAW+2+STR_VSRCRAW[0]*(NUM_STICKS+1+i), STR_VSRCRAW[0]-1, flags & ~(BSS|ZCHAR)); + lcdDrawTextWithLen(x, y, STR_VSRCRAW+2+STR_VSRCRAW[0]*(NUM_STICKS+1+i), STR_VSRCRAW[0]-1, flags & ~(BSS|ZCHAR)); x = lcdNextPos+3; } } @@ -609,7 +608,7 @@ void menuModelSetup(uint8_t event) if (attr && m_posHorz == i) REPEAT_LAST_CURSOR_MOVE(); continue; } - lcd_putsiAtt(x, y, STR_RETA123, i, ((m_posHorz==i) && attr) ? BLINK|INVERS : (((g_model.beepANACenter & ((BeepANACenter)1<0) { switch (m_posHorz) { case 0: @@ -664,7 +663,7 @@ void menuModelSetup(uint8_t event) #else case ITEM_MODEL_INTERNAL_MODULE_MODE: lcd_putsLeft(y, STR_MODE); - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_XJT_PROTOCOLS, 1+g_model.moduleData[0].rfProtocol, attr); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_XJT_PROTOCOLS, 1+g_model.moduleData[0].rfProtocol, attr); if (attr) { CHECK_INCDEC_MODELVAR(event, g_model.moduleData[0].rfProtocol, RF_PROTO_OFF, RF_PROTO_LAST); if (checkIncDec_Ret) { @@ -684,11 +683,11 @@ void menuModelSetup(uint8_t event) case ITEM_MODEL_EXTERNAL_MODULE_MODE: lcd_putsLeft(y, STR_MODE); - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_TARANIS_PROTOCOLS, g_model.moduleData[EXTERNAL_MODULE].type, m_posHorz==0 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_TARANIS_PROTOCOLS, g_model.moduleData[EXTERNAL_MODULE].type, m_posHorz==0 ? attr : 0); if (IS_MODULE_XJT(EXTERNAL_MODULE)) - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN+5*FW, y, STR_XJT_PROTOCOLS, 1+g_model.moduleData[EXTERNAL_MODULE].rfProtocol, m_posHorz==1 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN+5*FW, y, STR_XJT_PROTOCOLS, 1+g_model.moduleData[EXTERNAL_MODULE].rfProtocol, m_posHorz==1 ? attr : 0); else if (IS_MODULE_DSM2(EXTERNAL_MODULE)) - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN+5*FW, y, STR_DSM_PROTOCOLS, g_model.moduleData[EXTERNAL_MODULE].rfProtocol, m_posHorz==1 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN+5*FW, y, STR_DSM_PROTOCOLS, g_model.moduleData[EXTERNAL_MODULE].rfProtocol, m_posHorz==1 ? attr : 0); if (attr && s_editMode>0) { switch (m_posHorz) { case 0: @@ -727,7 +726,7 @@ void menuModelSetup(uint8_t event) ModuleData & moduleData = g_model.moduleData[moduleIdx]; lcd_putsLeft(y, STR_CHANNELRANGE); if ((int8_t)PORT_CHANNELS_ROWS(moduleIdx) >= 0) { - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN, y, STR_CH, m_posHorz==0 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN, y, STR_CH, m_posHorz==0 ? attr : 0); lcd_outdezAtt(lcdLastPos, y, moduleData.channelsStart+1, LEFT | (m_posHorz==0 ? attr : 0)); lcd_putc(lcdLastPos, y, '-'); lcd_outdezAtt(lcdLastPos + FW+1, y, moduleData.channelsStart+NUM_CHANNELS(moduleIdx), LEFT | (m_posHorz==1 ? attr : 0)); @@ -807,8 +806,8 @@ void menuModelSetup(uint8_t event) checkModelIdUnique(g_eeGeneral.currModel, moduleIdx); } } - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); - lcd_putsAtt(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+xOffsetBind, y, STR_MODULE_BIND, l_posHorz==1 ? attr : 0); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+MODEL_SETUP_RANGE_OFS+xOffsetBind, y, STR_MODULE_RANGE, l_posHorz==2 ? attr : 0); uint8_t newFlag = 0; if (attr && l_posHorz>0 && s_editMode>0) { if (l_posHorz == 1) @@ -830,8 +829,8 @@ void menuModelSetup(uint8_t event) ModuleData & moduleData = g_model.moduleData[moduleIdx]; lcd_putsLeft(y, TR_FAILSAFE); if (IS_MODULE_XJT(moduleIdx)) { - lcd_putsiAtt(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); - if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcd_putsAtt(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); + lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); + if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcdDrawText(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); if (attr) { if (moduleData.failsafeMode != FAILSAFE_CUSTOM) { m_posHorz = 0; @@ -937,7 +936,7 @@ void menuModelFailsafe(uint8_t event) } if (lenLabel > 0) - lcd_putsnAtt(x+1-ofs, y, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR | SMLSIZE); + lcdDrawTextWithLen(x+1-ofs, y, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR | SMLSIZE); else putsChn(x+1-ofs, y, ch+1, SMLSIZE); @@ -963,11 +962,11 @@ void menuModelFailsafe(uint8_t event) #endif if (failsafeValue == FAILSAFE_CHANNEL_HOLD) { - lcd_putsAtt(x+COL_W-4-wbar-ofs-16, y, "HOLD", flags); + lcdDrawText(x+COL_W-4-wbar-ofs-16, y, "HOLD", flags); failsafeValue = 0; } else if (failsafeValue == FAILSAFE_CHANNEL_NOPULSE) { - lcd_putsAtt(x+COL_W-4-wbar-ofs-16, y, "NONE", flags); + lcdDrawText(x+COL_W-4-wbar-ofs-16, y, "NONE", flags); failsafeValue = 0; } else { diff --git a/radio/src/gui/Taranis/menu_model_telemetry.cpp b/radio/src/gui/Taranis/menu_model_telemetry.cpp index 51c7092cf..9edb46800 100644 --- a/radio/src/gui/Taranis/menu_model_telemetry.cpp +++ b/radio/src/gui/Taranis/menu_model_telemetry.cpp @@ -302,7 +302,7 @@ void menuModelSensor(uint8_t event) case SENSOR_FIELD_UNIT: lcd_putsLeft(y, STR_UNIT); // TODO flash saving with selectMenuItem where I copied those 2 lines? - lcd_putsiAtt(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); + lcdDrawTextAtIndex(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); if (attr) { CHECK_INCDEC_MODELVAR_ZERO(event, sensor->unit, UNIT_MAX); if (checkIncDec_Ret) { @@ -517,7 +517,7 @@ void menuModelTelemetry(uint8_t event) } } - MENU(STR_MENUTELEMETRY, menuTabModel, e_Telemetry, ITEM_TELEMETRY_MAX, { TELEMETRY_TYPE_ROWS RSSI_ROWS SENSORS_ROWS VARIO_ROWS LABEL(TopBar), 0, 0, TELEMETRY_SCREEN_ROWS(0), TELEMETRY_SCREEN_ROWS(1), CASE_CPUARM(TELEMETRY_SCREEN_ROWS(2)) CASE_CPUARM(TELEMETRY_SCREEN_ROWS(3)) }); + MENU(STR_MENUTELEMETRY, menuTabModel, e_Telemetry, ITEM_TELEMETRY_MAX, { TELEMETRY_TYPE_ROWS RSSI_ROWS SENSORS_ROWS VARIO_ROWS LABEL(TopBar), 0, 0, TELEMETRY_SCREEN_ROWS(0), TELEMETRY_SCREEN_ROWS(1), TELEMETRY_SCREEN_ROWS(2), TELEMETRY_SCREEN_ROWS(3) }); int sub = m_posVert; @@ -536,7 +536,7 @@ void menuModelTelemetry(uint8_t event) int index = k-ITEM_TELEMETRY_SENSOR1; lcd_outdezAtt(INDENT_WIDTH, y, index+1, LEFT|attr); lcdDrawChar(lcdLastPos, y, ':', attr); - lcd_putsnAtt(3*FW, y, g_model.telemetrySensors[index].label, TELEM_LABEL_LEN, ZCHAR); + lcdDrawTextWithLen(3*FW, y, g_model.telemetrySensors[index].label, TELEM_LABEL_LEN, ZCHAR); if (telemetryItems[index].isFresh()) { lcd_putc(10*FW, y, '*'); } @@ -549,7 +549,7 @@ void menuModelTelemetry(uint8_t event) if (isOld) lcd_putc(lcdLastPos, y, ']'); } else { - lcd_putsAtt(TELEM_COL2, y, "---", 0); // TODO shortcut + lcdDrawText(TELEM_COL2, y, "---", 0); // TODO shortcut } TelemetrySensor * sensor = & g_model.telemetrySensors[index]; if (sensor->type == TELEM_TYPE_CUSTOM && !g_model.ignoreSensorIds) { @@ -579,14 +579,14 @@ void menuModelTelemetry(uint8_t event) case ITEM_TELEMETRY_SENSORS_LABEL: lcd_putsLeft(y, STR_TELEMETRY_SENSORS); - lcd_putsAtt(TELEM_COL2, y, STR_VALUE, 0); + lcdDrawText(TELEM_COL2, y, STR_VALUE, 0); if (!g_model.ignoreSensorIds) { - lcd_putsAtt(TELEM_COL3, y, STR_ID, 0); + lcdDrawText(TELEM_COL3, y, STR_ID, 0); } break; case ITEM_TELEMETRY_DISCOVER_SENSORS: - lcd_putsAtt(0, y, allowNewSensors ? STR_STOP_DISCOVER_SENSORS : STR_DISCOVER_SENSORS, attr); + lcdDrawText(0, y, allowNewSensors ? STR_STOP_DISCOVER_SENSORS : STR_DISCOVER_SENSORS, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; allowNewSensors = !allowNewSensors; @@ -594,7 +594,7 @@ void menuModelTelemetry(uint8_t event) break; case ITEM_TELEMETRY_NEW_SENSOR: - lcd_putsAtt(0, y, STR_TELEMETRY_NEWSENSOR, attr); + lcdDrawText(0, y, STR_TELEMETRY_NEWSENSOR, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; int res = availableTelemetryIndex(); @@ -609,7 +609,7 @@ void menuModelTelemetry(uint8_t event) break; case ITEM_TELEMETRY_DELETE_ALL_SENSORS: - lcd_putsAtt(0, y, STR_DELETE_ALL_SENSORS, attr); + lcdDrawText(0, y, STR_DELETE_ALL_SENSORS, attr); if (attr) s_editMode = 0; if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { @@ -670,7 +670,7 @@ void menuModelTelemetry(uint8_t event) lcd_putsLeft(y, STR_CENTER); lcd_outdezAtt(TELEM_COL2, y, -5+g_model.frsky.varioCenterMin, (m_posHorz==0 ? attr : 0)|PREC1|LEFT); lcd_outdezAtt(TELEM_COL2+7*FW, y, 5+g_model.frsky.varioCenterMax, (m_posHorz==1 ? attr : 0)|PREC1|LEFT); - lcd_putsiAtt(TELEM_COL3, y, STR_VVARIOCENTER, g_model.frsky.varioCenterSilent, (m_posHorz==2 ? attr : 0)); + lcdDrawTextAtIndex(TELEM_COL3, y, STR_VVARIOCENTER, g_model.frsky.varioCenterSilent, (m_posHorz==2 ? attr : 0)); if (attr && s_editMode>0) { switch (m_posHorz) { case 0: @@ -728,9 +728,9 @@ void menuModelTelemetry(uint8_t event) // TODO better function name for --- // TODO function for these lines if (ZEXIST(scriptData.file)) - lcd_putsnAtt(TELEM_SCRTYPE_COL+7*FW, y, scriptData.file, sizeof(scriptData.file), (m_posHorz==1 ? attr : 0)); + lcdDrawTextWithLen(TELEM_SCRTYPE_COL+7*FW, y, scriptData.file, sizeof(scriptData.file), (m_posHorz==1 ? attr : 0)); else - lcd_putsiAtt(TELEM_SCRTYPE_COL+7*FW, y, STR_VCSWFUNC, 0, (m_posHorz==1 ? attr : 0)); + lcdDrawTextAtIndex(TELEM_SCRTYPE_COL+7*FW, y, STR_VCSWFUNC, 0, (m_posHorz==1 ? attr : 0)); if (m_posHorz==1 && attr && event==EVT_KEY_BREAK(KEY_ENTER) && READ_ONLY_UNLOCKED()) { s_editMode = 0; diff --git a/radio/src/gui/Taranis/menus.h b/radio/src/gui/Taranis/menus.h index 81736caf0..eda4925fd 100644 --- a/radio/src/gui/Taranis/menus.h +++ b/radio/src/gui/Taranis/menus.h @@ -108,7 +108,7 @@ void menuAboutView(uint8_t event); void menuTraceBuffer(uint8_t event); #endif -void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); +void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); void menuMainViewChannelsMonitor(uint8_t event); void menuChannelsView(uint8_t event); @@ -327,7 +327,6 @@ void drawStatusLine(); extern char s_text_file[TEXT_FILENAME_MAXLEN]; void menuTextView(uint8_t event); void pushMenuTextView(const char *filename); -bool modelHasNotes(); void pushModelNotes(); void menuChannelsView(uint8_t event); @@ -335,23 +334,19 @@ void menuChannelsView(uint8_t event); #define LABEL(...) (uint8_t)-1 #if defined(REV9E) && !defined(SIMU) - #define KEY_MOVE_UP KEY_MINUS - #define KEY_MOVE_DOWN KEY_PLUS - #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_MINUS) - #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_PLUS) - #define CASE_EVT_ROTARY_MOVE_RIGHT CASE_EVT_ROTARY_RIGHT - #define CASE_EVT_ROTARY_MOVE_LEFT CASE_EVT_ROTARY_LEFT - #define IS_ROTARY_MOVE_RIGHT IS_ROTARY_RIGHT - #define IS_ROTARY_MOVE_LEFT IS_ROTARY_LEFT + #define KEY_UP KEY_MINUS + #define KEY_DOWN KEY_PLUS + #define KEY_RIGHT KEY_PLUS + #define KEY_LEFT KEY_MINUS + #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_LEFT) + #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_RIGHT) #else - #define KEY_MOVE_UP KEY_PLUS - #define KEY_MOVE_DOWN KEY_MINUS - #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_PLUS) - #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_MINUS) - #define CASE_EVT_ROTARY_MOVE_RIGHT CASE_EVT_ROTARY_LEFT - #define CASE_EVT_ROTARY_MOVE_LEFT CASE_EVT_ROTARY_RIGHT - #define IS_ROTARY_MOVE_RIGHT IS_ROTARY_LEFT - #define IS_ROTARY_MOVE_LEFT IS_ROTARY_RIGHT + #define KEY_UP KEY_PLUS + #define KEY_DOWN KEY_MINUS + #define KEY_RIGHT KEY_MINUS + #define KEY_LEFT KEY_PLUS + #define CURSOR_MOVED_LEFT(event) (EVT_KEY_MASK(event) == KEY_LEFT) + #define CURSOR_MOVED_RIGHT(event) (EVT_KEY_MASK(event) == KEY_RIGHT) #endif #define REPEAT_LAST_CURSOR_MOVE() { if (CURSOR_MOVED_LEFT(event) || CURSOR_MOVED_RIGHT(event)) putEvent(event); else m_posHorz = 0; } diff --git a/radio/src/gui/Taranis/navigation.cpp b/radio/src/gui/Taranis/navigation.cpp index 6724101e6..4e0ca0d2a 100644 --- a/radio/src/gui/Taranis/navigation.cpp +++ b/radio/src/gui/Taranis/navigation.cpp @@ -173,7 +173,7 @@ int checkIncDec(unsigned int event, int val, int i_min, int i_max, unsigned int } #endif - if (s_editMode>0 && (IS_ROTARY_RIGHT(event) || event==EVT_KEY_FIRST(KEY_UP) || event==EVT_KEY_REPT(KEY_UP))) { + if (s_editMode>0 && (event==EVT_KEY_FIRST(KEY_PLUS) || event==EVT_KEY_REPT(KEY_PLUS))) { do { if (IS_KEY_REPT(event) && (i_flags & INCDEC_REP10)) { newval += min(10, i_max-val); @@ -192,7 +192,7 @@ int checkIncDec(unsigned int event, int val, int i_min, int i_max, unsigned int AUDIO_KEYPAD_UP(); } } - else if (s_editMode>0 && (IS_ROTARY_LEFT(event) || event==EVT_KEY_FIRST(KEY_DOWN) || event==EVT_KEY_REPT(KEY_DOWN))) { + else if (s_editMode>0 && (event==EVT_KEY_FIRST(KEY_MINUS) || event==EVT_KEY_REPT(KEY_MINUS))) { do { if (IS_KEY_REPT(event) && (i_flags & INCDEC_REP10)) { newval -= min(10, val-i_min); @@ -212,7 +212,7 @@ int checkIncDec(unsigned int event, int val, int i_min, int i_max, unsigned int } } - if (!READ_ONLY() && i_min==0 && i_max==1 && (event==EVT_KEY_BREAK(KEY_ENTER) || IS_ROTARY_BREAK(event))) { + if (!READ_ONLY() && i_min==0 && i_max==1 && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; newval = !val; } @@ -249,7 +249,7 @@ int checkIncDec(unsigned int event, int val, int i_min, int i_max, unsigned int } if (newval != val) { - if (!(i_flags & NO_INCDEC_MARKS) && (newval != i_max) && (newval != i_min) && stops.contains(newval) && !IS_ROTARY_EVENT(event)) { + if (!(i_flags & NO_INCDEC_MARKS) && (newval != i_max) && (newval != i_min) && stops.contains(newval)) { bool pause = (newval > val ? !stops.contains(newval+1) : !stops.contains(newval-1)); if (pause) { pauseEvents(event); // delay before auto-repeat continues @@ -474,7 +474,8 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP } break; - CASE_EVT_ROTARY_MOVE_RIGHT + case EVT_KEY_FIRST(KEY_RIGHT): + case EVT_KEY_REPT(KEY_RIGHT): if (s_editMode != 0) break; if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { if (l_posHorz >= 0) { @@ -489,8 +490,6 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP } else { l_posHorz = 0; - if (!IS_ROTARY_MOVE_RIGHT(event)) - break; } } @@ -503,7 +502,8 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP l_posHorz = POS_HORZ_INIT(l_posVert); break; - CASE_EVT_ROTARY_MOVE_LEFT + case EVT_KEY_FIRST(KEY_LEFT): + case EVT_KEY_REPT(KEY_LEFT): if (s_editMode != 0) break; if ((COLATTR(l_posVert) & NAVIGATION_LINE_BY_LINE)) { if (l_posHorz >= 0) { @@ -511,18 +511,9 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP break; } } - else { - if (l_posHorz > 0) { - l_posHorz--; - break; - } - else if (IS_ROTARY_MOVE_LEFT(event) && s_editMode == 0) { - l_posHorz = 0xff; - } - else { - l_posHorz = maxcol; - break; - } + else if (l_posHorz > 0) { + l_posHorz--; + break; } do { @@ -546,7 +537,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP if (horTab) { linesCount = 0; for (int i=0; i=horTabMax || horTab[i] != HIDDEN_ROW) { + if (i>horTabMax || horTab[i] != HIDDEN_ROW) { linesCount++; } } @@ -577,7 +568,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP else { linesCount = s_pgOfs + NUM_BODY_LINES; for (int i=lastLine; i=horTabMax || horTab[i] != HIDDEN_ROW) { + if (i>horTabMax || horTab[i] != HIDDEN_ROW) { linesCount++; } } @@ -597,7 +588,7 @@ void check(const char *name, check_event_t event, uint8_t curr, const MenuFuncP } if (scrollbar_X && linesCount > NUM_BODY_LINES) { - lcdDrawScrollbar(scrollbar_X, MENU_HEADER_HEIGHT, LCD_H-MENU_HEADER_HEIGHT, s_pgOfs, linesCount, NUM_BODY_LINES); + drawScrollbar(scrollbar_X, MENU_HEADER_HEIGHT, LCD_H-MENU_HEADER_HEIGHT, s_pgOfs, linesCount, NUM_BODY_LINES); } if (name) { diff --git a/radio/src/gui/Taranis/popups.cpp b/radio/src/gui/Taranis/popups.cpp index 0fd46346c..aac4461c5 100644 --- a/radio/src/gui/Taranis/popups.cpp +++ b/radio/src/gui/Taranis/popups.cpp @@ -76,11 +76,11 @@ void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SO #define MESSAGE_LCD_OFFSET 60 #if defined(TRANSLATIONS_FR) || defined(TRANSLATIONS_IT) || defined(TRANSLATIONS_CZ) - lcd_putsAtt(MESSAGE_LCD_OFFSET, 0, STR_WARNING, DBLSIZE); - lcd_putsAtt(MESSAGE_LCD_OFFSET, 2*FH, title, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 0, STR_WARNING, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 2*FH, title, DBLSIZE); #else - lcd_putsAtt(MESSAGE_LCD_OFFSET, 0, title, DBLSIZE); - lcd_putsAtt(MESSAGE_LCD_OFFSET, 2*FH, STR_WARNING, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 0, title, DBLSIZE); + lcdDrawText(MESSAGE_LCD_OFFSET, 2*FH, STR_WARNING, DBLSIZE); #endif drawFilledRect(MESSAGE_LCD_OFFSET, 0, LCD_W-MESSAGE_LCD_OFFSET, 32); @@ -102,7 +102,7 @@ void displayWarning(uint8_t event) s_warning_result = false; displayBox(s_warning); if (s_warning_info) { - lcd_putsnAtt(WARNING_LINE_X, WARNING_LINE_Y+FH, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); + lcdDrawTextWithLen(WARNING_LINE_X, WARNING_LINE_Y+FH, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); } lcd_puts(WARNING_LINE_X, WARNING_LINE_Y+2*FH, s_warning_type == WARNING_TYPE_ASTERISK ? STR_EXIT : STR_POPUPS); switch (event) { @@ -134,17 +134,17 @@ const char * displayMenu(uint8_t event) lcdDrawRect(MENU_X, y, MENU_W, display_count * (FH+1) + 2); for (uint8_t i=0; i display_count) { - lcdDrawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_DISPLAY_LINES * (FH+1), s_menu_offset, s_menu_count, display_count); + drawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_DISPLAY_LINES * (FH+1), s_menu_offset, s_menu_count, display_count); } switch(event) { - case EVT_KEY_FIRST(KEY_MOVE_UP): - case EVT_KEY_REPT(KEY_MOVE_UP): + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): if (s_menu_item > 0) { s_menu_item--; } @@ -161,8 +161,8 @@ const char * displayMenu(uint8_t event) } break; - case EVT_KEY_FIRST(KEY_MOVE_DOWN): - case EVT_KEY_REPT(KEY_MOVE_DOWN): + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): if (s_menu_item < display_count - 1 && s_menu_offset + s_menu_item + 1 < s_menu_count) { s_menu_item++; } @@ -178,7 +178,6 @@ const char * displayMenu(uint8_t event) } } break; - CASE_EVT_ROTARY_BREAK case EVT_KEY_BREAK(KEY_ENTER): result = s_menu[s_menu_item + (s_menu_offset_type == MENU_OFFSET_INTERNAL ? s_menu_offset : 0)]; // no break diff --git a/radio/src/gui/Taranis/view_about.cpp b/radio/src/gui/Taranis/view_about.cpp index 05c92a86a..c76138fb1 100644 --- a/radio/src/gui/Taranis/view_about.cpp +++ b/radio/src/gui/Taranis/view_about.cpp @@ -83,7 +83,7 @@ void menuAboutView(uint8_t event) } lcd_bmp(0, 0, about_bmp); - lcd_putsAtt(64, 0, STR_ABOUTUS, DBLSIZE); + lcdDrawText(64, 0, STR_ABOUTUS, DBLSIZE); lcd_hline(ABOUT_X, 18, 120); lcd_hline(ABOUT_X, 19, 130, GREY_DEFAULT); LcdFlags att = GREY(max(0, 15-greyIndex/2)); @@ -93,67 +93,67 @@ void menuAboutView(uint8_t event) switch (screenIndex) { case ABOUT_OPENTX: case ABOUT_END: - lcd_putsAtt(ABOUT_X, 22, STR_ABOUT_OPENTX_1, SMLSIZE); - lcd_putsAtt(ABOUT_X, 30, STR_ABOUT_OPENTX_2, SMLSIZE); - lcd_putsAtt(ABOUT_X, 38, STR_ABOUT_OPENTX_3, SMLSIZE); - lcd_putsAtt(ABOUT_X, 46, STR_ABOUT_OPENTX_4, SMLSIZE); - lcd_putsAtt(ABOUT_X, 54, STR_ABOUT_OPENTX_5, SMLSIZE); + lcdDrawText(ABOUT_X, 22, STR_ABOUT_OPENTX_1, SMLSIZE); + lcdDrawText(ABOUT_X, 30, STR_ABOUT_OPENTX_2, SMLSIZE); + lcdDrawText(ABOUT_X, 38, STR_ABOUT_OPENTX_3, SMLSIZE); + lcdDrawText(ABOUT_X, 46, STR_ABOUT_OPENTX_4, SMLSIZE); + lcdDrawText(ABOUT_X, 54, STR_ABOUT_OPENTX_5, SMLSIZE); screenDuration = 255; break; case ABOUT_BERTRAND: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_BERTRAND_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_BERTRAND_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_BERTRAND_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_BERTRAND_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_BERTRAND_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_BERTRAND_3, att|SMLSIZE); break; case ABOUT_ANDRE: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_ANDRE_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ANDRE_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ANDRE_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_ANDRE_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ANDRE_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ANDRE_3, att|SMLSIZE); break; case ABOUT_MIKE: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_MIKE_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MIKE_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_MIKE_3, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_MIKE_4, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_MIKE_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MIKE_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_MIKE_3, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_MIKE_4, att|SMLSIZE); break; case ABOUT_KJELL: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_KJELL_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_KJELL_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_KJELL_3, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_KJELL_4, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_KJELL_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_KJELL_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_KJELL_3, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_KJELL_4, att|SMLSIZE); break; case ABOUT_MARTIN: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_MARTIN_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MARTIN_2, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_MARTIN_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_MARTIN_2, att|SMLSIZE); break; case ABOUT_ROMOLO: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_ROMOLO_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROMOLO_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ROMOLO_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_ROMOLO_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROMOLO_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_ROMOLO_3, att|SMLSIZE); break; case ABOUT_ROB: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_ROB_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROB_2, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_ROB_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_ROB_2, att|SMLSIZE); break; case ABOUT_HARDWARE: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_HARDWARE_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_HARDWARE_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_HARDWARE_3, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_HARDWARE_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_HARDWARE_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_HARDWARE_3, att|SMLSIZE); break; case ABOUT_PARENTS: - lcd_putsAtt(ABOUT_X, 24, STR_ABOUT_PARENTS_1, att); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_PARENTS_2, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_PARENTS_3, att|SMLSIZE); - lcd_putsAtt(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_PARENTS_4, att|SMLSIZE); + lcdDrawText(ABOUT_X, 24, STR_ABOUT_PARENTS_1, att); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 36, STR_ABOUT_PARENTS_2, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 44, STR_ABOUT_PARENTS_3, att|SMLSIZE); + lcdDrawText(ABOUT_X+ABOUT_INDENT, 52, STR_ABOUT_PARENTS_4, att|SMLSIZE); screenDuration = 255; break; } diff --git a/radio/src/gui/Taranis/view_channels.cpp b/radio/src/gui/Taranis/view_channels.cpp index b5e48aea0..9dd22de26 100644 --- a/radio/src/gui/Taranis/view_channels.cpp +++ b/radio/src/gui/Taranis/view_channels.cpp @@ -100,7 +100,7 @@ void menuChannelsView(uint8_t event) } if (lenLabel > 0) - lcd_putsnAtt(x+1-ofs, y, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR | SMLSIZE); + lcdDrawTextWithLen(x+1-ofs, y, g_model.limitData[ch].name, sizeof(g_model.limitData[ch].name), ZCHAR | SMLSIZE); else putsChn(x+1-ofs, y, ch+1, SMLSIZE); diff --git a/radio/src/gui/Taranis/view_main.cpp b/radio/src/gui/Taranis/view_main.cpp index d9b91211f..ef4a30465 100644 --- a/radio/src/gui/Taranis/view_main.cpp +++ b/radio/src/gui/Taranis/view_main.cpp @@ -176,7 +176,7 @@ void displayTrims(uint8_t phase) } } -void displaySliders() +void drawSliders() { for (uint8_t i=NUM_STICKS; i 0) { - lcd_putsnAtt(TIMERS_X, y-7, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE); + lcdDrawTextWithLen(TIMERS_X, y-7, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE); } else { putsTimerMode(TIMERS_X, y-7, timerData.mode, SMLSIZE); @@ -538,7 +538,7 @@ void menuMainView(uint8_t event) // Flight Mode Name int mode = mixerCurrentFlightMode; - lcd_putsnAtt(PHASE_X, PHASE_Y, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|PHASE_FLAGS); + lcdDrawTextWithLen(PHASE_X, PHASE_Y, g_model.flightModeData[mode].name, sizeof(g_model.flightModeData[mode].name), ZCHAR|PHASE_FLAGS); // Model Name putsModelName(MODELNAME_X, MODELNAME_Y, g_model.header.name, g_eeGeneral.currModel, BIGSIZE); @@ -550,7 +550,7 @@ void menuMainView(uint8_t event) displayTopBar(); // Sliders (Pots / Sliders) - displaySliders(); + drawSliders(); lcd_bmp(BITMAP_X, BITMAP_Y, modelBitmap); @@ -616,8 +616,8 @@ void menuMainView(uint8_t event) drawFilledRect(BITMAP_X, BITMAP_Y, 64, 32, SOLID, ERASE); lcdDrawRect(BITMAP_X, BITMAP_Y, 64, 32); putsStrIdx(BITMAP_X+FW, BITMAP_Y+FH-1, STR_GV, s_gvar_last+1); - lcd_putsnAtt(BITMAP_X+4*FW+FW/2, BITMAP_Y+FH-1, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, ZCHAR); - lcd_putsAtt(BITMAP_X+FW, BITMAP_Y+2*FH+3, PSTR("[\010]"), BOLD); + lcdDrawTextWithLen(BITMAP_X+4*FW+FW/2, BITMAP_Y+FH-1, g_model.gvars[s_gvar_last].name, LEN_GVAR_NAME, ZCHAR); + lcdDrawText(BITMAP_X+FW, BITMAP_Y+2*FH+3, PSTR("[\010]"), BOLD); lcd_outdezAtt(BITMAP_X+5*FW+FW/2, BITMAP_Y+2*FH+3, GVAR_VALUE(s_gvar_last, getGVarFlightPhase(mixerCurrentFlightMode, s_gvar_last)), BOLD); } #endif diff --git a/radio/src/gui/Taranis/view_statistics.cpp b/radio/src/gui/Taranis/view_statistics.cpp index 5aa740c22..7d8dc0da7 100644 --- a/radio/src/gui/Taranis/view_statistics.cpp +++ b/radio/src/gui/Taranis/view_statistics.cpp @@ -168,9 +168,9 @@ void menuStatisticsDebug(uint8_t event) #if defined(LUA) lcd_putsLeft(MENU_DEBUG_Y_LUA, "Lua scripts"); - lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_LUA+1, "[Duration]", SMLSIZE); + lcdDrawText(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_LUA+1, "[Duration]", SMLSIZE); lcd_outdezAtt(lcdLastPos, MENU_DEBUG_Y_LUA, 10*maxLuaDuration, LEFT); - lcd_putsAtt(lcdLastPos+2, MENU_DEBUG_Y_LUA+1, "[Interval]", SMLSIZE); + lcdDrawText(lcdLastPos+2, MENU_DEBUG_Y_LUA+1, "[Interval]", SMLSIZE); lcd_outdezAtt(lcdLastPos, MENU_DEBUG_Y_LUA, 10*maxLuaInterval, LEFT); #endif @@ -188,13 +188,13 @@ void menuStatisticsDebug(uint8_t event) #endif lcd_putsLeft(MENU_DEBUG_Y_RTOS, STR_FREESTACKMINB); - lcd_putsAtt(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_RTOS+1, "[M]", SMLSIZE); + lcdDrawText(MENU_DEBUG_COL1_OFS, MENU_DEBUG_Y_RTOS+1, "[M]", SMLSIZE); lcd_outdezAtt(lcdLastPos, MENU_DEBUG_Y_RTOS, menusStack.available(), UNSIGN|LEFT); - lcd_putsAtt(lcdLastPos+2, MENU_DEBUG_Y_RTOS+1, "[X]", SMLSIZE); + lcdDrawText(lcdLastPos+2, MENU_DEBUG_Y_RTOS+1, "[X]", SMLSIZE); lcd_outdezAtt(lcdLastPos, MENU_DEBUG_Y_RTOS, mixerStack.available(), UNSIGN|LEFT); - lcd_putsAtt(lcdLastPos+2, MENU_DEBUG_Y_RTOS+1, "[A]", SMLSIZE); + lcdDrawText(lcdLastPos+2, MENU_DEBUG_Y_RTOS+1, "[A]", SMLSIZE); lcd_outdezAtt(lcdLastPos, MENU_DEBUG_Y_RTOS, audioStack.available(), UNSIGN|LEFT); - lcd_putsAtt(lcdLastPos+2, MENU_DEBUG_Y_RTOS+1, "[I]", SMLSIZE); + lcdDrawText(lcdLastPos+2, MENU_DEBUG_Y_RTOS+1, "[I]", SMLSIZE); lcd_outdezAtt(lcdLastPos, MENU_DEBUG_Y_RTOS, stackAvailable(), UNSIGN|LEFT); lcd_puts(3*FW, 7*FH+1, STR_MENUTORESET); diff --git a/radio/src/gui/Taranis/view_telemetry.cpp b/radio/src/gui/Taranis/view_telemetry.cpp index eed7f0b6b..c2a212eba 100644 --- a/radio/src/gui/Taranis/view_telemetry.cpp +++ b/radio/src/gui/Taranis/view_telemetry.cpp @@ -58,7 +58,7 @@ void displayRssiLine() drawFilledRect(BAR_LEFT+1, 58, 19*rssi/25, 5, (rssi < getRssiAlarmValue(0)) ? DOTTED : SOLID); } else { - lcd_putsAtt(7*FW, STATUS_BAR_Y, STR_NODATA, BLINK); + lcdDrawText(7*FW, STATUS_BAR_Y, STR_NODATA, BLINK); lcd_status_line(); } } diff --git a/radio/src/gui/Taranis/view_text.cpp b/radio/src/gui/Taranis/view_text.cpp index b71bb1a32..a79532f19 100644 --- a/radio/src/gui/Taranis/view_text.cpp +++ b/radio/src/gui/Taranis/view_text.cpp @@ -136,7 +136,7 @@ void menuTextView(uint8_t event) } for (int i=0; i LCD_LINES-1) { - lcdDrawScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); + drawScrollbar(LCD_W-1, FH, LCD_H-FH, s_pgOfs, lines_count, LCD_LINES-1); } } diff --git a/radio/src/gui/Taranis/widgets.cpp b/radio/src/gui/Taranis/widgets.cpp index b9ebd0496..255c06737 100644 --- a/radio/src/gui/Taranis/widgets.cpp +++ b/radio/src/gui/Taranis/widgets.cpp @@ -61,7 +61,7 @@ void drawStick(coord_t centrex, int16_t xval, int16_t yval) void displayColumnHeader(const char * const *headers, uint8_t index) { - lcd_putsAtt(17*FW, 0, headers[index], 0); + lcdDrawText(17*FW, 0, headers[index], 0); } void menu_lcd_onoff(coord_t x, coord_t y, uint8_t value, LcdFlags attr) @@ -82,7 +82,7 @@ void displayScreenIndex(uint8_t index, uint8_t count, uint8_t attr) lcd_outdezAtt(x, 0, index+1, attr); } -void lcdDrawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) +void drawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { lcdDrawVerticalLine(x, y, h, DOTTED); coord_t yofs = (h * offset) / count; @@ -101,12 +101,12 @@ void displayMenuBar(const MenuItem *menu, int index) drawFilledRect(0, 0, LCD_W, 32, SOLID, ERASE); drawFilledRect(0, 24, LCD_W, 7, SOLID, GREY_DEFAULT); lcd_bmp(1, 0, MAINMENU_LBM); - lcd_putsAtt(0, 24, menu[index].name, INVERS); + lcdDrawText(0, 24, menu[index].name, INVERS); lcdDrawRect(index*24, 0, 26, 24, SOLID, FORCE); lcdDrawHorizontalLine(0, 31, LCD_W, SOLID, FORCE); } -void displayProgressBar(const char *label) +void drawProgressBar(const char *label) { lcd_putsLeft(4*FH, label); lcdDrawRect(3, 6*FH+4, 204, 7); @@ -137,13 +137,13 @@ void drawGauge(coord_t x, coord_t y, coord_t w, coord_t h, int32_t val, int32_t void title(const pm_char * s) { - lcd_putsAtt(0, 0, s, INVERS); + lcdDrawText(0, 0, s, INVERS); } select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, uint8_t event) { lcd_putsColumnLeft(x, y, label); - if (values) lcd_putsiAtt(x, y, values, value-min, attr); + if (values) lcdDrawTextAtIndex(x, y, values, value-min, attr); if (attr) value = checkIncDec(event, value, min, max, (g_menuPos[0] == 0) ? EE_MODEL : EE_GENERAL); return value; } @@ -162,7 +162,7 @@ swsrc_t switchMenuItem(coord_t x, coord_t y, swsrc_t value, LcdFlags attr, uint8 return value; } -void displaySlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) +void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr) { lcd_putc(x+(value*4*FW)/max, y, '$'); lcd_hline(x, y+3, 5*FW-1, FORCE); @@ -255,7 +255,7 @@ void drawStatusLine() } drawFilledRect(0, LCD_H-statusLineHeight, LCD_W, FH, SOLID, ERASE); - lcd_putsAtt(5, LCD_H+1-statusLineHeight, statusLineMsg, BSS); + lcdDrawText(5, LCD_H+1-statusLineHeight, statusLineMsg, BSS); drawFilledRect(0, LCD_H-statusLineHeight, LCD_W, FH, SOLID); } } diff --git a/radio/src/gui/gui_helpers.h b/radio/src/gui/gui_helpers.h index 5a6183005..abb1a3545 100644 --- a/radio/src/gui/gui_helpers.h +++ b/radio/src/gui/gui_helpers.h @@ -61,4 +61,6 @@ bool isSwitchAvailableInMixes(int swtch); bool isSwitchAvailableInTimers(int swtch); bool isModuleAvailable(int module); +bool modelHasNotes(); + #endif // _GUI_HELPERS_H_ diff --git a/radio/src/keys.h b/radio/src/keys.h index 8dfd1fbf5..2c678bd9c 100644 --- a/radio/src/keys.h +++ b/radio/src/keys.h @@ -34,8 +34,8 @@ * */ -#ifndef keys_h -#define keys_h +#ifndef _KEYS_H_ +#define _KEYS_H_ enum EnumKeys { #if defined(PCBHORUS) @@ -217,7 +217,7 @@ enum EnumKeys { #define EVT_ROTARY_LONG EVT_KEY_LONG(KEY_ENTER) #define EVT_ROTARY_LEFT 0xDF00 #define EVT_ROTARY_RIGHT 0xDE00 -#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) +#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) #define EVT_ROTARY_BREAK EVT_KEY_BREAK(KEY_ENTER) #define EVT_ROTARY_LONG EVT_KEY_LONG(KEY_ENTER) #else @@ -227,61 +227,6 @@ enum EnumKeys { #define EVT_ROTARY_RIGHT 0xde #endif -#if defined(PCBHORUS) - #define IS_ROTARY_LEFT(evt) (evt==EVT_KEY_FIRST(KEY_MINUS) || evt==EVT_KEY_REPT(KEY_MINUS)) - #define IS_ROTARY_RIGHT(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) - #define IS_ROTARY_UP(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) - #define IS_ROTARY_DOWN(evt) (evt==EVT_KEY_FIRST(KEY_MINUS) || evt==EVT_KEY_REPT(KEY_MINUS)) - #define IS_ROTARY_BREAK(evt) (evt==EVT_KEY_BREAK(KEY_ENTER)) - #define IS_ROTARY_LONG(evt) (evt==EVT_KEY_LONG(KEY_ENTER)) - #define IS_ROTARY_EVENT(evt) (0) - #define CASE_EVT_ROTARY_BREAK /*case EVT_KEY_BREAK(KEY_ENTER):*/ - #define CASE_EVT_ROTARY_LONG /*case EVT_KEY_LONG(KEY_ENTER):*/ - #define CASE_EVT_ROTARY_LEFT case EVT_KEY_FIRST(KEY_MOVE_UP): case EVT_KEY_REPT(KEY_MOVE_UP): - #define CASE_EVT_ROTARY_RIGHT case EVT_KEY_FIRST(KEY_MOVE_DOWN): case EVT_KEY_REPT(KEY_MOVE_DOWN): -#elif defined(PCBTARANIS) || defined(PCBFLAMENCO) - #define IS_ROTARY_LEFT(evt) (evt==EVT_KEY_FIRST(KEY_MINUS) || evt==EVT_KEY_REPT(KEY_MINUS)) - #define IS_ROTARY_RIGHT(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) - #define IS_ROTARY_UP(evt) (evt==EVT_KEY_FIRST(KEY_PLUS) || evt==EVT_KEY_REPT(KEY_PLUS)) - #define IS_ROTARY_DOWN(evt) (evt==EVT_KEY_FIRST(KEY_MINUS) || evt==EVT_KEY_REPT(KEY_MINUS)) - #define IS_ROTARY_BREAK(evt) (evt==EVT_KEY_BREAK(KEY_ENTER)) - #define IS_ROTARY_LONG(evt) (evt==EVT_KEY_LONG(KEY_ENTER)) - #define IS_ROTARY_EVENT(evt) (0) - #define CASE_EVT_ROTARY_BREAK /*case EVT_KEY_BREAK(KEY_ENTER):*/ - #define CASE_EVT_ROTARY_LONG /*case EVT_KEY_LONG(KEY_ENTER):*/ - #if defined(REV9E) && !defined(SIMU) - #define CASE_EVT_ROTARY_LEFT case EVT_KEY_FIRST(KEY_MOVE_UP): case EVT_KEY_REPT(KEY_MOVE_UP): - #define CASE_EVT_ROTARY_RIGHT case EVT_KEY_FIRST(KEY_MOVE_DOWN): case EVT_KEY_REPT(KEY_MOVE_DOWN): - #else - #define CASE_EVT_ROTARY_LEFT case EVT_KEY_FIRST(KEY_MOVE_DOWN): case EVT_KEY_REPT(KEY_MOVE_DOWN): - #define CASE_EVT_ROTARY_RIGHT case EVT_KEY_FIRST(KEY_MOVE_UP): case EVT_KEY_REPT(KEY_MOVE_UP): - #endif -#elif defined(ROTARY_ENCODER_NAVIGATION) - #define IS_ROTARY_LEFT(evt) (evt == EVT_ROTARY_LEFT) - #define IS_ROTARY_RIGHT(evt) (evt == EVT_ROTARY_RIGHT) - #define IS_ROTARY_UP(evt) IS_ROTARY_LEFT(evt) - #define IS_ROTARY_DOWN(evt) IS_ROTARY_RIGHT(evt) - #define IS_ROTARY_BREAK(evt) (evt == EVT_ROTARY_BREAK) - #define IS_ROTARY_LONG(evt) (evt == EVT_ROTARY_LONG) - #define IS_ROTARY_EVENT(evt) (EVT_KEY_MASK(evt) >= 0x0e) - #define CASE_EVT_ROTARY_BREAK case EVT_ROTARY_BREAK: - #define CASE_EVT_ROTARY_LONG case EVT_ROTARY_LONG: - #define CASE_EVT_ROTARY_LEFT case EVT_ROTARY_LEFT: - #define CASE_EVT_ROTARY_RIGHT case EVT_ROTARY_RIGHT: -#else - #define IS_ROTARY_LEFT(evt) (0) - #define IS_ROTARY_RIGHT(evt) (0) - #define IS_ROTARY_UP(evt) (0) - #define IS_ROTARY_DOWN(evt) (0) - #define IS_ROTARY_BREAK(evt) (0) - #define IS_ROTARY_LONG(evt) (0) - #define IS_ROTARY_EVENT(evt) (0) - #define CASE_EVT_ROTARY_BREAK - #define CASE_EVT_ROTARY_LONG - #define CASE_EVT_ROTARY_LEFT - #define CASE_EVT_ROTARY_RIGHT -#endif - class Key { #define FILTERBITS 4 @@ -335,4 +280,4 @@ void killEvents(uint8_t enuk); uint8_t keyDown(); -#endif +#endif // _KEYS_H_ diff --git a/radio/src/lua_api.cpp b/radio/src/lua_api.cpp index 6f6a70c9e..ac0322004 100644 --- a/radio/src/lua_api.cpp +++ b/radio/src/lua_api.cpp @@ -460,7 +460,7 @@ static int luaLcdDrawText(lua_State *L) int y = luaL_checkinteger(L, 2); const char * s = luaL_checkstring(L, 3); unsigned int att = luaL_optunsigned(L, 4, 0); - lcd_putsAtt(x, y, s, att); + lcdDrawText(x, y, s, att); return 0; } @@ -635,7 +635,7 @@ static int luaLcdDrawCombobox(lua_State *L) for (int i=0; igetKeyState(KEY_G)) { - if (!rotencAction) g_rotenc[0] += ROTARY_ENCODER_GRANULARITY; + if (!rotencAction) ROTENC_VALUE += ROTARY_ENCODER_GRANULARITY; rotencAction = true; } else if (getApp()->getKeyState(KEY_D)) { - if (!rotencAction) g_rotenc[0] -= ROTARY_ENCODER_GRANULARITY; + if (!rotencAction) ROTENC_VALUE -= ROTARY_ENCODER_GRANULARITY; rotencAction = true; } else { diff --git a/radio/src/storage/eeprom_rlc.cpp b/radio/src/storage/eeprom_rlc.cpp index 2cc1e6ce3..6b9c34b24 100644 --- a/radio/src/storage/eeprom_rlc.cpp +++ b/radio/src/storage/eeprom_rlc.cpp @@ -863,7 +863,7 @@ void RlcFile::flush() } #if defined (EEPROM_PROGRESS_BAR) -void RlcFile::DisplayProgressBar(uint8_t x) +void RlcFile::drawProgressBar(uint8_t x) { if (storageDirtyMsk || isWriting() || eeprom_buffer_size) { uint8_t len = storageDirtyMsk ? 1 : limit((uint8_t)1, (uint8_t)(7 - (m_rlc_len/m_ratio)), (uint8_t)7); diff --git a/radio/src/storage/eeprom_rlc.h b/radio/src/storage/eeprom_rlc.h index fb2652dc8..9c28e50fd 100644 --- a/radio/src/storage/eeprom_rlc.h +++ b/radio/src/storage/eeprom_rlc.h @@ -208,7 +208,7 @@ class RlcFile: public EFile uint16_t readRlc(uint8_t *buf, uint16_t i_len); #if defined (EEPROM_PROGRESS_BAR) - void DisplayProgressBar(uint8_t x); + void drawProgressBar(uint8_t x); #endif }; @@ -230,7 +230,7 @@ inline void eepromWriteProcess() } #if defined (EEPROM_PROGRESS_BAR) -#define DISPLAY_PROGRESS_BAR(x) theFile.DisplayProgressBar(x) +#define DISPLAY_PROGRESS_BAR(x) theFile.drawProgressBar(x) #else #define DISPLAY_PROGRESS_BAR(x) #endif diff --git a/radio/src/storage/sdcard_raw.cpp b/radio/src/storage/sdcard_raw.cpp index a1eca7eb0..8d2fd1ed1 100644 --- a/radio/src/storage/sdcard_raw.cpp +++ b/radio/src/storage/sdcard_raw.cpp @@ -189,6 +189,8 @@ void storageCheck(bool immediately) void storageReadAll() { + sdInit(); + if (loadGeneralSettings() != NULL) { storageEraseAll(true); strcpy(g_eeGeneral.currModelFilename, "model.bin"); diff --git a/radio/src/switches.cpp b/radio/src/switches.cpp index b7b8083c2..e59cad714 100644 --- a/radio/src/switches.cpp +++ b/radio/src/switches.cpp @@ -838,7 +838,7 @@ void checkSwitches() x += 20; } else if (numWarnings == 6) { - lcd_putsAtt(x, y, "...", ALARM_COLOR); + lcdDrawText(x, y, "...", ALARM_COLOR); } } #else @@ -851,7 +851,7 @@ void checkSwitches() x = lcdNextPos + 3; } else if (numWarnings == 7) { - lcd_putsAtt(x, y, "...", 0); + lcdDrawText(x, y, "...", 0); } } #endif @@ -874,7 +874,7 @@ void checkSwitches() strncpy(s, &STR_VSRCRAW[1+(NUM_STICKS+1+i)*STR_VSRCRAW[0]], STR_VSRCRAW[0]); s[int(STR_VSRCRAW[0])] = '\0'; #else - lcd_putsiAtt(x, y, STR_VSRCRAW, NUM_STICKS+1+i, INVERS); + lcdDrawTextAtIndex(x, y, STR_VSRCRAW, NUM_STICKS+1+i, INVERS); if (IS_POT(POT1+i)) lcdDrawChar(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? 126 : 127, INVERS); else @@ -882,10 +882,10 @@ void checkSwitches() #endif #if defined(COLORLCD) if (++numWarnings < 6) { - lcd_putsAtt(x, y, s, ALARM_COLOR); + lcdDrawText(x, y, s, ALARM_COLOR); } else if (numWarnings == 6) { - lcd_putsAtt(x, y, "...", ALARM_COLOR); + lcdDrawText(x, y, "...", ALARM_COLOR); } x += 20; #else diff --git a/radio/src/targets/Horus/diskio.cpp b/radio/src/targets/Horus/diskio.cpp index 3da1d09c4..49b948c71 100644 --- a/radio/src/targets/Horus/diskio.cpp +++ b/radio/src/targets/Horus/diskio.cpp @@ -248,9 +248,7 @@ void sdInit(void) return; } - TRACE("AVANT F_MOUNT"); if (f_mount(&g_FATFS_Obj, "", 1) == FR_OK) { - TRACE("APRES F_MOUNT"); // call sdGetFreeSectors() now because f_getfree() takes a long time first time it's called sdGetFreeSectors(); diff --git a/radio/src/targets/Taranis/bootloader/boot.cpp b/radio/src/targets/Taranis/bootloader/boot.cpp index fb47cb1e6..d0df4469e 100644 --- a/radio/src/targets/Taranis/bootloader/boot.cpp +++ b/radio/src/targets/Taranis/bootloader/boot.cpp @@ -539,7 +539,7 @@ int main() else { x = 0; } - lcd_putsnAtt(INDENT_WIDTH, 16 + FH * i, &Filenames[i][x], DISPLAY_CHAR_WIDTH, 0); + lcdDrawTextWithLen(INDENT_WIDTH, 16 + FH * i, &Filenames[i][x], DISPLAY_CHAR_WIDTH, 0); } if (event == EVT_KEY_REPT(BOOT_KEY_DOWN) || event == EVT_KEY_FIRST(BOOT_KEY_DOWN)) { diff --git a/radio/src/telemetry/ardupilot.cpp b/radio/src/telemetry/ardupilot.cpp index 50f22d970..dc21e1c80 100644 --- a/radio/src/telemetry/ardupilot.cpp +++ b/radio/src/telemetry/ardupilot.cpp @@ -514,10 +514,10 @@ void menuTelemetryArduPilot1(uint8_t event) title ('1'); lcd_puts (5*FW, 1*FH, PSTR(" Latitude")); // line 1 column 5 // first buffer into line 2 column 2 - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (5*FW, 4*FH, PSTR(" Longitude")); // line 4 column 5 // second buffer into line 5 column 2 - lcd_putsAtt (1*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (1*FW, 5*FH, VALSTR(1), APSIZE); } void menuTelemetryArduPilot2(uint8_t event) @@ -539,9 +539,9 @@ void menuTelemetryArduPilot2(uint8_t event) initval (1, PACK_POS, CRT); title ('2'); lcd_puts (1*FW, 1*FH, PSTR(" Ground speed")); - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (1*FW, 4*FH, PSTR(" Climb rate") ); - lcd_putsAtt (2*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (2*FW, 5*FH, VALSTR(1), APSIZE); } void menuTelemetryArduPilot3(uint8_t event) @@ -563,9 +563,9 @@ void menuTelemetryArduPilot3(uint8_t event) initval (1, PACK_POS, ALH); title ('3'); lcd_puts (1*FW, 1*FH, PSTR(" Altitude")); - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (1*FW, 4*FH, PSTR(" Altitude Hold") ); - lcd_putsAtt (2*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (2*FW, 5*FH, VALSTR(1), APSIZE); } void menuTelemetryArduPilot4(uint8_t event) @@ -587,9 +587,9 @@ void menuTelemetryArduPilot4(uint8_t event) initval (1, PACK_POS, BER); title ('4'); lcd_puts (1*FW, 1*FH, PSTR(" Course")); - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (1*FW, 4*FH, PSTR(" Bearing")); - lcd_putsAtt (2*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (2*FW, 5*FH, VALSTR(1), APSIZE); } void menuTelemetryArduPilot5(uint8_t event) @@ -613,9 +613,9 @@ void menuTelemetryArduPilot5(uint8_t event) initval (1, PACK_POS, DST); title ('5'); lcd_puts (1*FW, 1*FH, PSTR(" Way Point # ")); - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (1*FW, 4*FH, PSTR(" Distance ")); - lcd_putsAtt (2*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (2*FW, 5*FH, VALSTR(1), APSIZE); } void menuTelemetryArduPilot6(uint8_t event) @@ -639,9 +639,9 @@ void menuTelemetryArduPilot6(uint8_t event) initval (1, PACK_ATT, THH); title ('6'); lcd_puts (1*FW, 1*FH, PSTR(" Air Speed ")); - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (1*FW, 4*FH, PSTR(" Climb Rate ")); - lcd_putsAtt (2*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (2*FW, 5*FH, VALSTR(1), APSIZE); } void menuTelemetryArduPilot7(uint8_t event) @@ -665,9 +665,9 @@ void menuTelemetryArduPilot7(uint8_t event) initval (1, PACK_ATT, PCH); title ('7'); lcd_puts (1*FW, 1*FH, PSTR(" Roll Angle")); - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (1*FW, 4*FH, PSTR(" Pitch Angle")); - lcd_putsAtt (2*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (2*FW, 5*FH, VALSTR(1), APSIZE); } void menuTelemetryArduPilot8(uint8_t event) @@ -691,14 +691,14 @@ void menuTelemetryArduPilot8(uint8_t event) initval (1, PACK_WPC, WPC); title ('8'); lcd_puts (1*FW, 1*FH, PSTR(" ArduPilot Mode")); - lcd_putsAtt (2*FW, 2*FH, VALSTR(0), APSIZE); + lcdDrawText (2*FW, 2*FH, VALSTR(0), APSIZE); lcd_puts (1*FW, 4*FH, PSTR(" RTL Distance")); - lcd_putsAtt (2*FW, 5*FH, VALSTR(1), APSIZE); + lcdDrawText (2*FW, 5*FH, VALSTR(1), APSIZE); } void title(char x) { - lcd_putsAtt (0, 0, PSTR(" ARDU PILOT Mega ?/8 "), INVERS); + lcdDrawText (0, 0, PSTR(" ARDU PILOT Mega ?/8 "), INVERS); lcdDrawChar(17*FW, 0*FH, x, INVERS); } diff --git a/radio/src/telemetry/nmea.cpp b/radio/src/telemetry/nmea.cpp index 4adf107f3..64b3c39f9 100644 --- a/radio/src/telemetry/nmea.cpp +++ b/radio/src/telemetry/nmea.cpp @@ -464,14 +464,14 @@ void menuTelemetryNMEA1(uint8_t event) if (rbuf[0][0]) { // show always if data have been received lcdDrawChar ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view - lcd_putsnAtt ( 2*FW, 2*FH, &rbuf[0][0], 2, APSIZE); // hours + lcdDrawTextWithLen ( 2*FW, 2*FH, &rbuf[0][0], 2, APSIZE); // hours lcdDrawChar ( 6*FW, 2*FH, ':', DBLSIZE); // ":" - lcd_putsnAtt ( 8*FW, 2*FH, &rbuf[0][2], 2, APSIZE); // minutes + lcdDrawTextWithLen ( 8*FW, 2*FH, &rbuf[0][2], 2, APSIZE); // minutes lcdDrawChar ( 12*FW, 2*FH, ':', DBLSIZE); // ":" - lcd_putsnAtt ( 14*FW, 2*FH, &rbuf[0][4], 2, APSIZE); // seconds + lcdDrawTextWithLen ( 14*FW, 2*FH, &rbuf[0][4], 2, APSIZE); // seconds } else - lcd_putsAtt ( 2*FW, 2*FH, val_unknown, APSIZE); // "?" + lcdDrawText ( 2*FW, 2*FH, val_unknown, APSIZE); // "?" if ((show_timer == 1) && rbuf[0][0]) { // show the Timer when data have been received @@ -483,14 +483,14 @@ void menuTelemetryNMEA1(uint8_t event) lcd_puts ( 2*FW, 4*FH, PSTR("Date")); // show the UTC Date if (rbuf[1][0]) { - lcd_putsnAtt( 2*FW, 5*FH, &rbuf[1][0], 2, APSIZE); // year + lcdDrawTextWithLen( 2*FW, 5*FH, &rbuf[1][0], 2, APSIZE); // year lcdDrawChar ( 6*FW, 5*FH, '/', DBLSIZE); // "/" - lcd_putsnAtt( 8*FW, 5*FH, &rbuf[1][2], 2, APSIZE); // month + lcdDrawTextWithLen( 8*FW, 5*FH, &rbuf[1][2], 2, APSIZE); // month lcdDrawChar (12*FW, 5*FH, '/', DBLSIZE); // "/" - lcd_putsnAtt(14*FW, 5*FH, &rbuf[1][4], 2, APSIZE); // day + lcdDrawTextWithLen(14*FW, 5*FH, &rbuf[1][4], 2, APSIZE); // day } else - lcd_putsAtt ( 2*FW, 5*FH, val_unknown, APSIZE); // "?" + lcdDrawText ( 2*FW, 5*FH, val_unknown, APSIZE); // "?" } } @@ -651,8 +651,8 @@ void menuTelemetryNMEA2(uint8_t event) } } else { - lcd_putsAtt ( 2*FW, 2*FH, val_unknown, APSIZE); - lcd_putsAtt ( 2*FW, 5*FH, val_unknown, APSIZE); + lcdDrawText ( 2*FW, 2*FH, val_unknown, APSIZE); + lcdDrawText ( 2*FW, 5*FH, val_unknown, APSIZE); } } @@ -694,15 +694,15 @@ void menuTelemetryNMEA3(uint8_t event) } i++; } - lcd_putsAtt ( 2*FW, 2*FH, VALSTR(0), APSIZE); // speed over ground + lcdDrawText ( 2*FW, 2*FH, VALSTR(0), APSIZE); // speed over ground } else - lcd_putsAtt ( 2*FW, 2*FH, val_unknown, APSIZE); + lcdDrawText ( 2*FW, 2*FH, val_unknown, APSIZE); lcdDrawChar ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view lcd_puts ( 1*FW, 4*FH, PSTR("Course over ground") ); - lcd_putsAtt ( 2*FW, 5*FH, VALSTR(1), APSIZE); // course over ground + lcdDrawText ( 2*FW, 5*FH, VALSTR(1), APSIZE); // course over ground } @@ -741,29 +741,29 @@ void menuTelemetryNMEA4(uint8_t event) { lcdDrawChar ( 13*FW, 1*FH, sbuf[0], 0); // N or S lcdDrawChar ( 19*FW, 1*FH, sbuf[2], 0); // satellites in view - lcd_putsnAtt ( 1*FW, 2*FH, rbuf[0], 2, APSIZE); + lcdDrawTextWithLen ( 1*FW, 2*FH, rbuf[0], 2, APSIZE); lcdDrawChar ( 5*FW, 2*FH, '@',0); - lcd_putsAtt ( 6*FW, 2*FH, &rbuf[0][2], APSIZE); // minutes with small decimal point + lcdDrawText ( 6*FW, 2*FH, &rbuf[0][2], APSIZE); // minutes with small decimal point } else - lcd_putsAtt ( 2*FW, 2*FH, val_unknown, APSIZE); + lcdDrawText ( 2*FW, 2*FH, val_unknown, APSIZE); lcd_puts ( 3*FW, 4*FH, PSTR("Longitude")); // line 4 column 5 // second buffer into line 5 column 2 if (rbuf[0][0]) { lcdDrawChar ( 13*FW, 4*FH, sbuf[1], 0); // E or W - lcd_putsnAtt ( 0*FW, 5*FH, rbuf[1], 3, APSIZE); + lcdDrawTextWithLen ( 0*FW, 5*FH, rbuf[1], 3, APSIZE); lcdDrawChar ( 6*FW, 5*FH, '@',0); - lcd_putsAtt ( 7*FW, 5*FH, &rbuf[1][3], APSIZE); // minutes with small decimal point + lcdDrawText ( 7*FW, 5*FH, &rbuf[1][3], APSIZE); // minutes with small decimal point } else - lcd_putsAtt ( 2*FW, 5*FH, val_unknown, APSIZE); + lcdDrawText ( 2*FW, 5*FH, val_unknown, APSIZE); } void title(char x) { - lcd_putsAtt (0*FW, 0*FH, PSTR(" GPS NMEA data ?/4 "), INVERS); + lcdDrawText (0*FW, 0*FH, PSTR(" GPS NMEA data ?/4 "), INVERS); lcdDrawChar(16*FW, 0*FH, x, INVERS); } diff --git a/radio/src/tests/lcd.cpp b/radio/src/tests/lcd.cpp index e1200efb5..05561b83a 100644 --- a/radio/src/tests/lcd.cpp +++ b/radio/src/tests/lcd.cpp @@ -133,14 +133,14 @@ TEST(outdezNAtt, testBigNumbers) TEST(Lcd, Invers_0_0) { lcdClear(); - lcd_putsAtt(0, 0, "Test", INVERS); + lcdDrawText(0, 0, "Test", INVERS); EXPECT_TRUE(checkScreenshot("invers_0_0")); } TEST(Lcd, Invers_0_1) { lcdClear(); - lcd_putsAtt(0, 1, "Test", INVERS); + lcdDrawText(0, 1, "Test", INVERS); EXPECT_TRUE(checkScreenshot("invers_0_1")); } @@ -177,7 +177,7 @@ TEST(Lcd, Line_Wrap) TEST(Lcd, DblsizeBottomRight) { lcdClear(); - lcd_putsAtt(LCD_W-20, LCD_H-16, "TEST", DBLSIZE); + lcdDrawText(LCD_W-20, LCD_H-16, "TEST", DBLSIZE); EXPECT_TRUE(checkScreenshot("dblsize_bottom_right")); } @@ -213,14 +213,14 @@ TEST(Lcd, vline_x_lt0) TEST(Lcd, Smlsize) { lcdClear(); - lcd_putsAtt(0, 0, "TESTgy,", SMLSIZE); - lcd_putsAtt(10, 22, "TESTgy,", SMLSIZE|INVERS); + lcdDrawText(0, 0, "TESTgy,", SMLSIZE); + lcdDrawText(10, 22, "TESTgy,", SMLSIZE|INVERS); drawFilledRect(8, 40, 100, 20); - lcd_putsAtt(10, 42, "TESTgy,", SMLSIZE); + lcdDrawText(10, 42, "TESTgy,", SMLSIZE); bool invert = false; for(int i=0; i<3; i++) { - lcd_putsAtt(40+(4*i), 0+(4*i), "ABC", SMLSIZE|(invert?INVERS:0)); + lcdDrawText(40+(4*i), 0+(4*i), "ABC", SMLSIZE|(invert?INVERS:0)); invert = !invert; } @@ -230,14 +230,14 @@ TEST(Lcd, Smlsize) TEST(Lcd, Stdsize) { lcdClear(); - lcd_putsAtt(0, 0, "TEST", 0); - lcd_putsAtt(10, 22, "TEST", INVERS); + lcdDrawText(0, 0, "TEST", 0); + lcdDrawText(10, 22, "TEST", INVERS); drawFilledRect(8, 40, 100, 20); - lcd_putsAtt(10, 42, "TEST", 0); + lcdDrawText(10, 42, "TEST", 0); bool invert = false; for(int i=0; i<3; i++) { - lcd_putsAtt(40+(4*i), 0+(4*i), "ABC", (invert?INVERS:0)); + lcdDrawText(40+(4*i), 0+(4*i), "ABC", (invert?INVERS:0)); invert = !invert; } @@ -247,14 +247,14 @@ TEST(Lcd, Stdsize) TEST(Lcd, Midsize) { lcdClear(); - lcd_putsAtt(0, 0, "TEST", MIDSIZE); - lcd_putsAtt(10, 22, "TEST", MIDSIZE|INVERS); + lcdDrawText(0, 0, "TEST", MIDSIZE); + lcdDrawText(10, 22, "TEST", MIDSIZE|INVERS); drawFilledRect(8, 40, 100, 20); - lcd_putsAtt(10, 42, "TEST", MIDSIZE); + lcdDrawText(10, 42, "TEST", MIDSIZE); bool invert = false; for(int i=0; i<3; i++) { - lcd_putsAtt(40+(4*i), 0+(4*i), "ABC", MIDSIZE|(invert?INVERS:0)); + lcdDrawText(40+(4*i), 0+(4*i), "ABC", MIDSIZE|(invert?INVERS:0)); invert = !invert; } @@ -264,14 +264,14 @@ TEST(Lcd, Midsize) TEST(Lcd, Dblsize) { lcdClear(); - lcd_putsAtt(2, 10, "TST", DBLSIZE); - lcd_putsAtt(42, 10, "TST", DBLSIZE|INVERS); + lcdDrawText(2, 10, "TST", DBLSIZE); + lcdDrawText(42, 10, "TST", DBLSIZE|INVERS); drawFilledRect(80, 8, 46, 24); - lcd_putsAtt(82, 10, "TST", DBLSIZE); + lcdDrawText(82, 10, "TST", DBLSIZE); bool invert = false; for(int i=0; i<3; i++) { - lcd_putsAtt(10+(4*i), 30+(4*i), "ABC", DBLSIZE|(invert?INVERS:0)); + lcdDrawText(10+(4*i), 30+(4*i), "ABC", DBLSIZE|(invert?INVERS:0)); invert = !invert; } diff --git a/radio/src/translations.cpp b/radio/src/translations.cpp index b02c249ad..e87f6b2cc 100644 --- a/radio/src/translations.cpp +++ b/radio/src/translations.cpp @@ -550,6 +550,7 @@ const pm_char STR_BLCOLOR[] PROGMEM = TR_BLCOLOR; const pm_char STR_PREFLIGHT[] PROGMEM = TR_PREFLIGHT; const pm_char STR_CHECKLIST[] PROGMEM = TR_CHECKLIST; const pm_char STR_VIEW_NOTES[] PROGMEM = TR_VIEW_NOTES; +const pm_char STR_MODEL_SELECT[] PROGMEM = TR_MODEL_SELECT; const pm_char STR_RESET_SUBMENU[] PROGMEM = TR_RESET_SUBMENU; const pm_char STR_LOWALARM[] PROGMEM = TR_LOWALARM; const pm_char STR_CRITICALALARM[] PROGMEM = TR_CRITICALALARM; diff --git a/radio/src/translations.h b/radio/src/translations.h index 14b28d745..dd1978b89 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -769,6 +769,7 @@ extern const pm_char STR_BLCOLOR[]; extern const pm_char STR_PREFLIGHT[]; extern const pm_char STR_CHECKLIST[]; extern const pm_char STR_VIEW_NOTES[]; +extern const pm_char STR_MODEL_SELECT[]; extern const pm_char STR_RESET_SUBMENU[]; extern const pm_char STR_LOWALARM[]; extern const pm_char STR_CRITICALALARM[]; diff --git a/radio/src/translations/cz.h.txt b/radio/src/translations/cz.h.txt index bc404cc32..99bcdabc5 100644 --- a/radio/src/translations/cz.h.txt +++ b/radio/src/translations/cz.h.txt @@ -1063,6 +1063,7 @@ #define TR_SCALE "Měřítko" #define TR_VIEW_CHANNELS "Zobrazit kanály" #define TR_VIEW_NOTES "Zobrazit poznámky" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Změny nejsou povoleny!" #define TR_UNLOCKED "Odemčeno" #define TR_ID "ID" diff --git a/radio/src/translations/de.h.txt b/radio/src/translations/de.h.txt index 5360c51bf..57a540434 100644 --- a/radio/src/translations/de.h.txt +++ b/radio/src/translations/de.h.txt @@ -1067,6 +1067,7 @@ #define TR_SCALE "Skalierer" #define TR_VIEW_CHANNELS "Zeige Kanäle" #define TR_VIEW_NOTES "Zeige Notizen" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Veränderungen verboten!" #define TR_UNLOCKED "Entsperrt" #define TR_ID "ID" diff --git a/radio/src/translations/en.h.txt b/radio/src/translations/en.h.txt index ed96fea43..796994624 100644 --- a/radio/src/translations/en.h.txt +++ b/radio/src/translations/en.h.txt @@ -1085,6 +1085,7 @@ #define TR_SCALE "Scale" #define TR_VIEW_CHANNELS "View Channels" #define TR_VIEW_NOTES "View Notes" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Modifications forbidden!" #define TR_UNLOCKED "Unlocked" #define TR_ID "ID" diff --git a/radio/src/translations/es.h.txt b/radio/src/translations/es.h.txt index d09331755..6defc6b88 100644 --- a/radio/src/translations/es.h.txt +++ b/radio/src/translations/es.h.txt @@ -1017,6 +1017,7 @@ #define TR_SCALE "Scale" #define TR_VIEW_CHANNELS "View Channels" #define TR_VIEW_NOTES "View Notes" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Modifications forbidden!" #define TR_UNLOCKED "Unlocked" #define TR_ID "ID" diff --git a/radio/src/translations/fi.h.txt b/radio/src/translations/fi.h.txt index 65c8a1840..553a14321 100644 --- a/radio/src/translations/fi.h.txt +++ b/radio/src/translations/fi.h.txt @@ -1017,6 +1017,7 @@ #define TR_SCALE "Scale" #define TR_VIEW_CHANNELS "View Channels" #define TR_VIEW_NOTES "View Notes" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Muutokset kielletty!" #define TR_UNLOCKED "Avattu" #define TR_ID "ID" diff --git a/radio/src/translations/fr.h.txt b/radio/src/translations/fr.h.txt index 7ec9a6eaf..e738566d1 100644 --- a/radio/src/translations/fr.h.txt +++ b/radio/src/translations/fr.h.txt @@ -1054,6 +1054,7 @@ #define TR_SCALE "Echelle" #define TR_VIEW_CHANNELS "Voir Voies" #define TR_VIEW_NOTES "Voir Notes" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Modifications désactivées!" #define TR_UNLOCKED "Déverrouillé" #define TR_ID "ID" diff --git a/radio/src/translations/it.h.txt b/radio/src/translations/it.h.txt index ab26bbf76..8f02b67a9 100644 --- a/radio/src/translations/it.h.txt +++ b/radio/src/translations/it.h.txt @@ -1065,6 +1065,7 @@ #define TR_SCALE "Scala" #define TR_VIEW_CHANNELS "Vedi Canali" #define TR_VIEW_NOTES "Vedi Note" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Modifica proibita!" #define TR_UNLOCKED "Sbloccato" #define TR_ID "ID" diff --git a/radio/src/translations/nl.h.txt b/radio/src/translations/nl.h.txt index 6155920dc..d02f8a7bf 100644 --- a/radio/src/translations/nl.h.txt +++ b/radio/src/translations/nl.h.txt @@ -1084,6 +1084,7 @@ #define TR_SCALE "Schaal" #define TR_VIEW_CHANNELS "Toon Kanalen" #define TR_VIEW_NOTES "Toon Notities" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Wijzigen niet toegestaan!" #define TR_UNLOCKED "Vrijgegeven" #define TR_ID "ID" diff --git a/radio/src/translations/pl.h.txt b/radio/src/translations/pl.h.txt index 49a93ccee..86225bc75 100644 --- a/radio/src/translations/pl.h.txt +++ b/radio/src/translations/pl.h.txt @@ -1066,6 +1066,7 @@ #define TR_SCALE "Skala" #define TR_VIEW_CHANNELS "Pokaż Kanały" #define TR_VIEW_NOTES "Pokaż Notki" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Modyfikacje zabronione!" #define TR_UNLOCKED "Odblokowane" #define TR_ID "ID" diff --git a/radio/src/translations/pt.h.txt b/radio/src/translations/pt.h.txt index a7bf22fbe..45efa69b6 100644 --- a/radio/src/translations/pt.h.txt +++ b/radio/src/translations/pt.h.txt @@ -1017,6 +1017,7 @@ #define TR_SCALE "Scale" #define TR_VIEW_CHANNELS "View Channels" #define TR_VIEW_NOTES "View Notes" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Modifications forbidden!" #define TR_UNLOCKED "Unlocked" #define TR_ID "ID" diff --git a/radio/src/translations/se.h.txt b/radio/src/translations/se.h.txt index 690c6b2d2..bc5dbff7b 100644 --- a/radio/src/translations/se.h.txt +++ b/radio/src/translations/se.h.txt @@ -1017,6 +1017,7 @@ #define TR_SCALE "Skala" #define TR_VIEW_CHANNELS "Visa Kanaler" #define TR_VIEW_NOTES "Visa Noteringars" +#define TR_MODEL_SELECT "Model Select" #define TR_MODS_FORBIDDEN "Modifikationer förbjudna!" #define TR_UNLOCKED "Olåst" #define TR_ID "ID" From 6f774eb61ab5bed4b14bb17cd1f2f268befc331f Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 24 Oct 2015 16:08:29 +0200 Subject: [PATCH 36/74] [Horus] Companion simulator --- companion/src/constants.h | 4 +- .../src/firmwares/opentx/opentxinterface.cpp | 16 + .../firmwares/opentx/simulator/CMakeLists.txt | 25 +- .../opentx/simulator/opentxsimulator.cpp | 114 +- .../opentx/simulator/opentxsimulator.h | 10 +- companion/src/helpers.cpp | 8 +- companion/src/simulation/CMakeLists.txt | 2 + companion/src/simulation/lcdwidget.h | 29 +- .../src/simulation/simulatordialog-9x.ui | 4 +- .../simulation/simulatordialog-flamenco.ui | 1663 +++++++++++++++++ .../src/simulation/simulatordialog-horus.ui | 1663 +++++++++++++++++ .../src/simulation/simulatordialog-taranis.ui | 4 +- companion/src/simulation/simulatordialog.cpp | 4 + companion/src/simulation/simulatordialog.h | 52 +- .../simulation/simulatordialogflamenco.cpp | 166 ++ .../src/simulation/simulatordialoghorus.cpp | 166 ++ companion/src/simulation/simulatorimport.h | 2 +- companion/src/simulator.cpp | 6 +- radio/src/gui/Horus/menu_model_select.cpp | 50 - radio/src/gui/Horus/menus.h | 1 + .../inc/stm32f4xx_dma2d.h | 2 +- .../inc/stm32f4xx_fmc.h | 2 +- .../inc/stm32f4xx_ltdc.h | 2 +- .../inc/stm32f4xx_tim.h | 2 +- 24 files changed, 3916 insertions(+), 81 deletions(-) create mode 100644 companion/src/simulation/simulatordialog-flamenco.ui create mode 100644 companion/src/simulation/simulatordialog-horus.ui create mode 100644 companion/src/simulation/simulatordialogflamenco.cpp create mode 100644 companion/src/simulation/simulatordialoghorus.cpp diff --git a/companion/src/constants.h b/companion/src/constants.h index 958134e07..b1f85fba2 100644 --- a/companion/src/constants.h +++ b/companion/src/constants.h @@ -11,7 +11,9 @@ enum BoardEnum { BOARD_AR9X, BOARD_TARANIS, BOARD_TARANIS_PLUS, - BOARD_TARANIS_X9E + BOARD_TARANIS_X9E, + BOARD_FLAMENCO, + BOARD_HORUS }; #define C9X_MAX_MODELS 60 diff --git a/companion/src/firmwares/opentx/opentxinterface.cpp b/companion/src/firmwares/opentx/opentxinterface.cpp index 16b5d6f78..c598d8d63 100644 --- a/companion/src/firmwares/opentx/opentxinterface.cpp +++ b/companion/src/firmwares/opentx/opentxinterface.cpp @@ -75,6 +75,10 @@ const char * OpenTxEepromInterface::getName() return "OpenTX for 9XR-PRO"; case BOARD_AR9X: return "OpenTX for ar9x board / 9X"; + case BOARD_FLAMENCO: + return "OpenTX for Flamenco experimental"; + case BOARD_HORUS: + return "OpenTX for FrSky Horus"; default: return "OpenTX for an unknown board"; } @@ -98,6 +102,8 @@ const int OpenTxEepromInterface::getEEpromSize() case BOARD_TARANIS: case BOARD_TARANIS_PLUS: case BOARD_TARANIS_X9E: + case BOARD_FLAMENCO: + case BOARD_HORUS: return EESIZE_TARANIS; default: return 0; @@ -403,6 +409,8 @@ int OpenTxEepromInterface::save(uint8_t *eeprom, RadioData &radioData, uint32_t case BOARD_SKY9X: case BOARD_AR9X: case BOARD_9XRPRO: + case BOARD_FLAMENCO: + case BOARD_HORUS: version = 218; break; case BOARD_GRUVIN9X: @@ -1432,6 +1440,14 @@ void registerOpenTxFirmwares() addOpenTxCommonOptions(firmware); firmwares.push_back(firmware); + /* Horus board */ + firmware = new OpenTxFirmware("opentx-horus", QObject::tr("FrSky Horus"), BOARD_HORUS); + firmware->addOption("noheli", QObject::tr("Disable HELI menu and cyclic mix support")); + firmware->addOption("nogvars", QObject::tr("Disable Global variables")); + firmware->addOption("lua", QObject::tr("Support for Lua model scripts")); + addOpenTxCommonOptions(firmware); + firmwares.push_back(firmware); + default_firmware_variant = GetFirmware("opentx-taranisplus-en"); current_firmware_variant = default_firmware_variant; } diff --git a/companion/src/firmwares/opentx/simulator/CMakeLists.txt b/companion/src/firmwares/opentx/simulator/CMakeLists.txt index 3a35e6ef7..f0ebe02cb 100644 --- a/companion/src/firmwares/opentx/simulator/CMakeLists.txt +++ b/companion/src/firmwares/opentx/simulator/CMakeLists.txt @@ -13,6 +13,16 @@ add_custom_target(about_lbm_taranis WORKING_DIRECTORY ${RADIO_SRC_DIRECTORY} ) +add_custom_target(about_lbm_flamenco + make lbm PCB=FLAMENCO + WORKING_DIRECTORY ${RADIO_SRC_DIRECTORY} +) + +add_custom_target(about_lbm_horus + make lbm PCB=HORUS + WORKING_DIRECTORY ${RADIO_SRC_DIRECTORY} +) + add_custom_target(commondeps make stamp_header tra WORKING_DIRECTORY ${RADIO_SRC_DIRECTORY} @@ -20,8 +30,11 @@ add_custom_target(commondeps set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${RADIO_SRC_DIRECTORY}/stamp-opentx.h) -add_custom_target(lua_exports - make PCB=TARANIS lua_exports_taranis.inc +add_custom_target(lua_exports + COMMAND make PCB=TARANIS lua_exports_taranis.inc + COMMAND make PCB=FLAMENCO lua_exports_flamenco.inc + COMMAND make PCB=HORUS lua_exports_horus.inc + DEPENDS ${RADIO_SRC_DIRECTORY}/myeeprom.h WORKING_DIRECTORY ${RADIO_SRC_DIRECTORY} ) @@ -84,6 +97,7 @@ add_library(opentx-ar9x${SUFFIX}-simulator SHARED ${OPENTX_SRC_FILES}) add_library(opentx-taranis${SUFFIX}-simulator SHARED ${OPENTX_SRC_FILES}) add_library(opentx-taranisplus${SUFFIX}-simulator SHARED ${OPENTX_SRC_FILES}) add_library(opentx-taranisx9e${SUFFIX}-simulator SHARED ${OPENTX_SRC_FILES}) +add_library(opentx-horus${SUFFIX}-simulator SHARED ${OPENTX_SRC_FILES}) add_dependencies(opentx-9x${SUFFIX}-simulator about_lbm_9x commondeps) add_dependencies(opentx-9xr${SUFFIX}-simulator about_lbm_9x commondeps) @@ -97,6 +111,7 @@ add_dependencies(opentx-ar9x${SUFFIX}-simulator about_lbm_9x commondeps) add_dependencies(opentx-taranis${SUFFIX}-simulator about_lbm_taranis commondeps lua_exports) add_dependencies(opentx-taranisplus${SUFFIX}-simulator about_lbm_taranis commondeps lua_exports) add_dependencies(opentx-taranisx9e${SUFFIX}-simulator about_lbm_taranis commondeps lua_exports) +add_dependencies(opentx-horus${SUFFIX}-simulator about_lbm_horus commondeps lua_exports) set(OPENTX_LIBRARIES opentx-9x${SUFFIX}-simulator @@ -111,6 +126,7 @@ set(OPENTX_LIBRARIES opentx-taranis${SUFFIX}-simulator opentx-taranisplus${SUFFIX}-simulator opentx-taranisx9e${SUFFIX}-simulator + opentx-horus${SUFFIX}-simulator ) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") @@ -177,6 +193,11 @@ target_include_directories(opentx-taranisx9e${SUFFIX}-simulator PRIVATE ${RADIO_ target_compile_definitions(opentx-taranisx9e${SUFFIX}-simulator PRIVATE PCBTARANIS REVPLUS REV9E CPUARM CPUSTM32 STM32F40_41xxx EEPROM EEPROM_RLC VOICE SDCARD ${COMMON_DEFINITIONS}) target_link_libraries(opentx-taranisx9e${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) +# Horus +target_include_directories(opentx-horus${SUFFIX}-simulator PRIVATE ${RADIO_SRC_DIRECTORY}/targets/Horus) +target_compile_definitions(opentx-horus${SUFFIX}-simulator PRIVATE PCBHORUS CPUARM CPUSTM32 STM32F40_41xxx COLORLCD VOICE SDCARD ${COMMON_DEFINITIONS}) +target_link_libraries(opentx-horus${SUFFIX}-simulator PRIVATE ${COMMON_LIBRARIES}) + add_custom_target(opentx-simulators DEPENDS ${OPENTX_LIBRARIES} ) diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp index 861e0535b..4d452c312 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp @@ -40,7 +40,11 @@ #define AUTOSWITCH #define GRAPHICS #define CURVES -#if defined(PCBTARANIS) +#if defined(PCBFLAMENCO) || defined(PCBHORUS) +#define RTCLOCK +#define XCURVES +#define VIRTUALINPUTS +#elif defined(PCBTARANIS) #define RTCLOCK #define XCURVES #define VIRTUALINPUTS @@ -97,6 +101,7 @@ namespace NAMESPACE { #include "radio/src/pulses/pulses_avr.cpp" #endif +#include "radio/src/storage/storage_common.cpp" #if defined(EEPROM_RLC) #include "radio/src/storage/eeprom_common.cpp" #include "radio/src/storage/eeprom_rlc.cpp" @@ -127,7 +132,79 @@ namespace NAMESPACE { #include "radio/src/vario.cpp" #include "radio/src/trainer_input.cpp" -#if defined(PCBTARANIS) +#if defined(PCBHORUS) +#include "radio/src/gui/Horus/bitmaps.cpp" +#include "radio/src/gui/Horus/fonts.cpp" +#include "radio/src/gui/Horus/widgets.cpp" +#include "radio/src/gui/Horus/navigation.cpp" +#include "radio/src/gui/Horus/popups.cpp" +#include "radio/src/gui/Horus/helpers.cpp" +#include "radio/src/gui/Horus/menus.cpp" +#include "radio/src/gui/Horus/menu_model.cpp" +#include "radio/src/gui/Horus/menu_model_select.cpp" +#include "radio/src/gui/Horus/menu_model_setup.cpp" +#include "radio/src/gui/Horus/menu_model_heli.cpp" +#include "radio/src/gui/Horus/menu_model_flightmodes.cpp" +#include "radio/src/gui/Horus/menu_model_inputs_mixes.cpp" +#include "radio/src/gui/Horus/menu_model_curves.cpp" +#include "radio/src/gui/Horus/menu_model_logical_switches.cpp" +#include "radio/src/gui/Horus/menu_model_custom_functions.cpp" +// #include "radio/src/gui/Horus/menu_model_custom_scripts.cpp" +#include "radio/src/gui/Horus/menu_model_gvars.cpp" +#include "radio/src/gui/Horus/menu_model_limits.cpp" +#include "radio/src/gui/Horus/menu_model_telemetry.cpp" +#include "radio/src/gui/Horus/menu_general.cpp" +#include "radio/src/gui/Horus/menu_general_setup.cpp" +#include "radio/src/gui/Horus/menu_general_sdmanager.cpp" +#include "radio/src/gui/Horus/menu_general_trainer.cpp" +#include "radio/src/gui/Horus/menu_general_version.cpp" +// #include "radio/src/gui/Horus/menu_general_hardware.cpp" +#include "radio/src/gui/Horus/menu_general_calib.cpp" +#include "radio/src/gui/Horus/view_main.cpp" +#include "radio/src/gui/Horus/view_statistics.cpp" +#include "radio/src/gui/Horus/view_channels.cpp" +#include "radio/src/gui/Horus/view_telemetry.cpp" +#include "radio/src/gui/Horus/view_text.cpp" +#include "radio/src/gui/Horus/view_about.cpp" +#include "radio/src/gui/Horus/lcd.cpp" +#include "radio/src/gui/Horus/splash.cpp" +#elif defined(PCBFLAMENCO) +#include "radio/src/gui/Flamenco/fonts.cpp" +#include "radio/src/gui/Flamenco/widgets.cpp" +#include "radio/src/gui/Flamenco/navigation.cpp" +#include "radio/src/gui/Flamenco/popups.cpp" +#include "radio/src/gui/Flamenco/helpers.cpp" +#include "radio/src/gui/Flamenco/menus.cpp" +#include "radio/src/gui/Flamenco/menu_model.cpp" +#include "radio/src/gui/Flamenco/menu_model_select.cpp" +#include "radio/src/gui/Flamenco/menu_model_setup.cpp" +#include "radio/src/gui/Flamenco/menu_model_heli.cpp" +#include "radio/src/gui/Flamenco/menu_model_flightmodes.cpp" +#include "radio/src/gui/Flamenco/menu_model_inputs_mixes.cpp" +#include "radio/src/gui/Flamenco/menu_model_curves.cpp" +#include "radio/src/gui/Flamenco/menu_model_logical_switches.cpp" +#include "radio/src/gui/Flamenco/menu_model_custom_functions.cpp" +// #include "radio/src/gui/Flamenco/menu_model_custom_scripts.cpp" +#include "radio/src/gui/Flamenco/menu_model_gvars.cpp" +#include "radio/src/gui/Flamenco/menu_model_limits.cpp" +#include "radio/src/gui/Flamenco/menu_model_telemetry.cpp" +#include "radio/src/gui/Flamenco/menu_general.cpp" +#include "radio/src/gui/Flamenco/menu_general_setup.cpp" +#include "radio/src/gui/Flamenco/menu_general_sdmanager.cpp" +#include "radio/src/gui/Flamenco/menu_general_trainer.cpp" +#include "radio/src/gui/Flamenco/menu_general_version.cpp" +// #include "radio/src/gui/Flamenco/menu_general_hardware.cpp" +#include "radio/src/gui/Flamenco/menu_general_calib.cpp" +#include "radio/src/gui/Flamenco/view_main.cpp" +#include "radio/src/gui/Flamenco/view_statistics.cpp" +#include "radio/src/gui/Flamenco/view_channels.cpp" +#include "radio/src/gui/Flamenco/view_telemetry.cpp" +#include "radio/src/gui/Flamenco/view_text.cpp" +#include "radio/src/gui/Flamenco/view_about.cpp" +#include "radio/src/gui/Flamenco/lcd.cpp" +#include "radio/src/gui/Flamenco/splash.cpp" +#include "radio/src/targets/Flamenco/tw8823_driver.cpp" +#elif defined(PCBTARANIS) #include "radio/src/bmp.cpp" #include "radio/src/gui/Taranis/fonts.cpp" #include "radio/src/gui/Taranis/widgets.cpp" @@ -224,7 +301,26 @@ namespace NAMESPACE { #include "radio/src/buzzer.cpp" #endif -#if defined(PCBTARANIS) +#if defined(PCBHORUS) +#include "radio/src/targets/Horus/board_horus.cpp" +#include "radio/src/targets/Horus/keys_driver.cpp" +#include "radio/src/targets/Horus/audio_driver.cpp" +#include "radio/src/targets/Horus/telemetry_driver.cpp" +// #include "radio/src/targets/Horus/haptic_driver.cpp" +#include "radio/src/targets/Horus/pulses_driver.cpp" +// #include "radio/src/targets/Horus/rtc_driver.cpp" +#include "radio/src/targets/Horus/trainer_driver.cpp" +#elif defined(PCBFLAMENCO) +#include "radio/src/targets/Flamenco/board_flamenco.cpp" +#include "radio/src/targets/Flamenco/keys_driver.cpp" +#include "radio/src/targets/Flamenco/audio_driver.cpp" +#include "radio/src/targets/Flamenco/telemetry_driver.cpp" +#include "radio/src/targets/Flamenco/eeprom_driver.cpp" +// #include "radio/src/targets/Flamenco/haptic_driver.cpp" +#include "radio/src/targets/Flamenco/pulses_driver.cpp" +// #include "radio/src/targets/Flamenco/rtc_driver.cpp" +#include "radio/src/targets/Flamenco/trainer_driver.cpp" +#elif defined(PCBTARANIS) #include "radio/src/targets/Taranis/board_taranis.cpp" #include "radio/src/targets/Taranis/keys_driver.cpp" #include "radio/src/targets/Taranis/audio_driver.cpp" @@ -255,7 +351,11 @@ namespace NAMESPACE { #elif defined(PCBMEGA2560) #include "radio/src/targets/Mega2560/board_mega2560.cpp" #else -#include "../../../../../radio/src/targets/9x/board_stock.cpp" +#include "radio/src/targets/9x/board_stock.cpp" +#endif + +#if defined(CPUARM) +#include "radio/src/gui/gui_helpers.cpp" #endif #include "radio/src/keys.cpp" @@ -526,7 +626,11 @@ class OpenTxSimulatorFactory: public SimulatorFactory virtual BoardEnum type() { -#if defined(PCBTARANIS) +#if defined(PCBHORUS) + return BOARD_HORUS; +#elif defined(PCBFLAMENCO) + return BOARD_FLAMENCO; +#elif defined(PCBTARANIS) return BOARD_TARANIS; #else return BOARD_STOCK; diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.h b/companion/src/firmwares/opentx/simulator/opentxsimulator.h index a17f2aba5..95cdafd39 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.h +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.h @@ -19,7 +19,15 @@ #include "simulatorinterface.h" -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBHORUS) + #define FLAVOUR "horus" + #define NAMESPACE HORUS + #define EEPROM_VARIANT 0 +#elif defined(PCBFLAMENCO) + #define FLAVOUR "flamenco" + #define NAMESPACE FLAMENCO + #define EEPROM_VARIANT 0 +#elif defined(PCBTARANIS) && defined(REV9E) #define FLAVOUR "taranisx9e" #define NAMESPACE TaranisX9E #define EEPROM_VARIANT 0x8000 diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index c396f13b4..518e3cbc2 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -788,7 +788,13 @@ void startSimulation(QWidget * parent, RadioData & radioData, int modelIdx) } BoardEnum board = GetCurrentFirmware()->getBoard(); SimulatorDialog * sd; - if (IS_TARANIS(board)) { + if (board == BOARD_HORUS) { + sd = new SimulatorDialogHorus(parent, si, flags); + } + else if (board == BOARD_FLAMENCO) { + sd = new SimulatorDialogFlamenco(parent, si, flags); + } + else if (IS_TARANIS(board)) { for (int i=0; igetCapability(Pots); i++) { if (radioData.generalSettings.isPotAvailable(i)) { flags |= (SIMULATOR_FLAGS_S1 << i); diff --git a/companion/src/simulation/CMakeLists.txt b/companion/src/simulation/CMakeLists.txt index bda9fed7f..c5173c094 100644 --- a/companion/src/simulation/CMakeLists.txt +++ b/companion/src/simulation/CMakeLists.txt @@ -9,6 +9,8 @@ set(simulation_SRCS set(simulation_UIS simulatordialog-9x.ui simulatordialog-taranis.ui + simulatordialog-horus.ui + simulatordialog-flamenco.ui telemetrysimu.ui trainersimu.ui debugoutput.ui diff --git a/companion/src/simulation/lcdwidget.h b/companion/src/simulation/lcdwidget.h index 35af6e114..035c8ce8b 100644 --- a/companion/src/simulation/lcdwidget.h +++ b/companion/src/simulation/lcdwidget.h @@ -14,15 +14,15 @@ * */ -#ifndef lcd_widget_h -#define lcd_widget_h +#ifndef _LCD_WIDGET_H_ +#define _LCD_WIDGET_H_ #include #include "appdata.h" -class lcdWidget : public QWidget { +class LcdWidget : public QWidget { public: - lcdWidget(QWidget * parent = 0): + LcdWidget(QWidget * parent = 0): QWidget(parent), lcdBuf(NULL), previousBuf(NULL), @@ -30,10 +30,11 @@ class lcdWidget : public QWidget { { } - ~lcdWidget() + ~LcdWidget() { - if (previousBuf) + if (previousBuf) { free(previousBuf); + } } void setData(unsigned char *buf, int width, int height, int depth=1) @@ -43,7 +44,7 @@ class lcdWidget : public QWidget { lcdHeight = height; lcdDepth = depth; if (depth >= 8) - lcdSize = (width * height) * (depth / 8); + lcdSize = (width * height) * ((depth+7) / 8); else lcdSize = (width * ((height+7)/8)) * depth; previousBuf = (unsigned char *)malloc(lcdSize); @@ -109,7 +110,17 @@ class lcdWidget : public QWidget { { QRgb rgb; - if (lcdDepth >= 8) { + if (lcdDepth == 16) { + for (int x=0; x>11)/0x1F, 255*((z&0x07E0)>>5)/0x3F, 255*(z&0x001F)/0x1F); + p.setPen(rgb); + p.drawPoint(x, y); + } + } + } + else if (lcdDepth == 12) { for (int x=0; x - + 0 @@ -1469,7 +1469,7 @@ QPushButton:checked { - lcdWidget + LcdWidget QWidget
lcdwidget.h
1 diff --git a/companion/src/simulation/simulatordialog-flamenco.ui b/companion/src/simulation/simulatordialog-flamenco.ui new file mode 100644 index 000000000..09d45e6cc --- /dev/null +++ b/companion/src/simulation/simulatordialog-flamenco.ui @@ -0,0 +1,1663 @@ + + + SimulatorDialogFlamenco + + + + 0 + 0 + 726 + 688 + + + + + 0 + 0 + + + + + 739 + 16777215 + + + + Companion Simulator + + + + :/icon.png:/icon.png + + + + 5 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 570 + 5 + + + + + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + + Flamenco Simulator + + + + + + QLayout::SetMinimumSize + + + 0 + + + + + + 0 + 0 + + + + + 120 + 208 + + + + true + + + background:url(:/images/x9l0.png); + + + + + + + + 0 + 0 + + + + + 120 + 208 + + + + background:url(:/images/x9r0.png) + + + + + + + + 0 + 0 + + + + + 320 + 240 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + 438 + 6 + + + + + + + + + + + Outputs + + + + + + + + + + + + + Outputs2 + + + + + + + + + + + + + Gvars + + + + + 10 + 10 + 691 + 231 + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QFrame::Panel + + + QFrame::Raised + + + 2 + + + + 5 + + + 6 + + + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + ^ + + + false + + + + + + + + 0 + 0 + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + v + + + false + + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 5 + 20 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix X + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold X + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + 20 + + + 20 + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 245 + 245 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + SF + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + + + + 1 + + + 1 + + + 0 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + SA + + + Qt::AlignCenter + + + + + + + SB + + + Qt::AlignCenter + + + + + + + SE + + + Qt::AlignCenter + + + + + + + LS + + + Qt::AlignCenter + + + + + + + S1 + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + <- + + + false + + + + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + -> + + + false + + + + + + + + + + + QLayout::SetDefaultConstraint + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 5 + 20 + + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + ^ + + + false + + + + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + v + + + false + + + + + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + S2 + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 245 + 245 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix X + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold X + + + true + + + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + false + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + + + + 1 + + + 0 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + SC + + + Qt::AlignCenter + + + + + + + SD + + + Qt::AlignCenter + + + + + + + SG + + + Qt::AlignCenter + + + + + + + SH + + + Qt::AlignCenter + + + + + + + RS + + + Qt::AlignCenter + + + + + + + 20 + + + 20 + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + <- + + + false + + + + + + + + 0 + 0 + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + -> + + + false + + + + + + + + + + + + + + + LcdWidget + QWidget +
lcdwidget.h
+ 1 +
+ + mySlider + QSlider +
myslider.h
+
+ + xcursorWidget + QWidget +
xcursorwidget.h
+ 1 +
+ + xmenuWidget + QWidget +
xmenuwidget.h
+ 1 +
+
+ + + + +
diff --git a/companion/src/simulation/simulatordialog-horus.ui b/companion/src/simulation/simulatordialog-horus.ui new file mode 100644 index 000000000..fbeb54962 --- /dev/null +++ b/companion/src/simulation/simulatordialog-horus.ui @@ -0,0 +1,1663 @@ + + + SimulatorDialogHorus + + + + 0 + 0 + 726 + 688 + + + + + 0 + 0 + + + + + 739 + 16777215 + + + + Companion Simulator + + + + :/icon.png:/icon.png + + + + 5 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 570 + 5 + + + + + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + + Forus Simulator + + + + + + QLayout::SetMinimumSize + + + 0 + + + + + + 0 + 0 + + + + + 120 + 208 + + + + true + + + background:url(:/images/x9l0.png); + + + + + + + + 0 + 0 + + + + + 120 + 208 + + + + background:url(:/images/x9r0.png) + + + + + + + + 0 + 0 + + + + + 480 + 272 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + 438 + 6 + + + + + + + + + + + Outputs + + + + + + + + + + + + + Outputs2 + + + + + + + + + + + + + Gvars + + + + + 10 + 10 + 691 + 231 + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QFrame::Panel + + + QFrame::Raised + + + 2 + + + + 5 + + + 6 + + + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + ^ + + + false + + + + + + + + 0 + 0 + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + v + + + false + + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 5 + 20 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix X + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold X + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + 20 + + + 20 + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 245 + 245 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + SF + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + + + + 1 + + + 1 + + + 0 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + SA + + + Qt::AlignCenter + + + + + + + SB + + + Qt::AlignCenter + + + + + + + SE + + + Qt::AlignCenter + + + + + + + LS + + + Qt::AlignCenter + + + + + + + S1 + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + <- + + + false + + + + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + -> + + + false + + + + + + + + + + + QLayout::SetDefaultConstraint + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 5 + 20 + + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + ^ + + + false + + + + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + v + + + false + + + + + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + S2 + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 245 + 245 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix Y + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Fix X + + + true + + + + + + + + 8 + + + + QPushButton { + background-color: #EEEEEE; + border-style: outset; + border-width: 1px; + border-radius: 4px; + border-color: black; + padding: 2px; + } + +QPushButton:checked { + background-color: #4CC417; + border-style: inset; + } + + + Hold X + + + true + + + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + 2 + + + 1 + + + 0 + + + Qt::Vertical + + + true + + + false + + + QSlider::TicksBothSides + + + 1 + + + + + + + + 0 + 0 + + + + + 42 + 42 + + + + + + + -1024 + + + 1024 + + + 128 + + + 64.000000000000000 + + + true + + + + + + + + 0 + 0 + + + + + 16777215 + 50 + + + + + + + + + + 1 + + + 0 + + + 0 + + + Qt::Vertical + + + true + + + QSlider::TicksBothSides + + + 1 + + + + + + + SC + + + Qt::AlignCenter + + + + + + + SD + + + Qt::AlignCenter + + + + + + + SG + + + Qt::AlignCenter + + + + + + + SH + + + Qt::AlignCenter + + + + + + + RS + + + Qt::AlignCenter + + + + + + + 20 + + + 20 + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + <- + + + false + + + + + + + + 0 + 0 + + + + Right Double Click to Reset + + + -125 + + + 125 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 23 + 23 + + + + -> + + + false + + + + + + + + + + + + + + + LcdWidget + QWidget +
lcdwidget.h
+ 1 +
+ + mySlider + QSlider +
myslider.h
+
+ + xcursorWidget + QWidget +
xcursorwidget.h
+ 1 +
+ + xmenuWidget + QWidget +
xmenuwidget.h
+ 1 +
+
+ + + + +
diff --git a/companion/src/simulation/simulatordialog-taranis.ui b/companion/src/simulation/simulatordialog-taranis.ui index 34fec2025..422cc5195 100644 --- a/companion/src/simulation/simulatordialog-taranis.ui +++ b/companion/src/simulation/simulatordialog-taranis.ui @@ -146,7 +146,7 @@
- + 0 @@ -1735,7 +1735,7 @@ QPushButton:checked { - lcdWidget + LcdWidget QWidget
lcdwidget.h
1 diff --git a/companion/src/simulation/simulatordialog.cpp b/companion/src/simulation/simulatordialog.cpp index 31fa94406..24d6bba2b 100644 --- a/companion/src/simulation/simulatordialog.cpp +++ b/companion/src/simulation/simulatordialog.cpp @@ -217,6 +217,7 @@ SimulatorDialogTaranis::~SimulatorDialogTaranis() delete ui; } + SimulatorDialog::~SimulatorDialog() { traceCallbackInstance = 0; @@ -1134,3 +1135,6 @@ void SimulatorDialog::onjoystickAxisValueChanged(int axis, int value) } } #endif + +#include "simulatordialogflamenco.cpp" +#include "simulatordialoghorus.cpp" diff --git a/companion/src/simulation/simulatordialog.h b/companion/src/simulation/simulatordialog.h index 18f30dfdf..44af808e6 100644 --- a/companion/src/simulation/simulatordialog.h +++ b/companion/src/simulation/simulatordialog.h @@ -21,10 +21,12 @@ namespace Ui { class SimulatorDialog9X; class SimulatorDialogTaranis; + class SimulatorDialogFlamenco; + class SimulatorDialogHorus; } // TODO rename + move? -class lcdWidget; +class LcdWidget; class mySlider; #define SIMULATOR_FLAGS_NOTX 1 @@ -38,6 +40,8 @@ class mySlider; #define SIMULATOR_FLAGS_S3_MULTI 256 #define SIMULATOR_FLAGS_S4_MULTI 512 // reserved for the future +void traceCb(const char * text); + class SimulatorDialog : public QDialog { Q_OBJECT @@ -56,7 +60,7 @@ class SimulatorDialog : public QDialog virtual void updateBeepButton() { } unsigned int flags; - lcdWidget * lcd; + LcdWidget * lcd; QGraphicsView * leftStick, * rightStick; QVector pots; QVector potLabels; @@ -205,4 +209,48 @@ class SimulatorDialogTaranis: public SimulatorDialog void on_switchH_sliderReleased(); }; +class SimulatorDialogFlamenco: public SimulatorDialog +{ + Q_OBJECT + + public: + explicit SimulatorDialogFlamenco(QWidget * parent, SimulatorInterface *simulator, unsigned int flags=0); + virtual ~SimulatorDialogFlamenco(); + + protected: + virtual void getValues(); + void saveSwitches(void); + void restoreSwitches(void); + + private: + Ui::SimulatorDialogFlamenco * ui; + static uint32_t switchstatus; + + private slots: + void resetSH(); + void on_switchH_sliderReleased(); +}; + +class SimulatorDialogHorus: public SimulatorDialog +{ + Q_OBJECT + + public: + explicit SimulatorDialogHorus(QWidget * parent, SimulatorInterface *simulator, unsigned int flags=0); + virtual ~SimulatorDialogHorus(); + + protected: + virtual void getValues(); + void saveSwitches(void); + void restoreSwitches(void); + + private: + Ui::SimulatorDialogHorus * ui; + static uint32_t switchstatus; + + private slots: + void resetSH(); + void on_switchH_sliderReleased(); +}; + #endif // SIMULATORDIALOG_H diff --git a/companion/src/simulation/simulatordialogflamenco.cpp b/companion/src/simulation/simulatordialogflamenco.cpp new file mode 100644 index 000000000..364c5a819 --- /dev/null +++ b/companion/src/simulation/simulatordialogflamenco.cpp @@ -0,0 +1,166 @@ +#include "simulatordialog.h" +#include "ui_simulatordialog-flamenco.h" +#include "helpers.h" + +uint32_t SimulatorDialogFlamenco::switchstatus = 0; + +SimulatorDialogFlamenco::SimulatorDialogFlamenco(QWidget * parent, SimulatorInterface *simulator, unsigned int flags): + SimulatorDialog(parent, simulator, flags), + ui(new Ui::SimulatorDialogFlamenco) +{ + lcdWidth = 320; + lcdHeight = 240; + lcdDepth = 12; + + initUi(ui); + + // install simulator TRACE hook + simulator->installTraceHook(traceCb); + + // TODO dialP_4 = ui->dialP_4; + + ui->lcd->setBackgroundColor(47, 123, 227); + + //restore switches + if (g.simuSW()) + restoreSwitches(); + + ui->trimHR_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHR_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVR_U->setText(QString::fromUtf8(upArrow)); + ui->trimVR_D->setText(QString::fromUtf8(downArrow)); + ui->trimHL_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHL_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVL_U->setText(QString::fromUtf8(upArrow)); + ui->trimVL_D->setText(QString::fromUtf8(downArrow)); + + connect(ui->cursor, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->menu, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHR_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_D, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); +} + +SimulatorDialogFlamenco::~SimulatorDialogFlamenco() +{ + saveSwitches(); + delete ui; +} + +void SimulatorDialogFlamenco::resetSH() +{ + ui->switchH->setValue(0); +} + +void SimulatorDialogFlamenco::on_switchH_sliderReleased() +{ + QTimer::singleShot(400, this, SLOT(resetSH())); +} + +void SimulatorDialogFlamenco::getValues() +{ + TxInputs inputs = { + { + int(1024*nodeLeft->getX()), // LEFT HORZ + int(-1024*nodeLeft->getY()), // LEFT VERT + int(-1024*nodeRight->getY()), // RGHT VERT + int(1024*nodeRight->getX()) // RGHT HORZ + }, + + { + -ui->dialP_1->value(), + ui->dialP_2->value(), + 0, + -ui->dialP_3->value(), + ui->dialP_4->value() + }, + + { + ui->switchA->value() - 1, + ui->switchB->value() - 1, + ui->switchC->value() - 1, + ui->switchD->value() - 1, + ui->switchE->value() - 1, + ui->switchF->value(), + ui->switchG->value() - 1, + ui->switchH->value(), 0, 0 + }, + + { + buttonPressed == Qt::Key_PageUp, + buttonPressed == Qt::Key_Escape, + buttonPressed == Qt::Key_Enter, + buttonPressed == Qt::Key_PageDown, + buttonPressed == Qt::Key_Plus, + buttonPressed == Qt::Key_Minus + }, + + middleButtonPressed, + + { + trimPressed == TRIM_LH_L, + trimPressed == TRIM_LH_R, + trimPressed == TRIM_LV_DN, + trimPressed == TRIM_LV_UP, + trimPressed == TRIM_RV_DN, + trimPressed == TRIM_RV_UP, + trimPressed == TRIM_RH_L, + trimPressed == TRIM_RH_R + } + }; + + simulator->setValues(inputs); +} + +void SimulatorDialogFlamenco::saveSwitches(void) +{ + // qDebug() << "SimulatorDialogTaranis::saveSwitches()"; + switchstatus=ui->switchA->value(); + switchstatus<<=2; + switchstatus+=ui->switchB->value(); + switchstatus<<=2; + switchstatus+=ui->switchC->value(); + switchstatus<<=2; + switchstatus+=ui->switchD->value(); + switchstatus<<=2; + switchstatus+=ui->switchE->value(); + switchstatus<<=2; + switchstatus+=ui->switchF->value(); + switchstatus<<=2; + switchstatus+=ui->switchG->value(); + switchstatus<<=2; + switchstatus+=ui->switchH->value(); +} + +void SimulatorDialogFlamenco::restoreSwitches(void) +{ + // qDebug() << "SimulatorDialogTaranis::restoreSwitches()"; + ui->switchH->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchG->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchF->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchE->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchD->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchC->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchB->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchA->setValue(switchstatus & 0x3); +} diff --git a/companion/src/simulation/simulatordialoghorus.cpp b/companion/src/simulation/simulatordialoghorus.cpp new file mode 100644 index 000000000..df64a61b0 --- /dev/null +++ b/companion/src/simulation/simulatordialoghorus.cpp @@ -0,0 +1,166 @@ +#include "simulatordialog.h" +#include "ui_simulatordialog-horus.h" +#include "helpers.h" + +uint32_t SimulatorDialogHorus::switchstatus = 0; + +SimulatorDialogHorus::SimulatorDialogHorus(QWidget * parent, SimulatorInterface *simulator, unsigned int flags): + SimulatorDialog(parent, simulator, flags), + ui(new Ui::SimulatorDialogHorus) +{ + lcdWidth = 480; + lcdHeight = 270; + lcdDepth = 16; + + initUi(ui); + + // install simulator TRACE hook + simulator->installTraceHook(traceCb); + + // TODO dialP_4 = ui->dialP_4; + + ui->lcd->setBackgroundColor(47, 123, 227); + + //restore switches + if (g.simuSW()) + restoreSwitches(); + + ui->trimHR_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHR_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVR_U->setText(QString::fromUtf8(upArrow)); + ui->trimVR_D->setText(QString::fromUtf8(downArrow)); + ui->trimHL_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHL_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVL_U->setText(QString::fromUtf8(upArrow)); + ui->trimVL_D->setText(QString::fromUtf8(downArrow)); + + connect(ui->cursor, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->menu, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHR_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_D, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); +} + +SimulatorDialogHorus::~SimulatorDialogHorus() +{ + saveSwitches(); + delete ui; +} + +void SimulatorDialogHorus::resetSH() +{ + ui->switchH->setValue(0); +} + +void SimulatorDialogHorus::on_switchH_sliderReleased() +{ + QTimer::singleShot(400, this, SLOT(resetSH())); +} + +void SimulatorDialogHorus::getValues() +{ + TxInputs inputs = { + { + int(1024*nodeLeft->getX()), // LEFT HORZ + int(-1024*nodeLeft->getY()), // LEFT VERT + int(-1024*nodeRight->getY()), // RGHT VERT + int(1024*nodeRight->getX()) // RGHT HORZ + }, + + { + -ui->dialP_1->value(), + ui->dialP_2->value(), + 0, + -ui->dialP_3->value(), + ui->dialP_4->value() + }, + + { + ui->switchA->value() - 1, + ui->switchB->value() - 1, + ui->switchC->value() - 1, + ui->switchD->value() - 1, + ui->switchE->value() - 1, + ui->switchF->value(), + ui->switchG->value() - 1, + ui->switchH->value(), 0, 0 + }, + + { + buttonPressed == Qt::Key_PageUp, + buttonPressed == Qt::Key_Escape, + buttonPressed == Qt::Key_Enter, + buttonPressed == Qt::Key_PageDown, + buttonPressed == Qt::Key_Plus, + buttonPressed == Qt::Key_Minus + }, + + middleButtonPressed, + + { + trimPressed == TRIM_LH_L, + trimPressed == TRIM_LH_R, + trimPressed == TRIM_LV_DN, + trimPressed == TRIM_LV_UP, + trimPressed == TRIM_RV_DN, + trimPressed == TRIM_RV_UP, + trimPressed == TRIM_RH_L, + trimPressed == TRIM_RH_R + } + }; + + simulator->setValues(inputs); +} + +void SimulatorDialogHorus::saveSwitches(void) +{ + // qDebug() << "SimulatorDialogTaranis::saveSwitches()"; + switchstatus=ui->switchA->value(); + switchstatus<<=2; + switchstatus+=ui->switchB->value(); + switchstatus<<=2; + switchstatus+=ui->switchC->value(); + switchstatus<<=2; + switchstatus+=ui->switchD->value(); + switchstatus<<=2; + switchstatus+=ui->switchE->value(); + switchstatus<<=2; + switchstatus+=ui->switchF->value(); + switchstatus<<=2; + switchstatus+=ui->switchG->value(); + switchstatus<<=2; + switchstatus+=ui->switchH->value(); +} + +void SimulatorDialogHorus::restoreSwitches(void) +{ + // qDebug() << "SimulatorDialogTaranis::restoreSwitches()"; + ui->switchH->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchG->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchF->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchE->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchD->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchC->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchB->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchA->setValue(switchstatus & 0x3); +} diff --git a/companion/src/simulation/simulatorimport.h b/companion/src/simulation/simulatorimport.h index 0f4fee47e..59a835a57 100644 --- a/companion/src/simulation/simulatorimport.h +++ b/companion/src/simulation/simulatorimport.h @@ -50,7 +50,7 @@ if (inputs.rotenc) pind |= 0x20; if (inputs.rotenc) PIOB->PIO_PDSR &= ~0x40; else PIOB->PIO_PDSR |= 0x40; #endif -#if defined(PCBTARANIS) && defined(REV9E) +#if defined(PCBFLAMENCO) || (defined(PCBTARANIS) && defined(REV9E)) if (inputs.rotenc) simuSetKey(KEY_ENTER, true); #endif #endif diff --git a/companion/src/simulator.cpp b/companion/src/simulator.cpp index 45e22ffd8..3f923be7d 100644 --- a/companion/src/simulator.cpp +++ b/companion/src/simulator.cpp @@ -160,7 +160,11 @@ int main(int argc, char *argv[]) eepromFileName = QString("eeprom-%1.bin").arg(radioId); eepromFileName = eedir.filePath(eepromFileName.toAscii()); SimulatorFactory *factory = getSimulatorFactory(firmwareId); - if (factory->type() == BOARD_TARANIS) + if (factory->type() == BOARD_HORUS) + dialog = new SimulatorDialogHorus(NULL, factory->create()); + else if (factory->type() == BOARD_FLAMENCO) + dialog = new SimulatorDialogFlamenco(NULL, factory->create()); + else if (factory->type() == BOARD_TARANIS) dialog = new SimulatorDialogTaranis(NULL, factory->create(), SIMULATOR_FLAGS_S1|SIMULATOR_FLAGS_S2); else dialog = new SimulatorDialog9X(NULL, factory->create()); diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp index 4162a1edb..fe0f450dc 100644 --- a/radio/src/gui/Horus/menu_model_select.cpp +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -35,56 +35,6 @@ #include "../../opentx.h" -#define POS_MODEL_NAME 35 -#define POS_MODEL_SIZE 170 - -void onModelSelectMenu(const char *result) -{ - int sub = m_posVert; - - if (result == STR_SELECT_MODEL || result == STR_CREATE_MODEL) { - selectModel(sub); - } - else if (result == STR_COPY_MODEL) { - s_copyMode = COPY_MODE; - s_copyTgtOfs = 0; - s_copySrcRow = -1; - } - else if (result == STR_MOVE_MODEL) { - s_copyMode = MOVE_MODE; - s_copyTgtOfs = 0; - s_copySrcRow = -1; - } - else if (result == STR_BACKUP_MODEL) { - storageCheck(true); // force writing of current model data before this is changed - POPUP_WARNING(eeBackupModel(sub)); - } - else if (result == STR_RESTORE_MODEL || result == STR_UPDATE_LIST) { - if (!sdListFiles(MODELS_PATH, MODELS_EXT, MENU_LINE_LENGTH-1, NULL)) { - POPUP_WARNING(STR_NO_MODELS_ON_SD); - s_menu_flags = 0; - } - } - else if (result == STR_DELETE_MODEL) { - POPUP_CONFIRMATION(STR_DELETEMODEL); - // SET_WARNING_INFO(modelHeaders[sub].name, sizeof(g_model.header.name), ZCHAR); - } - else { - // The user choosed a file on SD to restore - POPUP_WARNING(eeRestoreModel(sub, (char *)result)); - // if (!s_warning && g_eeGeneral.currModel == sub) - // eeLoadModel(sub); - } -} - -char *strAppendModelSize(char *dest, int size) -{ - char s[8] = "---"; - if (size) sprintf(s, "%d", size); - dest = strSetCursor(dest, POS_MODEL_SIZE-getTextWidth(s)); - return strAppend(dest, s); -} - #define CATEGORIES_WIDTH 140 void menuModelSelect(evt_t event) diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h index d890a1feb..8363231c2 100644 --- a/radio/src/gui/Horus/menus.h +++ b/radio/src/gui/Horus/menus.h @@ -59,6 +59,7 @@ typedef evt_t & check_event_t; extern uint8_t menuPageIndex; extern uint8_t menuPageCount; +extern uint16_t linesCount; extern vertpos_t m_posVert; extern horzpos_t m_posHorz; extern vertpos_t s_pgOfs; diff --git a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_dma2d.h b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_dma2d.h index 0e456ed36..1dc95de1c 100644 --- a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_dma2d.h +++ b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_dma2d.h @@ -35,7 +35,7 @@ #endif /* Includes ------------------------------------------------------------------*/ -#include "stm32f4xx.h" +#include "../../CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h" /** @addtogroup STM32F4xx_StdPeriph_Driver * @{ diff --git a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_fmc.h b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_fmc.h index 41b3c64e9..a2157f4d0 100644 --- a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_fmc.h +++ b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_fmc.h @@ -35,7 +35,7 @@ #endif /* Includes ------------------------------------------------------------------*/ -#include "stm32f4xx.h" +#include "../../CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h" /** @addtogroup STM32F4xx_StdPeriph_Driver * @{ diff --git a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_ltdc.h b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_ltdc.h index 1c67a77fb..e94d2d09e 100644 --- a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_ltdc.h +++ b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_ltdc.h @@ -35,7 +35,7 @@ #endif /* Includes ------------------------------------------------------------------*/ -#include "stm32f4xx.h" +#include "../../CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h" /** @addtogroup STM32F4xx_StdPeriph_Driver * @{ diff --git a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_tim.h b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_tim.h index adc8140e5..3fc4d0db7 100644 --- a/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_tim.h +++ b/radio/src/thirdparty/STM32F4xx_DSP_StdPeriph_Lib_V1.4.0/Libraries/STM32F4xx_StdPeriph_Driver/inc/stm32f4xx_tim.h @@ -35,7 +35,7 @@ #endif /* Includes ------------------------------------------------------------------*/ -#include "stm32f4xx.h" +#include "../../CMSIS/Device/ST/STM32F4xx/Include/stm32f4xx.h" /** @addtogroup STM32F4xx_StdPeriph_Driver * @{ From 0673663427960971ac1d02a1d3b263be9138f436 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 24 Oct 2015 18:27:08 +0200 Subject: [PATCH 37/74] [Horus] Simulator refactoring. This one should be nice :) --- .../opentx/simulator/opentxsimulator.cpp | 7 +- companion/src/simulation/CMakeLists.txt | 8 +- companion/src/simulation/buttonswidget.h | 120 ++++++ companion/src/simulation/cursorwidget.h | 106 ----- companion/src/simulation/dialwidget.h | 25 ++ companion/src/simulation/lcdwidget.h | 10 +- companion/src/simulation/menuwidget.h | 73 ---- companion/src/simulation/myqdial.h | 21 - companion/src/simulation/myslider.h | 31 -- .../src/simulation/simulatordialog-9x.ui | 115 +++-- .../simulation/simulatordialog-flamenco.ui | 58 ++- .../src/simulation/simulatordialog-horus.ui | 140 +++--- .../src/simulation/simulatordialog-taranis.ui | 59 +-- companion/src/simulation/simulatordialog.cpp | 406 +----------------- companion/src/simulation/simulatordialog.h | 4 +- .../src/simulation/simulatordialog9x.cpp | 225 ++++++++++ .../simulation/simulatordialogflamenco.cpp | 4 +- .../src/simulation/simulatordialoghorus.cpp | 26 +- .../src/simulation/simulatordialogtaranis.cpp | 201 +++++++++ companion/src/simulation/sliderwidget.h | 30 ++ companion/src/simulation/xcursorwidget.h | 100 ----- companion/src/simulation/xmenuwidget.h | 95 ---- radio/src/simu.cpp | 1 - radio/util/font2png.py | 4 +- 24 files changed, 863 insertions(+), 1006 deletions(-) create mode 100644 companion/src/simulation/buttonswidget.h delete mode 100644 companion/src/simulation/cursorwidget.h create mode 100644 companion/src/simulation/dialwidget.h delete mode 100644 companion/src/simulation/menuwidget.h delete mode 100644 companion/src/simulation/myqdial.h delete mode 100644 companion/src/simulation/myslider.h create mode 100644 companion/src/simulation/simulatordialog9x.cpp create mode 100644 companion/src/simulation/simulatordialogtaranis.cpp create mode 100644 companion/src/simulation/sliderwidget.h delete mode 100644 companion/src/simulation/xcursorwidget.h delete mode 100644 companion/src/simulation/xmenuwidget.h diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp index 4d452c312..51f204ecd 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp @@ -559,7 +559,12 @@ void OpenTxSimulator::getTrims(Trims & trims) void OpenTxSimulator::wheelEvent(int steps) { -#if defined(REV9E) +#if defined(PCBHORUS) || defined(PCBFLAMENCO) + if (steps > 0) + rotencValue -= 2; + else + rotencValue += 2; +#elif defined(REV9E) if (steps == 255) rotencValue -= 2; else diff --git a/companion/src/simulation/CMakeLists.txt b/companion/src/simulation/CMakeLists.txt index c5173c094..8a6249219 100644 --- a/companion/src/simulation/CMakeLists.txt +++ b/companion/src/simulation/CMakeLists.txt @@ -18,11 +18,9 @@ set(simulation_UIS set(simulation_HDRS simulatordialog.h - cursorwidget.h - menuwidget.h - xcursorwidget.h - xmenuwidget.h - myslider.h + # lcdwidget.h + buttonswidget.h + sliderwidget.h telemetrysimu.h trainersimu.h debugoutput.h diff --git a/companion/src/simulation/buttonswidget.h b/companion/src/simulation/buttonswidget.h new file mode 100644 index 000000000..80d09ee06 --- /dev/null +++ b/companion/src/simulation/buttonswidget.h @@ -0,0 +1,120 @@ +/* + * Author - Bertrand Songis + * + * Based on th9x -> http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +#ifndef _BUTTONSWIDGET_H_ +#define _BUTTONSWIDGET_H_ + +#include +#include + +class ButtonsWidget : public QWidget +{ + + Q_OBJECT + + public: + + explicit ButtonsWidget(QWidget * parent): + QWidget(parent) + { + } + + + virtual void setStyleSheet(const QString & sheet) + { + defaultStyleSheet = sheet; + QWidget::setStyleSheet(sheet); + } + + void addArea(int x1, int y1, int x2, int y2, int key, const char * bitmap) + { + QPolygon polygon; + polygon.setPoints(4, x1, y1, x1, y2, x2, y2, x2, y1); + addArea(polygon, key, bitmap); + } + + void addArea(const QPolygon & polygon, int key, const char * bitmap) + { + areas.push_back(Area(polygon, key, bitmap)); + } + + protected: + + class Area { + public: + Area(const QPolygon & polygon, int key, const char * bitmap): + polygon(polygon), + key(key), + bitmap(bitmap) + { + } + bool contains(int x, int y) + { + return polygon.containsPoint(QPoint(x, y), Qt::OddEvenFill); + } + public: + QPolygon polygon; + int key; + const char * bitmap; + }; + + void setBitmap(const char * bitmap) + { + QWidget::setStyleSheet(QString("background:url(:/images/%1);").arg(bitmap)); + } + + virtual void mousePressEvent(QMouseEvent * event) + { + int x = event->x(); + int y = event->y(); + setFocus(); + if (event->button() == Qt::LeftButton) { + foreach(Area area, areas) { + if (area.contains(x, y)) { + setBitmap(area.bitmap); + emit buttonPressed(area.key); + break; + } + } + } + } + + virtual void mouseReleaseEvent(QMouseEvent * event) + { + QWidget::setStyleSheet(defaultStyleSheet); + setFocus(); + emit buttonPressed(0); + } + + void paintEvent(QPaintEvent *) + { + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + } + + signals: + + void buttonPressed(int button); + + protected: + + QList areas; + QString defaultStyleSheet; +}; + +#endif /* _BUTTONSWIDGET_H_ */ diff --git a/companion/src/simulation/cursorwidget.h b/companion/src/simulation/cursorwidget.h deleted file mode 100644 index 7d35b343e..000000000 --- a/companion/src/simulation/cursorwidget.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Author - Bertrand Songis - * - * Based on th9x -> http://code.google.com/p/th9x/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program 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. - * - */ -#ifndef CURSORWIDGET_H -#define CURSORWIDGET_H - -#include -#include - -class cursorWidget : public QWidget { - - Q_OBJECT - - public: - - explicit cursorWidget(QWidget * parent = 0): - QWidget(parent) - { - } - - protected: - - virtual void mousePressEvent(QMouseEvent * event) - { - float upx[] = {68,28,51,83,105,68}; - float upy[] = {83,45,32,32,45,83}; - float minx[] = {74,114,127,127,114,74}; - float miny[] = {90,51,80,106,130,90}; - float dox[] = {68,28,51,83,105,68}; - float doy[] = {98,137,151,151,137,98}; - float plusx[] = { 80,20,7,7,20,80}; - float plusy[] = {90,51,80,106,130,90}; - - int x=event->x(); - int y=event->y(); - setFocus(); - if (event->button()==Qt::LeftButton) { - if (pnpoly(6,upx,upy,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/9xcursup.png);"); - emit buttonPressed(Qt::Key_Up); - } - else if (pnpoly(6,minx,miny,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/9xcursmin.png);"); - emit buttonPressed(Qt::Key_Right); - } - else if (pnpoly(6,dox,doy,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/9xcursdown.png);"); - emit buttonPressed(Qt::Key_Down); - } - else if (pnpoly(6,plusx,plusy,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/9xcursplus.png);"); - emit buttonPressed(Qt::Key_Left); - } else if (sqrt(((float)x-22)*((float)x-22)+((float)y-165)*((float)y-165))<17) { - setStyleSheet("background:url(:/images/9xcursphoto.png);"); - emit buttonPressed(Qt::Key_Print); - } - } - // QWidget::mousePressEvent(event); - } - - virtual void mouseReleaseEvent(QMouseEvent * event) - { - setStyleSheet("background:url(:/images/9xcurs.png);"); - emit buttonPressed(0); - setFocus(); - //QWidget::mouseReleaseEvent(event); - } - - void paintEvent(QPaintEvent *) - { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - } - - inline int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy) - { - int i, j, c = 0; - for (i = 0, j = nvert-1; i < nvert; j = i++) { - if ( ((verty[i]>testy) != (verty[j]>testy)) && (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ) - c = !c; - } - return c; - } - - signals: - void buttonPressed(int button); - -}; - - -#endif /* CURSORWIDGET_H */ - diff --git a/companion/src/simulation/dialwidget.h b/companion/src/simulation/dialwidget.h new file mode 100644 index 000000000..d84099a61 --- /dev/null +++ b/companion/src/simulation/dialwidget.h @@ -0,0 +1,25 @@ +#ifndef _DIALWIDGET_H_ +#define _DIALWIDGET_H_ + +class DialWidget : public QDial +{ + public: + + explicit DialWidget(QWidget * parent = 0): + QDial(parent) + { + } + + protected: + + void mousePressEvent(QMouseEvent * event) + { + if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) { + setValue(0); + event->accept(); + } + QDial::mousePressEvent(event); + } +}; + +#endif // _DIALWIDGET_H_ diff --git a/companion/src/simulation/lcdwidget.h b/companion/src/simulation/lcdwidget.h index 035c8ce8b..12dbbc8bb 100644 --- a/companion/src/simulation/lcdwidget.h +++ b/companion/src/simulation/lcdwidget.h @@ -14,14 +14,16 @@ * */ -#ifndef _LCD_WIDGET_H_ -#define _LCD_WIDGET_H_ +#ifndef _LCDWIDGET_H_ +#define _LCDWIDGET_H_ #include #include "appdata.h" -class LcdWidget : public QWidget { +class LcdWidget : public QWidget +{ public: + LcdWidget(QWidget * parent = 0): QWidget(parent), lcdBuf(NULL), @@ -186,4 +188,4 @@ class LcdWidget : public QWidget { }; -#endif // _LCD_WIDGET_H_ +#endif // _LCDWIDGET_H_ diff --git a/companion/src/simulation/menuwidget.h b/companion/src/simulation/menuwidget.h deleted file mode 100644 index 9974286eb..000000000 --- a/companion/src/simulation/menuwidget.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Author - Bertrand Songis - * - * Based on th9x -> http://code.google.com/p/th9x/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program 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. - * - */ -#ifndef MENUWIDGET_H -#define MENUWIDGET_H - -#include -#include - -class menuWidget : public QWidget { - - Q_OBJECT - - public: - - explicit menuWidget(QWidget * parent = 0): - QWidget(parent) - { - } - - protected: - - virtual void mousePressEvent(QMouseEvent * event) - { - int x=event->x(); - int y=event->y(); - setFocus(); - if (event->button()==Qt::LeftButton) { - if (x>25 && x<71 && y>60 && y<81) { - setStyleSheet("background:url(:/images/9xmenumenu.png);"); - emit buttonPressed(Qt::Key_Enter); - } else if (x>25 && x<71 && y>117 && y<139) { - setStyleSheet("background:url(:/images/9xmenuexit.png);"); - emit buttonPressed(Qt::Key_Escape); - } - } - // QWidget::mousePressEvent(event); - } - - virtual void mouseReleaseEvent(QMouseEvent * event) - { - setStyleSheet("background:url(:/images/9xmenu.png);"); - setFocus(); - emit buttonPressed(0); - // QWidget::mouseReleaseEvent(event); - } - - void paintEvent(QPaintEvent *) - { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - } - - signals: - void buttonPressed(int button); - -}; -#endif /* MENUWIDGET_H */ - diff --git a/companion/src/simulation/myqdial.h b/companion/src/simulation/myqdial.h deleted file mode 100644 index 54f76473e..000000000 --- a/companion/src/simulation/myqdial.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef MYQDIAL_H -#define MYQDIAL_H - - -class myQDial : public QDial -{ -public: - explicit myQDial(QWidget *parent = 0) : QDial(parent) {} - -protected: - void mousePressEvent(QMouseEvent *event) - { - if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) { - setValue(0); - event->accept(); - } - QDial::mousePressEvent(event); - } -}; - -#endif diff --git a/companion/src/simulation/myslider.h b/companion/src/simulation/myslider.h deleted file mode 100644 index d2c6801a5..000000000 --- a/companion/src/simulation/myslider.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef MYSLIDER_H -#define MYSLIDER_H - -#include -#include - -class mySlider : public QSlider -{ - Q_OBJECT - - public: - - explicit mySlider(QFrame * parent = 0): - QSlider(parent) - { - } - - -protected: - - void mousePressEvent(QMouseEvent * event) - { - if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) { - setValue(0); - event->accept(); - } - QSlider::mousePressEvent(event); - } -}; - -#endif diff --git a/companion/src/simulation/simulatordialog-9x.ui b/companion/src/simulation/simulatordialog-9x.ui index a4bd8be46..085eb56b3 100644 --- a/companion/src/simulation/simulatordialog-9x.ui +++ b/companion/src/simulation/simulatordialog-9x.ui @@ -30,7 +30,16 @@ :/icon.png:/icon.png - + + 5 + + + 5 + + + 5 + + 5 @@ -66,7 +75,16 @@ 2 - + + 5 + + + 5 + + + 5 + + 5 @@ -399,7 +417,7 @@ QPushButton:checked {
- + Right Double Click to Reset @@ -463,7 +481,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -868,7 +886,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -932,7 +950,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -990,7 +1008,16 @@ QPushButton:checked { 2 - + + 2 + + + 2 + + + 2 + + 2 @@ -1020,7 +1047,7 @@ QPushButton:checked { - + 0 @@ -1077,7 +1104,7 @@ QPushButton:checked { - + 0 @@ -1137,7 +1164,7 @@ QPushButton:checked { - + 0 @@ -1286,7 +1313,7 @@ QPushButton:checked { - + 0 @@ -1339,8 +1366,27 @@ QPushButton:checked { + + + + + 0 + 0 + + + + + 55 + 186 + + + + background:url(:/images/9xdr.png) + + + - + 0 @@ -1361,25 +1407,6 @@ QPushButton:checked { - - - - - 0 - 0 - - - - - 55 - 186 - - - - background:url(:/images/9xdr.png) - - - @@ -1475,26 +1502,20 @@ QPushButton:checked { 1 - mySlider + SliderWidget QSlider -
myslider.h
+
sliderwidget.h
- cursorWidget - QWidget -
cursorwidget.h
- 1 -
- - menuWidget - QWidget -
menuwidget.h
- 1 -
- - myQDial + DialWidget QDial -
myqdial.h
+
dialwidget.h
+
+ + ButtonsWidget + QWidget +
buttonswidget.h
+ 1
diff --git a/companion/src/simulation/simulatordialog-flamenco.ui b/companion/src/simulation/simulatordialog-flamenco.ui index 09d45e6cc..74beb1638 100644 --- a/companion/src/simulation/simulatordialog-flamenco.ui +++ b/companion/src/simulation/simulatordialog-flamenco.ui @@ -7,7 +7,7 @@ 0 0 726 - 688 + 708 @@ -30,7 +30,16 @@ :/icon.png:/icon.png - + + 5 + + + 5 + + + 5 + + 5 @@ -86,7 +95,7 @@ 0 - + 0 @@ -108,7 +117,7 @@ - + 0 @@ -263,7 +272,16 @@ 2 - + + 5 + + + 5 + + + 5 + + 5 @@ -298,7 +316,7 @@ - + 0 @@ -902,7 +920,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -991,7 +1009,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -1579,7 +1597,7 @@ QPushButton:checked { - + 0 @@ -1639,21 +1657,15 @@ QPushButton:checked { 1 - mySlider + ButtonsWidget + QWidget +
buttonswidget.h
+ 1 +
+ + SliderWidget QSlider -
myslider.h
-
- - xcursorWidget - QWidget -
xcursorwidget.h
- 1 -
- - xmenuWidget - QWidget -
xmenuwidget.h
- 1 +
sliderwidget.h
diff --git a/companion/src/simulation/simulatordialog-horus.ui b/companion/src/simulation/simulatordialog-horus.ui index fbeb54962..c51e173f9 100644 --- a/companion/src/simulation/simulatordialog-horus.ui +++ b/companion/src/simulation/simulatordialog-horus.ui @@ -6,8 +6,8 @@ 0 0 - 726 - 688 + 739 + 740
@@ -30,7 +30,16 @@ :/icon.png:/icon.png - + + 5 + + + 5 + + + 5 + + 5 @@ -78,54 +87,13 @@ - + QLayout::SetMinimumSize 0 - - - - - 0 - 0 - - - - - 120 - 208 - - - - true - - - background:url(:/images/x9l0.png); - - - - - - - - 0 - 0 - - - - - 120 - 208 - - - - background:url(:/images/x9r0.png) - - - @@ -142,6 +110,47 @@ + + + + + 0 + 0 + + + + + 89 + 186 + + + + true + + + background:url(:/images/9xmenu.png); + + + + + + + + 0 + 0 + + + + + 128 + 186 + + + + background:url(:/images/9xcurs.png) + + + @@ -263,7 +272,16 @@ 2 - + + 5 + + + 5 + + + 5 + + 5 @@ -298,7 +316,7 @@
- + 0 @@ -902,7 +920,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -991,7 +1009,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -1579,7 +1597,7 @@ QPushButton:checked { - + 0 @@ -1639,21 +1657,15 @@ QPushButton:checked { 1 - mySlider + ButtonsWidget + QWidget +
buttonswidget.h
+ 1 +
+ + SliderWidget QSlider -
myslider.h
-
- - xcursorWidget - QWidget -
xcursorwidget.h
- 1 -
- - xmenuWidget - QWidget -
xmenuwidget.h
- 1 +
sliderwidget.h
diff --git a/companion/src/simulation/simulatordialog-taranis.ui b/companion/src/simulation/simulatordialog-taranis.ui index 422cc5195..65c040d92 100644 --- a/companion/src/simulation/simulatordialog-taranis.ui +++ b/companion/src/simulation/simulatordialog-taranis.ui @@ -7,7 +7,7 @@ 0 0 739 - 658 + 695
@@ -30,7 +30,16 @@ :/icon.png:/icon.png - + + 5 + + + 5 + + + 5 + + 5 @@ -86,7 +95,7 @@ 0 - + 0 @@ -127,7 +136,7 @@ - + 0 @@ -864,7 +873,7 @@ - + 0 @@ -904,7 +913,7 @@ - + 0 @@ -935,7 +944,7 @@ - + 0 @@ -966,7 +975,7 @@ - + 0 @@ -1248,7 +1257,7 @@ QPushButton:checked { - + 0 @@ -1320,7 +1329,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -1420,7 +1429,7 @@ QPushButton:checked { - + Right Double Click to Reset @@ -1638,7 +1647,7 @@ QPushButton:checked { - + 0 @@ -1741,26 +1750,20 @@ QPushButton:checked { 1 - mySlider + ButtonsWidget + QWidget +
buttonswidget.h
+ 1 +
+ + SliderWidget QSlider -
myslider.h
+
sliderwidget.h
- xcursorWidget - QWidget -
xcursorwidget.h
- 1 -
- - xmenuWidget - QWidget -
xmenuwidget.h
- 1 -
- - myQDial + DialWidget QDial -
myqdial.h
+
dialwidget.h
diff --git a/companion/src/simulation/simulatordialog.cpp b/companion/src/simulation/simulatordialog.cpp index 24d6bba2b..fbf96467f 100644 --- a/companion/src/simulation/simulatordialog.cpp +++ b/companion/src/simulation/simulatordialog.cpp @@ -1,9 +1,10 @@ #include "simulatordialog.h" -#include "ui_simulatordialog-9x.h" -#include "ui_simulatordialog-taranis.h" +#include "appdata.h" +#include "lcdwidget.h" #include #include "helpers.h" #include "simulatorinterface.h" +#include "sliderwidget.h" #define GBALL_SIZE 20 #define RESX 1024 @@ -47,6 +48,11 @@ void SimulatorDialog::updateDebugOutput() traceMutex.unlock(); } +void SimulatorDialog::wheelEvent (QWheelEvent *event) +{ + simulator->wheelEvent(event->delta() > 0 ? 1 : -1); +} + SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator, unsigned int flags): QDialog(parent), flags(flags), @@ -70,154 +76,6 @@ SimulatorDialog::SimulatorDialog(QWidget * parent, SimulatorInterface *simulator traceCallbackInstance = this; } -uint32_t SimulatorDialog9X::switchstatus = 0; - -SimulatorDialog9X::SimulatorDialog9X(QWidget * parent, SimulatorInterface *simulator, unsigned int flags): - SimulatorDialog(parent, simulator, flags), - ui(new Ui::SimulatorDialog9X), - beepShow(0) -{ - lcdWidth = 128; - lcdHeight = 64; - lcdDepth = 1; - - initUi(ui); - - // install simulator TRACE hook - simulator->installTraceHook(traceCb); - - backLight = g.backLight(); - if (backLight > 4) backLight = 0; - switch (backLight) { - case 1: - ui->lcd->setBackgroundColor(166,247,159); - break; - case 2: - ui->lcd->setBackgroundColor(247,159,166); - break; - case 3: - ui->lcd->setBackgroundColor(255,195,151); - break; - case 4: - ui->lcd->setBackgroundColor(247,242,159); - break; - default: - ui->lcd->setBackgroundColor(159,165,247); - break; - } - - //restore switches - if (g.simuSW()) - restoreSwitches(); - - ui->trimHR_L->setText(QString::fromUtf8(leftArrow)); - ui->trimHR_R->setText(QString::fromUtf8(rightArrow)); - ui->trimVR_U->setText(QString::fromUtf8(upArrow)); - ui->trimVR_D->setText(QString::fromUtf8(downArrow)); - ui->trimHL_L->setText(QString::fromUtf8(leftArrow)); - ui->trimHL_R->setText(QString::fromUtf8(rightArrow)); - ui->trimVL_U->setText(QString::fromUtf8(upArrow)); - ui->trimVL_D->setText(QString::fromUtf8(downArrow)); - for (int i=0; isetProperty("index", i); - connect(pots[i], SIGNAL(valueChanged(int)), this, SLOT(dialChanged(int))); - } - connect(ui->cursor, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); - connect(ui->menu, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); - connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVR_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHL_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHL_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVL_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVL_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHR_L, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimHR_R, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVR_U, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVR_D, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimHL_R, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); -} - -SimulatorDialog9X::~SimulatorDialog9X() -{ - saveSwitches(); - delete ui; -} - -uint32_t SimulatorDialogTaranis::switchstatus = 0; - -SimulatorDialogTaranis::SimulatorDialogTaranis(QWidget * parent, SimulatorInterface *simulator, unsigned int flags): - SimulatorDialog(parent, simulator, flags), - ui(new Ui::SimulatorDialogTaranis) -{ - lcdWidth = 212; - lcdHeight = 64; - lcdDepth = 4; - - initUi(ui); - - // install simulator TRACE hook - simulator->installTraceHook(traceCb); - - ui->lcd->setBackgroundColor(47, 123, 227); - - // restore switches - if (g.simuSW()) { - restoreSwitches(); - } - - for (int i=0; isetValue(-1024); - pots[i]->setSingleStep(2048/5); - pots[i]->setPageStep(2048/5); - } - else if (!(flags & (SIMULATOR_FLAGS_S1 << i))) { - pots[i]->hide(); - potLabels[i]->hide(); - } - } - - ui->trimHR_L->setText(QString::fromUtf8(leftArrow)); - ui->trimHR_R->setText(QString::fromUtf8(rightArrow)); - ui->trimVR_U->setText(QString::fromUtf8(upArrow)); - ui->trimVR_D->setText(QString::fromUtf8(downArrow)); - ui->trimHL_L->setText(QString::fromUtf8(leftArrow)); - ui->trimHL_R->setText(QString::fromUtf8(rightArrow)); - ui->trimVL_U->setText(QString::fromUtf8(upArrow)); - ui->trimVL_D->setText(QString::fromUtf8(downArrow)); - - connect(ui->cursor, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); - connect(ui->menu, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); - connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVR_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHL_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHL_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVL_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimVL_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); - connect(ui->trimHR_L, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimHR_R, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVR_U, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVR_D, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimHL_R, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); - connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); -} - -SimulatorDialogTaranis::~SimulatorDialogTaranis() -{ - saveSwitches(); - delete ui; -} - - SimulatorDialog::~SimulatorDialog() { traceCallbackInstance = 0; @@ -245,17 +103,6 @@ void SimulatorDialog::mouseReleaseEvent(QMouseEvent *event) } } -void SimulatorDialog9X::dialChanged(int value) -{ - int index = sender()->property("index").toInt(); - potValues[index]->setText(QString("%1 %").arg((value*100)/1024)); -} - -void SimulatorDialog::wheelEvent (QWheelEvent *event) -{ - simulator->wheelEvent(event->delta() > 0 ? 1 : -1); -} - void SimulatorDialog::onTrimPressed() { if (sender()->objectName() == QString("trimHL_L")) @@ -600,36 +447,6 @@ void SimulatorDialog::onButtonPressed(int value) } } -void SimulatorDialog9X::setLightOn(bool enable) -{ - QString bg = ""; - if (enable) { - QStringList list; - list << "bl" << "gr" << "rd" << "or" << "yl"; - bg = QString("-") + list[backLight]; - } - ui->top->setStyleSheet(QString("background:url(:/images/9xdt%1.png);").arg(bg)); - ui->bottom->setStyleSheet(QString("background:url(:/images/9xdb%1.png);").arg(bg)); - ui->left->setStyleSheet(QString("background:url(:/images/9xdl%1.png);").arg(bg)); - ui->right->setStyleSheet(QString("background:url(:/images/9xdr%1.png);").arg(bg)); -} - -void SimulatorDialog9X::updateBeepButton() -{ - #define CBEEP_ON "QLabel { background-color: #FF364E }" - #define CBEEP_OFF "QLabel { }" - - if (beepVal) { - beepShow = 20; - } - - ui->label_beep->setStyleSheet(beepShow ? CBEEP_ON : CBEEP_OFF); - - if (beepShow) { - beepShow--; - } -} - void SimulatorDialog::onTimerEvent() { static unsigned int lcd_counter = 0; @@ -729,211 +546,6 @@ void SimulatorDialog::setTrims() trimHRight->setRange(trimMin, trimMax); trimHRight->setValue(trims.values[3]); } -void SimulatorDialog9X::getValues() -{ - TxInputs inputs = { - { - int(1024*nodeLeft->getX()), // LEFT HORZ - int(-1024*nodeLeft->getY()), // LEFT VERT - int(-1024*nodeRight->getY()), // RGHT VERT - int(1024*nodeRight->getX()) // RGHT HORZ - }, - - { - pots[0]->value(), - pots[1]->value(), - pots[2]->value() - }, - - { - ui->switchTHR->isChecked(), - ui->switchRUD->isChecked(), - ui->switchELE->isChecked(), - ui->switchID2->isChecked() ? 1 : (ui->switchID1->isChecked() ? 0 : -1), - ui->switchAIL->isChecked(), - ui->switchGEA->isChecked(), - ui->switchTRN->isDown(), - 0, 0, 0 - }, - - { - buttonPressed == Qt::Key_Enter, - buttonPressed == Qt::Key_Escape, - buttonPressed == Qt::Key_Down, - buttonPressed == Qt::Key_Up, - buttonPressed == Qt::Key_Right, - buttonPressed == Qt::Key_Left, - }, - - middleButtonPressed, - - { - trimPressed == TRIM_LH_L, - trimPressed == TRIM_LH_R, - trimPressed == TRIM_LV_DN, - trimPressed == TRIM_LV_UP, - trimPressed == TRIM_RV_DN, - trimPressed == TRIM_RV_UP, - trimPressed == TRIM_RH_L, - trimPressed == TRIM_RH_R - } - }; - - simulator->setValues(inputs); -} - -void SimulatorDialog9X::saveSwitches(void) -{ - // qDebug() << "SimulatorDialog9X::saveSwitches()"; - switchstatus=ui->switchTHR->isChecked(); - switchstatus<<=1; - switchstatus+=(ui->switchRUD->isChecked()&0x1); - switchstatus<<=1; - switchstatus+=(ui->switchID2->isChecked()&0x1); - switchstatus<<=1; - switchstatus+=(ui->switchID1->isChecked()&0x1); - switchstatus<<=1; - switchstatus+=(ui->switchID0->isChecked()&0x1); - switchstatus<<=1; - switchstatus+=(ui->switchGEA->isChecked()&0x1); - switchstatus<<=1; - switchstatus+=(ui->switchELE->isChecked()&0x1); - switchstatus<<=1; - switchstatus+=(ui->switchAIL->isChecked()&0x1); -} - -void SimulatorDialog9X::restoreSwitches(void) -{ - // qDebug() << "SimulatorDialog9X::restoreSwitches()"; - ui->switchAIL->setChecked(switchstatus & 0x1); - switchstatus >>=1; - ui->switchELE->setChecked(switchstatus & 0x1); - switchstatus >>=1; - ui->switchGEA->setChecked(switchstatus & 0x1); - switchstatus >>=1; - ui->switchID0->setChecked(switchstatus & 0x1); - switchstatus >>=1; - ui->switchID1->setChecked(switchstatus & 0x1); - switchstatus >>=1; - ui->switchID2->setChecked(switchstatus & 0x1); - switchstatus >>=1; - ui->switchRUD->setChecked(switchstatus & 0x1); - switchstatus >>=1; - ui->switchTHR->setChecked(switchstatus & 0x1); -} - -void SimulatorDialogTaranis::resetSH() -{ - ui->switchH->setValue(0); -} - -void SimulatorDialogTaranis::on_switchH_sliderReleased() -{ - QTimer::singleShot(400, this, SLOT(resetSH())); -} - -void SimulatorDialogTaranis::getValues() -{ - for (int i=0; ivalue()+1024)/(2048.0/5))*(2048.0/5)-1024; - pots[i]->setValue(s1); - } - } - - TxInputs inputs = { - { - int(1024*nodeLeft->getX()), // LEFT HORZ - int(-1024*nodeLeft->getY()), // LEFT VERT - int(-1024*nodeRight->getY()), // RGHT VERT - int(1024*nodeRight->getX()) // RGHT HORZ - }, - - { - -pots[0]->value(), - pots[1]->value(), - ((flags && SIMULATOR_FLAGS_S3) ? pots[2]->value() : 0), - -sliders[0]->value(), - sliders[1]->value() - }, - - { - ui->switchA->value() - 1, - ui->switchB->value() - 1, - ui->switchC->value() - 1, - ui->switchD->value() - 1, - ui->switchE->value() - 1, - ui->switchF->value(), - ui->switchG->value() - 1, - ui->switchH->value(), 0, 0 - }, - - { - buttonPressed == Qt::Key_PageUp, - buttonPressed == Qt::Key_Escape, - buttonPressed == Qt::Key_Enter, - buttonPressed == Qt::Key_PageDown, - buttonPressed == Qt::Key_Plus, - buttonPressed == Qt::Key_Minus - }, - - middleButtonPressed, - - { - trimPressed == TRIM_LH_L, - trimPressed == TRIM_LH_R, - trimPressed == TRIM_LV_DN, - trimPressed == TRIM_LV_UP, - trimPressed == TRIM_RV_DN, - trimPressed == TRIM_RV_UP, - trimPressed == TRIM_RH_L, - trimPressed == TRIM_RH_R - } - }; - - simulator->setValues(inputs); -} - -void SimulatorDialogTaranis::saveSwitches(void) -{ - // qDebug() << "SimulatorDialogTaranis::saveSwitches()"; - switchstatus=ui->switchA->value(); - switchstatus<<=2; - switchstatus+=ui->switchB->value(); - switchstatus<<=2; - switchstatus+=ui->switchC->value(); - switchstatus<<=2; - switchstatus+=ui->switchD->value(); - switchstatus<<=2; - switchstatus+=ui->switchE->value(); - switchstatus<<=2; - switchstatus+=ui->switchF->value(); - switchstatus<<=2; - switchstatus+=ui->switchG->value(); - switchstatus<<=2; - switchstatus+=ui->switchH->value(); -} - -void SimulatorDialogTaranis::restoreSwitches(void) -{ - // qDebug() << "SimulatorDialogTaranis::restoreSwitches()"; - ui->switchH->setValue(switchstatus & 0x3); - switchstatus>>=2; - ui->switchG->setValue(switchstatus & 0x3); - switchstatus>>=2; - ui->switchF->setValue(switchstatus & 0x3); - switchstatus>>=2; - ui->switchE->setValue(switchstatus & 0x3); - switchstatus>>=2; - ui->switchD->setValue(switchstatus & 0x3); - switchstatus>>=2; - ui->switchC->setValue(switchstatus & 0x3); - switchstatus>>=2; - ui->switchB->setValue(switchstatus & 0x3); - switchstatus>>=2; - ui->switchA->setValue(switchstatus & 0x3); -} - inline int chVal(int val) { return qMin(1024, qMax(-1024, val)); @@ -1136,5 +748,7 @@ void SimulatorDialog::onjoystickAxisValueChanged(int axis, int value) } #endif +#include "simulatordialog9x.cpp" +#include "simulatordialogtaranis.cpp" #include "simulatordialogflamenco.cpp" #include "simulatordialoghorus.cpp" diff --git a/companion/src/simulation/simulatordialog.h b/companion/src/simulation/simulatordialog.h index 44af808e6..c63ba6d0b 100644 --- a/companion/src/simulation/simulatordialog.h +++ b/companion/src/simulation/simulatordialog.h @@ -27,7 +27,7 @@ namespace Ui { // TODO rename + move? class LcdWidget; -class mySlider; +class SliderWidget; #define SIMULATOR_FLAGS_NOTX 1 #define SIMULATOR_FLAGS_STICK_MODE_LEFT 2 @@ -67,7 +67,7 @@ class SimulatorDialog : public QDialog QVector potValues; QVector sliders; - mySlider * trimHLeft, * trimVLeft, * trimHRight, * trimVRight; + SliderWidget * trimHLeft, * trimVLeft, * trimHRight, * trimVRight; QLabel * leftXPerc, * rightXPerc, * leftYPerc, * rightYPerc; QTabWidget * tabWidget; QVector logicalSwitchLabels; diff --git a/companion/src/simulation/simulatordialog9x.cpp b/companion/src/simulation/simulatordialog9x.cpp new file mode 100644 index 000000000..3b84c8064 --- /dev/null +++ b/companion/src/simulation/simulatordialog9x.cpp @@ -0,0 +1,225 @@ +#include "ui_simulatordialog-9x.h" + +uint32_t SimulatorDialog9X::switchstatus = 0; + +SimulatorDialog9X::SimulatorDialog9X(QWidget * parent, SimulatorInterface *simulator, unsigned int flags): + SimulatorDialog(parent, simulator, flags), + ui(new Ui::SimulatorDialog9X), + beepShow(0) +{ + QPolygon polygon; + + lcdWidth = 128; + lcdHeight = 64; + lcdDepth = 1; + + initUi(ui); + + polygon.setPoints(6, 68, 83, 28, 45, 51, 32, 83, 32, 105, 45, 68, 83); + ui->leftbuttons->addArea(polygon, Qt::Key_Up, "9xcursup.png"); + polygon.setPoints(6, 74, 90, 114, 51, 127, 80, 127, 106, 114, 130, 74, 90); + ui->leftbuttons->addArea(polygon, Qt::Key_Right, "9xcursmin.png"); + polygon.setPoints(6, 68, 98, 28, 137, 51, 151, 83, 151, 105, 137, 68, 98); + ui->leftbuttons->addArea(polygon, Qt::Key_Down, "9xcursdown.png"); + polygon.setPoints(6, 80, 90, 20, 51, 7, 80, 7, 106, 20, 130, 80, 90); + ui->leftbuttons->addArea(polygon, Qt::Key_Left, "9xcursplus.png"); + ui->leftbuttons->addArea(5, 148, 39, 182, Qt::Key_Print, "9xcursphoto.png"); + + ui->rightbuttons->addArea(25, 60, 71, 81, Qt::Key_Enter, "9xmenumenu.png"); + ui->rightbuttons->addArea(25, 117, 71, 139, Qt::Key_Escape, "9xmenuexit.png"); + + // install simulator TRACE hook + simulator->installTraceHook(traceCb); + + backLight = g.backLight(); + if (backLight > 4) backLight = 0; + switch (backLight) { + case 1: + ui->lcd->setBackgroundColor(166,247,159); + break; + case 2: + ui->lcd->setBackgroundColor(247,159,166); + break; + case 3: + ui->lcd->setBackgroundColor(255,195,151); + break; + case 4: + ui->lcd->setBackgroundColor(247,242,159); + break; + default: + ui->lcd->setBackgroundColor(159,165,247); + break; + } + + //restore switches + if (g.simuSW()) + restoreSwitches(); + + ui->trimHR_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHR_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVR_U->setText(QString::fromUtf8(upArrow)); + ui->trimVR_D->setText(QString::fromUtf8(downArrow)); + ui->trimHL_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHL_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVL_U->setText(QString::fromUtf8(upArrow)); + ui->trimVL_D->setText(QString::fromUtf8(downArrow)); + for (int i=0; isetProperty("index", i); + connect(pots[i], SIGNAL(valueChanged(int)), this, SLOT(dialChanged(int))); + } + connect(ui->leftbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->rightbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHR_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_D, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); +} + +SimulatorDialog9X::~SimulatorDialog9X() +{ + saveSwitches(); + delete ui; +} + +void SimulatorDialog9X::dialChanged(int value) +{ + int index = sender()->property("index").toInt(); + potValues[index]->setText(QString("%1 %").arg((value*100)/1024)); +} + +void SimulatorDialog9X::setLightOn(bool enable) +{ + QString bg = ""; + if (enable) { + QStringList list; + list << "bl" << "gr" << "rd" << "or" << "yl"; + bg = QString("-") + list[backLight]; + } + ui->top->setStyleSheet(QString("background:url(:/images/9xdt%1.png);").arg(bg)); + ui->bottom->setStyleSheet(QString("background:url(:/images/9xdb%1.png);").arg(bg)); + ui->left->setStyleSheet(QString("background:url(:/images/9xdl%1.png);").arg(bg)); + ui->right->setStyleSheet(QString("background:url(:/images/9xdr%1.png);").arg(bg)); +} + +void SimulatorDialog9X::updateBeepButton() +{ + #define CBEEP_ON "QLabel { background-color: #FF364E }" + #define CBEEP_OFF "QLabel { }" + + if (beepVal) { + beepShow = 20; + } + + ui->label_beep->setStyleSheet(beepShow ? CBEEP_ON : CBEEP_OFF); + + if (beepShow) { + beepShow--; + } +} + + +void SimulatorDialog9X::getValues() +{ + TxInputs inputs = { + { + int(1024*nodeLeft->getX()), // LEFT HORZ + int(-1024*nodeLeft->getY()), // LEFT VERT + int(-1024*nodeRight->getY()), // RGHT VERT + int(1024*nodeRight->getX()) // RGHT HORZ + }, + + { + pots[0]->value(), + pots[1]->value(), + pots[2]->value() + }, + + { + ui->switchTHR->isChecked(), + ui->switchRUD->isChecked(), + ui->switchELE->isChecked(), + ui->switchID2->isChecked() ? 1 : (ui->switchID1->isChecked() ? 0 : -1), + ui->switchAIL->isChecked(), + ui->switchGEA->isChecked(), + ui->switchTRN->isDown(), + 0, 0, 0 + }, + + { + buttonPressed == Qt::Key_Enter, + buttonPressed == Qt::Key_Escape, + buttonPressed == Qt::Key_Down, + buttonPressed == Qt::Key_Up, + buttonPressed == Qt::Key_Right, + buttonPressed == Qt::Key_Left, + }, + + middleButtonPressed, + + { + trimPressed == TRIM_LH_L, + trimPressed == TRIM_LH_R, + trimPressed == TRIM_LV_DN, + trimPressed == TRIM_LV_UP, + trimPressed == TRIM_RV_DN, + trimPressed == TRIM_RV_UP, + trimPressed == TRIM_RH_L, + trimPressed == TRIM_RH_R + } + }; + + simulator->setValues(inputs); +} + +void SimulatorDialog9X::saveSwitches(void) +{ + // qDebug() << "SimulatorDialog9X::saveSwitches()"; + switchstatus=ui->switchTHR->isChecked(); + switchstatus<<=1; + switchstatus+=(ui->switchRUD->isChecked()&0x1); + switchstatus<<=1; + switchstatus+=(ui->switchID2->isChecked()&0x1); + switchstatus<<=1; + switchstatus+=(ui->switchID1->isChecked()&0x1); + switchstatus<<=1; + switchstatus+=(ui->switchID0->isChecked()&0x1); + switchstatus<<=1; + switchstatus+=(ui->switchGEA->isChecked()&0x1); + switchstatus<<=1; + switchstatus+=(ui->switchELE->isChecked()&0x1); + switchstatus<<=1; + switchstatus+=(ui->switchAIL->isChecked()&0x1); +} + +void SimulatorDialog9X::restoreSwitches(void) +{ + // qDebug() << "SimulatorDialog9X::restoreSwitches()"; + ui->switchAIL->setChecked(switchstatus & 0x1); + switchstatus >>=1; + ui->switchELE->setChecked(switchstatus & 0x1); + switchstatus >>=1; + ui->switchGEA->setChecked(switchstatus & 0x1); + switchstatus >>=1; + ui->switchID0->setChecked(switchstatus & 0x1); + switchstatus >>=1; + ui->switchID1->setChecked(switchstatus & 0x1); + switchstatus >>=1; + ui->switchID2->setChecked(switchstatus & 0x1); + switchstatus >>=1; + ui->switchRUD->setChecked(switchstatus & 0x1); + switchstatus >>=1; + ui->switchTHR->setChecked(switchstatus & 0x1); +} + diff --git a/companion/src/simulation/simulatordialogflamenco.cpp b/companion/src/simulation/simulatordialogflamenco.cpp index 364c5a819..c3ecac913 100644 --- a/companion/src/simulation/simulatordialogflamenco.cpp +++ b/companion/src/simulation/simulatordialogflamenco.cpp @@ -34,8 +34,8 @@ SimulatorDialogFlamenco::SimulatorDialogFlamenco(QWidget * parent, SimulatorInte ui->trimVL_U->setText(QString::fromUtf8(upArrow)); ui->trimVL_D->setText(QString::fromUtf8(downArrow)); - connect(ui->cursor, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); - connect(ui->menu, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->leftbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->rightbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); diff --git a/companion/src/simulation/simulatordialoghorus.cpp b/companion/src/simulation/simulatordialoghorus.cpp index df64a61b0..3bd7733bb 100644 --- a/companion/src/simulation/simulatordialoghorus.cpp +++ b/companion/src/simulation/simulatordialoghorus.cpp @@ -8,12 +8,27 @@ SimulatorDialogHorus::SimulatorDialogHorus(QWidget * parent, SimulatorInterface SimulatorDialog(parent, simulator, flags), ui(new Ui::SimulatorDialogHorus) { + QPolygon polygon; + lcdWidth = 480; lcdHeight = 270; lcdDepth = 16; initUi(ui); + polygon.setPoints(6, 68, 83, 28, 45, 51, 32, 83, 32, 105, 45, 68, 83); + ui->rightbuttons->addArea(polygon, Qt::Key_Up, "9xcursup.png"); + polygon.setPoints(6, 74, 90, 114, 51, 127, 80, 127, 106, 114, 130, 74, 90); + ui->rightbuttons->addArea(polygon, Qt::Key_Right, "9xcursmin.png"); + polygon.setPoints(6, 68, 98, 28, 137, 51, 151, 83, 151, 105, 137, 68, 98); + ui->rightbuttons->addArea(polygon, Qt::Key_Down, "9xcursdown.png"); + polygon.setPoints(6, 80, 90, 20, 51, 7, 80, 7, 106, 20, 130, 80, 90); + ui->rightbuttons->addArea(polygon, Qt::Key_Left, "9xcursplus.png"); + ui->rightbuttons->addArea(5, 148, 39, 182, Qt::Key_Print, "9xcursphoto.png"); + + ui->leftbuttons->addArea(25, 60, 71, 81, Qt::Key_PageUp, "9xmenumenu.png"); + ui->leftbuttons->addArea(25, 117, 71, 139, Qt::Key_Escape, "9xmenuexit.png"); + // install simulator TRACE hook simulator->installTraceHook(traceCb); @@ -34,8 +49,8 @@ SimulatorDialogHorus::SimulatorDialogHorus(QWidget * parent, SimulatorInterface ui->trimVL_U->setText(QString::fromUtf8(upArrow)); ui->trimVL_D->setText(QString::fromUtf8(downArrow)); - connect(ui->cursor, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); - connect(ui->menu, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->leftbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->rightbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); @@ -103,9 +118,10 @@ void SimulatorDialogHorus::getValues() buttonPressed == Qt::Key_PageUp, buttonPressed == Qt::Key_Escape, buttonPressed == Qt::Key_Enter, - buttonPressed == Qt::Key_PageDown, - buttonPressed == Qt::Key_Plus, - buttonPressed == Qt::Key_Minus + buttonPressed == Qt::Key_Up, + buttonPressed == Qt::Key_Down, + buttonPressed == Qt::Key_Right, + buttonPressed == Qt::Key_Left }, middleButtonPressed, diff --git a/companion/src/simulation/simulatordialogtaranis.cpp b/companion/src/simulation/simulatordialogtaranis.cpp new file mode 100644 index 000000000..432601a65 --- /dev/null +++ b/companion/src/simulation/simulatordialogtaranis.cpp @@ -0,0 +1,201 @@ +#include "ui_simulatordialog-taranis.h" + +uint32_t SimulatorDialogTaranis::switchstatus = 0; + +SimulatorDialogTaranis::SimulatorDialogTaranis(QWidget * parent, SimulatorInterface *simulator, unsigned int flags): + SimulatorDialog(parent, simulator, flags), + ui(new Ui::SimulatorDialogTaranis) +{ + QPolygon polygon; + + lcdWidth = 212; + lcdHeight = 64; + lcdDepth = 4; + + initUi(ui); + + polygon.setPoints(6, 20, 59, 27, 50, 45, 52, 56, 59, 50, 71, 26, 72); + ui->leftbuttons->addArea(polygon, Qt::Key_PageUp, "x9l1.png"); + polygon.setPoints(6, 23, 107, 30, 99, 46, 100, 55, 106, 47, 117, 28, 117); + ui->leftbuttons->addArea(polygon, Qt::Key_PageDown, "x9l2.png"); + polygon.setPoints(6, 24, 154, 32, 144, 46, 146, 57, 156, 46, 167, 29, 166); + ui->leftbuttons->addArea(polygon, Qt::Key_Escape, "x9l3.png"); + ui->leftbuttons->addArea(90, 177, 118, 197, Qt::Key_Print, "x9l4.png"); + polygon.setPoints(6, 64, 60, 71, 50, 90, 50, 100, 60, 90, 73, 72, 73); + ui->rightbuttons->addArea(polygon, Qt::Key_Plus, "x9r1.png"); + polygon.setPoints(6, 63, 109, 73, 100, 88, 100, 98, 109, 88, 119, 72, 119); + ui->rightbuttons->addArea(polygon, Qt::Key_Minus, "x9r2.png"); + polygon.setPoints(6, 63, 155, 72, 146, 90, 146, 98, 155, 88, 166, 72, 166); + ui->rightbuttons->addArea(polygon, Qt::Key_Enter, "x9r3.png"); + + // install simulator TRACE hook + simulator->installTraceHook(traceCb); + + ui->lcd->setBackgroundColor(47, 123, 227); + + // restore switches + if (g.simuSW()) { + restoreSwitches(); + } + + for (int i=0; isetValue(-1024); + pots[i]->setSingleStep(2048/5); + pots[i]->setPageStep(2048/5); + } + else if (!(flags & (SIMULATOR_FLAGS_S1 << i))) { + pots[i]->hide(); + potLabels[i]->hide(); + } + } + + ui->trimHR_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHR_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVR_U->setText(QString::fromUtf8(upArrow)); + ui->trimVR_D->setText(QString::fromUtf8(downArrow)); + ui->trimHL_L->setText(QString::fromUtf8(leftArrow)); + ui->trimHL_R->setText(QString::fromUtf8(rightArrow)); + ui->trimVL_U->setText(QString::fromUtf8(upArrow)); + ui->trimVL_D->setText(QString::fromUtf8(downArrow)); + + connect(ui->leftbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->rightbuttons, SIGNAL(buttonPressed(int)), this, SLOT(onButtonPressed(int))); + connect(ui->trimHR_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVR_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_R, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHL_L, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_U, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimVL_D, SIGNAL(pressed()), this, SLOT(onTrimPressed())); + connect(ui->trimHR_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHR_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVR_D, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_R, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimHL_L, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_U, SIGNAL(released()), this, SLOT(onTrimReleased())); + connect(ui->trimVL_D, SIGNAL(released()), this, SLOT(onTrimReleased())); +} + +SimulatorDialogTaranis::~SimulatorDialogTaranis() +{ + saveSwitches(); + delete ui; +} + + +void SimulatorDialogTaranis::resetSH() +{ + ui->switchH->setValue(0); +} + +void SimulatorDialogTaranis::on_switchH_sliderReleased() +{ + QTimer::singleShot(400, this, SLOT(resetSH())); +} + +void SimulatorDialogTaranis::getValues() +{ + for (int i=0; ivalue()+1024)/(2048.0/5))*(2048.0/5)-1024; + pots[i]->setValue(s1); + } + } + + TxInputs inputs = { + { + int(1024*nodeLeft->getX()), // LEFT HORZ + int(-1024*nodeLeft->getY()), // LEFT VERT + int(-1024*nodeRight->getY()), // RGHT VERT + int(1024*nodeRight->getX()) // RGHT HORZ + }, + + { + -pots[0]->value(), + pots[1]->value(), + ((flags && SIMULATOR_FLAGS_S3) ? pots[2]->value() : 0), + -sliders[0]->value(), + sliders[1]->value() + }, + + { + ui->switchA->value() - 1, + ui->switchB->value() - 1, + ui->switchC->value() - 1, + ui->switchD->value() - 1, + ui->switchE->value() - 1, + ui->switchF->value(), + ui->switchG->value() - 1, + ui->switchH->value(), 0, 0 + }, + + { + buttonPressed == Qt::Key_PageUp, + buttonPressed == Qt::Key_Escape, + buttonPressed == Qt::Key_Enter, + buttonPressed == Qt::Key_PageDown, + buttonPressed == Qt::Key_Plus, + buttonPressed == Qt::Key_Minus + }, + + middleButtonPressed, + + { + trimPressed == TRIM_LH_L, + trimPressed == TRIM_LH_R, + trimPressed == TRIM_LV_DN, + trimPressed == TRIM_LV_UP, + trimPressed == TRIM_RV_DN, + trimPressed == TRIM_RV_UP, + trimPressed == TRIM_RH_L, + trimPressed == TRIM_RH_R + } + }; + + simulator->setValues(inputs); +} + +void SimulatorDialogTaranis::saveSwitches(void) +{ + // qDebug() << "SimulatorDialogTaranis::saveSwitches()"; + switchstatus=ui->switchA->value(); + switchstatus<<=2; + switchstatus+=ui->switchB->value(); + switchstatus<<=2; + switchstatus+=ui->switchC->value(); + switchstatus<<=2; + switchstatus+=ui->switchD->value(); + switchstatus<<=2; + switchstatus+=ui->switchE->value(); + switchstatus<<=2; + switchstatus+=ui->switchF->value(); + switchstatus<<=2; + switchstatus+=ui->switchG->value(); + switchstatus<<=2; + switchstatus+=ui->switchH->value(); +} + +void SimulatorDialogTaranis::restoreSwitches(void) +{ + // qDebug() << "SimulatorDialogTaranis::restoreSwitches()"; + ui->switchH->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchG->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchF->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchE->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchD->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchC->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchB->setValue(switchstatus & 0x3); + switchstatus>>=2; + ui->switchA->setValue(switchstatus & 0x3); +} + + diff --git a/companion/src/simulation/sliderwidget.h b/companion/src/simulation/sliderwidget.h new file mode 100644 index 000000000..90f094cb6 --- /dev/null +++ b/companion/src/simulation/sliderwidget.h @@ -0,0 +1,30 @@ +#ifndef _SLIDERWIDGET_H_ +#define _SLIDERWIDGET_H_ + +#include +#include + +class SliderWidget : public QSlider +{ + Q_OBJECT + + public: + + explicit SliderWidget(QFrame * parent = 0): + QSlider(parent) + { + } + + protected: + + void mousePressEvent(QMouseEvent * event) + { + if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick) { + setValue(0); + event->accept(); + } + QSlider::mousePressEvent(event); + } +}; + +#endif // _SLIDERWIDGET_H_ diff --git a/companion/src/simulation/xcursorwidget.h b/companion/src/simulation/xcursorwidget.h deleted file mode 100644 index 36c024149..000000000 --- a/companion/src/simulation/xcursorwidget.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Author - Bertrand Songis - * - * Based on th9x -> http://code.google.com/p/th9x/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program 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. - * - */ -#ifndef XCURSORWIDGET_H -#define XCURSORWIDGET_H - -#include -#include - -class xcursorWidget : public QWidget { - - Q_OBJECT - - public: - - explicit xcursorWidget(QWidget * parent = 0): - QWidget(parent) - { - } - - protected: - - virtual void mousePressEvent(QMouseEvent * event) - { - float p1x[] = {20,27,45,56,50,26}; - float p1y[] = {59,50,52,59,71,72}; - float p2x[] = {23,30,46,55,47,28}; - float p2y[] = {107,99,100,106,117,117}; - float p3x[] = {24,32,46,57,46,29}; - float p3y[] = {154,144,146,156,167,166}; - - int x=event->x(); - int y=event->y(); - setFocus(); - if (event->button()==Qt::LeftButton) { - if (pnpoly(6,p1x,p1y,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/x9l1.png);"); - emit buttonPressed(Qt::Key_PageUp); - } - else if (pnpoly(6,p2x,p2y,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/x9l2.png);"); - emit buttonPressed(Qt::Key_PageDown); - } - else if (pnpoly(6,p3x,p3y,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/x9l3.png);"); - emit buttonPressed(Qt::Key_Escape); - } else if ((x>90 && x<118) && (y>177 && y<197)) { - setStyleSheet("background:url(:/images/x9l4.png);"); - emit buttonPressed(Qt::Key_Print); - } - } - // QWidget::mousePressEvent(event); - } - - virtual void mouseReleaseEvent(QMouseEvent * event) - { - setStyleSheet("background:url(:/images/x9l0.png);"); - emit buttonPressed(0); - setFocus(); - //QWidget::mouseReleaseEvent(event); - } - - void paintEvent(QPaintEvent *) - { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - } - - inline int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy) - { - int i, j, c = 0; - for (i = 0, j = nvert-1; i < nvert; j = i++) { - if ( ((verty[i]>testy) != (verty[j]>testy)) && (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ) - c = !c; - } - return c; - } - - signals: - void buttonPressed(int button); - -}; - - -#endif /* CURSORWIDGET_H */ - diff --git a/companion/src/simulation/xmenuwidget.h b/companion/src/simulation/xmenuwidget.h deleted file mode 100644 index 6bcf7795c..000000000 --- a/companion/src/simulation/xmenuwidget.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Author - Bertrand Songis - * - * Based on th9x -> http://code.google.com/p/th9x/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program 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. - * - */ -#ifndef XMENUWIDGET_H -#define XMENUWIDGET_H - -#include -#include - -class xmenuWidget : public QWidget { - - Q_OBJECT - - public: - - explicit xmenuWidget(QWidget * parent = 0): - QWidget(parent) - { - } - - protected: - - virtual void mousePressEvent(QMouseEvent * event) - { - float p1x[] = {64,71,90,100,90,72}; - float p1y[] = {60,50,50,60,73,73}; - float p2x[] = {63,73,88,98,88,72}; - float p2y[] = {109,100,100,109,119,119}; - float p3x[] = {63,72,90,98,88,72}; - float p3y[] = {155,146,146,155,166,166}; - - int x=event->x(); - int y=event->y(); - setFocus(); - if (event->button()==Qt::LeftButton) { - if (pnpoly(6,p1x,p1y,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/x9r1.png);"); - emit buttonPressed(Qt::Key_Plus); - } - else if (pnpoly(6,p2x,p2y,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/x9r2.png);"); - emit buttonPressed(Qt::Key_Minus); - } - else if (pnpoly(6,p3x,p3y,(float)x,(float)y)==1) { - setStyleSheet("background:url(:/images/x9r3.png);"); - emit buttonPressed(Qt::Key_Enter); - } - } - // QWidget::mousePressEvent(event); - } - - virtual void mouseReleaseEvent(QMouseEvent * event) - { - setStyleSheet("background:url(:/images/x9r0.png);"); - setFocus(); - emit buttonPressed(0); - // QWidget::mouseReleaseEvent(event); - } - - void paintEvent(QPaintEvent *) - { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); - } - - inline int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy) - { - int i, j, c = 0; - for (i = 0, j = nvert-1; i < nvert; j = i++) { - if ( ((verty[i]>testy) != (verty[j]>testy)) && (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ) - c = !c; - } - return c; - } - - signals: - void buttonPressed(int button); - -}; -#endif /* XMENUWIDGET_H */ - diff --git a/radio/src/simu.cpp b/radio/src/simu.cpp index 81aad43fe..d8616ed19 100644 --- a/radio/src/simu.cpp +++ b/radio/src/simu.cpp @@ -378,7 +378,6 @@ long Open9xSim::onTimeout(FXObject*, FXSelector, void*) #endif #if defined(ROTARY_ENCODER_NAVIGATION) || defined(PCBHORUS) - extern rotenc_t rotencValue; static bool rotencAction = false; if (getApp()->getKeyState(KEY_G)) { if (!rotencAction) ROTENC_VALUE += ROTARY_ENCODER_GRANULARITY; diff --git a/radio/util/font2png.py b/radio/util/font2png.py index 135671542..a44dbcb25 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -6,9 +6,9 @@ from PyQt4 import Qt, QtCore, QtGui import glob try: - app = Qt.QApplication(sys.argv) + app = Qt.QApplication(sys.argv) except: - pass + app = Qt.QCoreApplication(sys.argv) for f in glob.glob("fonts/*.ttf"): QtGui.QFontDatabase.addApplicationFont(f) From 9cd0cbf417de2d26c04d9eedf2d157802434f101 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 24 Oct 2015 20:17:41 +0200 Subject: [PATCH 38/74] Test for the Travis server ... --- radio/util/font2png.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/radio/util/font2png.py b/radio/util/font2png.py index a44dbcb25..924b42b45 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -6,9 +6,10 @@ from PyQt4 import Qt, QtCore, QtGui import glob try: - app = Qt.QApplication(sys.argv) -except: app = Qt.QCoreApplication(sys.argv) +except: + pass + # app = Qt.QCoreApplication(sys.argv) for f in glob.glob("fonts/*.ttf"): QtGui.QFontDatabase.addApplicationFont(f) From f0039129e9f725b4769161da6364e922beaa05c7 Mon Sep 17 00:00:00 2001 From: martin Date: Sun, 25 Oct 2015 00:04:44 +0200 Subject: [PATCH 39/74] library icons --- radio/src/bitmaps/Horus/mask_library.png | Bin 0 -> 1052 bytes .../bitmaps/Horus/mask_library_active_model.png | Bin 0 -> 402 bytes .../Horus/mask_library_category_index.png | Bin 0 -> 293 bytes .../bitmaps/Horus/mask_library_empty_img.png | Bin 0 -> 1043 bytes .../src/bitmaps/Horus/mask_library_score_0.png | Bin 0 -> 244 bytes .../src/bitmaps/Horus/mask_library_score_1.png | Bin 0 -> 315 bytes radio/src/bitmaps/Horus/mask_library_star_0.png | Bin 0 -> 432 bytes radio/src/bitmaps/Horus/mask_library_star_1.png | Bin 0 -> 433 bytes radio/src/bitmaps/Horus/mask_topmenu_sdcard.png | Bin 0 -> 684 bytes radio/src/bitmaps/Horus/mask_topmenu_usb.png | Bin 494 -> 302 bytes .../bitmaps/Horus/mask_topmenu_volume_ico.png | Bin 524 -> 436 bytes 11 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_library.png create mode 100644 radio/src/bitmaps/Horus/mask_library_active_model.png create mode 100644 radio/src/bitmaps/Horus/mask_library_category_index.png create mode 100644 radio/src/bitmaps/Horus/mask_library_empty_img.png create mode 100644 radio/src/bitmaps/Horus/mask_library_score_0.png create mode 100644 radio/src/bitmaps/Horus/mask_library_score_1.png create mode 100644 radio/src/bitmaps/Horus/mask_library_star_0.png create mode 100644 radio/src/bitmaps/Horus/mask_library_star_1.png create mode 100644 radio/src/bitmaps/Horus/mask_topmenu_sdcard.png diff --git a/radio/src/bitmaps/Horus/mask_library.png b/radio/src/bitmaps/Horus/mask_library.png new file mode 100644 index 0000000000000000000000000000000000000000..237883fc5ac1488cab0953fbc7ee6190bfeb505a GIT binary patch literal 1052 zcmV+%1mpXOP)a zgUe`9K~akdgK8oD7r`{Nmp0*|1RN-VViDpDr2L_E(EfNuxS z^CU?U>AF5>TN?YyW5Y0Tc6NrNqa)PobpU|N(Baukh_E8kYN-{)5V}(M2g25nME*ErNhuiJ$ zJL%=+C9bcphw|3c^71lPS6BON&1MtR)6*~v1BF5XztZV603aHT+Mu;s4UdnHn3|dz z3HZr@yks1e!t(g!OF@CCMG5@KR-VbIF(A_{QTS& zFD66Nw0E$Xdrft_U1TyD6pKZ8z21?)l}ZJHKmgrt7Y>KRvYQM|(=h(kVu!;4pU;PK zxjYgS0LtYud_G?v^rOaEmIctOG9MKVhmp(WMh53{IfTPuo84rvEc*^Fl}fgWp->3< ze12qbKA%S@6tdY(2FtP-7X$%7FCd!DX5Yj=>2b5!L@Jd600@GB@jxH|p66j029n9- ze+Et_lQ0YeJkR%9BqAb7lB}xqyHr&rr_)KDPG^W()KjO^A*a)6D}!d0k|gOpSJ!Ga z1cO1;>vhPojJ35j>!g#Dli1ta8#;-+yu2VDkE79OShkCc3#_lNgJBpH3I%vP9tFFte0cDZs=No~)-};#PE6tv%pJ;FYUk&~5`teVg&-5oh+WrB1 WXAvi_r33c>0000`wDqxY-SlDT!MMz<983Y$#s~ZRi7J|8k zfV@*UILQ1MXAa!v`|f$FdoA0x(TllWuYz0ccs$Cf)oRMJ>?OpqEJ@O8wNkg+)tmkh z-}f~hkCmpWdcB^USAomrqV0AoNm?uxw_&5vplpmW3Ealo zF|l^yLf%#f0hi}8OtrI^1WFoQl?qH67I3aQc#&-$m&0Pe2?9t1{-nK7uR@-BXM-x=K z7A4#^yR+LM^!nCC({F8iYIFYod%MreKB{<19KYCip!&Sc`7*!Cd7Ia*o?8(b+9L4g j-LHMm_vH&S{9xQ@;^D?}W!X-kYZ*LU{an^LB{Ts5AV6@Y literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_library_empty_img.png b/radio/src/bitmaps/Horus/mask_library_empty_img.png new file mode 100644 index 0000000000000000000000000000000000000000..d53131e3cc059f3b082c842f3aad8bee9d07853e GIT binary patch literal 1043 zcmV+u1nm2XP)q@pM&it?dY@rUQ~EqHu>uh%&`I-**w0${V*2#3QgEG(ec>)+%UjYb>} z2Of`yP$)#Z-6ogIadB}$zco&b$a1;N@$oUTEMqVjh(sa;gFyu0bG1>g*AoZ?SXx>l zolX;v$0tK%KA-30zAG>2m&iBE0ZCzTCH+=dWxbb`2Bv?*Vhrn z@153IMar_w;o%|OZWoWo!^Xx2I-Ty@^NiID#9}cTjRrQG?Z*Vd7)9RP+)ykQ5d?u~ zG&;0;zJ*q+#ogWAB#Ei1 zDZ=6Kr?`DP_xJBn7>meUE(gHk;vz<)@k1O{RjF311EbI%t5hoVdcBd{v2533u?T?A z=NlT+7ga8oDV0i;%VpZ__Ny30Q8+j_KoA6+PA4-nGq_wXTrL;W)6;`E%|+hc-qLEd z(ChWg&CLysOC%CpUS59anTnzyNfMGIkxr)pSX)~o5C{Nc*sk~Y_W;b!&W^Nfs;Yh| z&r81MBKs!`i^ZZ@&asI!nM|7H)cl?7mwki5Ffwj+b#-v)eThsaGk8}8L11fZ>rMX4 zs;#-mzO#9LejXWTG#cM*FE7V*IvsYq{oS0JZ&$O~4E(WXTH&7%srh#8H)%Pp>T6d+2mJ$g)hQ(|I-CzpWe9 zS2Y(YiX!v#^T3~L4^47vlI1V>d_D?=0#{d8gu~$vT~^uccD!Ei(EmqWnT$oG-|r`x zOj57c$z(EwLZNp#KA&%-p&x~@9JNGIBoc`LaDIONZqNKGbpOKl$=@tSePf+*U0VPE N002ovPDHLkV1hq--LwDz literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_library_score_0.png b/radio/src/bitmaps/Horus/mask_library_score_0.png new file mode 100644 index 0000000000000000000000000000000000000000..18c9cc06398f0e6854951477d3eb7efbca6dca25 GIT binary patch literal 244 zcmeAS@N?(olHy`uVBq!ia0vp^AT~D#8<2F%laT;YY)RhkESMovTX5+ONB_C*{m)J2oRh!8f=p zR@`h=_JQjD#}-ZB?mf0SzrnyHEs|~NmD8rlJ{RhzFP;&~ujBa>=qCnGS3j3^P6HK~yM_g_14H!T=P7pN&@$maOxm*$n;whh;xgECz$oA`1$F zC_@y3s316t(;xoO`b!NhHAceWEDBahxQXzeE-Yf`G^4ktSm# zO&pKMl*?sI(`3KjQ?J**jAl}|!{I=wRH9HQa6BH9Ck8>F-EPP2c0&^P`<+IkfoGYK^3`4bAEiD#{ anEMAahcbdnD)4v!0000}Rl8<3oNC%zs?u@pObhHwBu4M$1`kk47*5m^jW za~*^koit`w00kvWTq8n6vc%A?I6nv~-tW>}Zoy*t0tc2dicO*)4^|7>|)xyhhv{(+x0e3NdxEejHu z5;1F;h*JQ^K@;E9cn#Lv+iDC<3m#hpZ78>zTVWv+Ai}z8l~d%j8+qF+_Qo+VD6+&{ zZ%vfAP%1kuHPLJ~4Wj6zgDeBWm@8ZjIW-;IA-12{N1;OOXx#l=O?gs!fx6p2IxOdauf zT&-41#`p%^ZWr6O>2x~SwoRc>z_Kh%)BL9Zki^5o1C>ez&+{l03O{d9tJMPfxvs0%*H?{3Be||C*L5`*4Akj#VJPyEMFdzUJh7r7DnEL#Hnco3cR#qsLN&v)SG4lC5YinzyQYjLN1gEE`0Ho7t z4i67$x7)0)uKr3GS5dK86rfBdqiHo+fuG+46TopCs@3W{P-pz={E+$gBVT|oz<&osqfr2Q zyt*tE@jRv(^jZ`Yd^78Uj$?Nqx7Z(=* zY;JBsv)PnoS)sIxld-U{py%i3?~T{f)048NzylwIdO|v S@qw2B0000UM`;i literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_topmenu_usb.png b/radio/src/bitmaps/Horus/mask_topmenu_usb.png index 252c7ac536b7a23b51f5d1ca53c665ed469e6c63..92272dd566332c03ebce74aac1da4a94c1afd30c 100644 GIT binary patch delta 254 zcmVaBtXM3JWhfzjZ#%r_WOO0uq=zI zREn$B>Jzs}l7Eil@N&73Bt<+Pr{g#PfN(gBX0wUo@d(2(P^na)X&M570E7^~;u%cS z#B@4^VHi+?!JtP7ArK0M5R1joXfznIkBzW-1Z1+MGDbzNXQ9y6It zGLcBI*Xun!(Y9^oayicDb6+cL+oqKA%VUynAMx#n&g`u~JUQcB7Fe$UNj!$P6(QuqOMc~*qXYl`#$0000< KMNUMnLSTYz2+=YC diff --git a/radio/src/bitmaps/Horus/mask_topmenu_volume_ico.png b/radio/src/bitmaps/Horus/mask_topmenu_volume_ico.png index cf61eecaa4d3c962b197be4fe0e4a8ebefcfae34..01afa66b25db5b47b2b8b5560687bf7776160af5 100644 GIT binary patch delta 389 zcmV;00eb$71hfN?Bo78+OGiWi&;ZZ?(0lA7!jUl;e+P6)O+^Ra3K$a`GLInSxBvhF zK}keGR4C7#k)e+2U=T)6Hw`7J;0dS)1qMQbAy_Q38rSFOE1)Z}Sb{+SqF@RSfd3N& zZAzPbD{i>=vfItFd*sZV$$S}0k^suGB#I(7n+?a~k@NWsK&#cF)9ElA4)J{-+qMC) zBx$$Xe=(cQD9e(`WJ0&wrQL1=a5|kh91bj(OKjU_KA+R?_aRMFd7h^*3{@0`zM?1! zg<&Yq^OUBkEJ?cG@6>8FUg0roJu*JHUav$^#Cp93U^E)x`#z0E<7vT@nCCg;@t8c% znNFtwEEWqK$6>Wv;W*Ae^Zy*i7zII~EX)4ZYqBg;5Ck&DJZ1iL>Z@3;>;6tO#;DnB zvfuAt5_-KJm&@f(#yE~uuh*3%$(Mgjl0>fSDvo1G5+55?a#fG2fsa%zeB+ncRmJUg j`*ZPjyD=CHz7^jAs4!U^(qrX700000NkvXXu0mjfZlSR| delta 498 zcmV^kwL35VHk$* zIZ0EBrLtfrDaFh}%7U_Det?yd;&0etZKb4`KVdh8!h&84`F{Z@D?&|S>OPCFIq{8i zzVBSC_qltnxA%D;i6jBQvMlJjj>%*KAp|y?4FZ7x;_)~XMM0%ff#2_kjYN|3`5d)c z4VGm=(=;R!2}Gk&?Du;JAuydzF&>Z6Xf#l*Rv}3eV45a_!62K>CLfOnzr!#LhQnbF zheML2M3VRW9e-}O8~@_DPqJsUIiJtabsdIbAQFk7SS%tIi#aa&crI?Y8w!O2`u#qV z$s`Vk1Jday2~}0mYPBHCG5|nP6kM-Yhv<0f|00D#pKx7+Po zX}w<4VDwSTcuRW@&isSM4cDB>$glsnZlDYnFn9XJm o(KJm*_9Bi(BcxKPAAi>76DNW>%h~hAegFUf07*qoM6N<$f|4-dEC2ui From b96f88286698737b1a8b7ad9c17eaa6c0eeb3c8a Mon Sep 17 00:00:00 2001 From: martin Date: Sun, 25 Oct 2015 02:18:55 +0200 Subject: [PATCH 40/74] asterisk --- radio/src/bitmaps/Horus/asterisk.png | Bin 0 -> 14306 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/bitmaps/Horus/asterisk.png diff --git a/radio/src/bitmaps/Horus/asterisk.png b/radio/src/bitmaps/Horus/asterisk.png new file mode 100644 index 0000000000000000000000000000000000000000..0b6df40dbb93af4944f57e5dca6d2c2d6ebe704c GIT binary patch literal 14306 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 literal 0 HcmV?d00001 From 543123a169919299b7f23e7429c60dae4af811df Mon Sep 17 00:00:00 2001 From: martin Date: Sun, 25 Oct 2015 02:55:11 +0200 Subject: [PATCH 41/74] asterisk --- radio/src/bitmaps/Horus/asterisk.png | Bin 14306 -> 14224 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/radio/src/bitmaps/Horus/asterisk.png b/radio/src/bitmaps/Horus/asterisk.png index 0b6df40dbb93af4944f57e5dca6d2c2d6ebe704c..00c24413dc71305ca3c8fc7ba722f31df58be875 100644 GIT binary patch literal 14224 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
r%*!yMhg2H5SfE0M zMb1BXw({rCyWi)3^^dI;4URxldo9{yQPsu!cQO$)5Vtg8^XB$;U`hpz839K+X`p5r z+zPl9Kx{dw>q?rPq(MeaqASUGEOdyEo|>?a^&4kZEiK~XwBoW~%qv%h`oYs1zfp#T zHq%-)$aNE)09!aRZ(o;h1$?C|NL`BN&}D6DJG==mF#GB%Bg{2^<^YbIXR)=spQ zlqx~yuOOmFt%&F!mF}{c-pIoI4jMM46V1)+J&y}AeLY9Fas=p=r!kg$PZ`e6J)m2d zY$^M!W=nuBjIm5j!GghQhu=QU{*9*$o-ZAo{Gh37X=~en z_Qz|9Bu3F5UqB%FC0yimW2lPt!_yciZ;(R`BhV_*G5uvZ1pv#>4}EZ?I%eOn4<-C9rFyawHD9e@jt2r}AmqvYW3+Eg z>~^>i&1dsB3xLtQyu2qjWOpo8Y=iS=?W`OH4uzmU$a@*hAptynaN19qQ-Y+`=^A4&sebjrsSIsB#xASrV!BwOMvQFK6aA7Sf(X(fAX=#Ga8-q(U=n>#3L)#F7HOA@;31p9uioLnwZcq zV~qq-=9OPW4)4F8C{BsO{ta!mtvE|95QH`{aC9ACU#YXoi0r$GJQ<_ZzRP!&W*59v z&JYa;d8j))vIFdgz@+F$1Ns&~wNXD^!R-A=HQWGNMh~Wsat6d7un=|inT(|3kk3d| z$)X*#pljZi{L@zz)ulUcv1X+D+zNLo=oV;3{*ho(~k z7`oSE)07JUkK?w~3cUeUFf6Q9ub=1n7xggwu^W?~Z+L+K?huT5p!q*};|QcXr=0(` zRd}3Q>;M&^)6%1x%yJZ{``cg)(RlYx8W1kufQL!+w072BvE_2XD*-6KdxuSz8M3li z56WtaxjGjl^bilUlpJtlfASRL8dd8kTo(giWHfBKc99X=)aAohjbdnGu$fi zaEv=ZmXD%UZUiV>5!@$R@XwhTOxkv{Sg{bBvId5t!okJIhf7ikU+-20hMbX1kB$G# zK_S@(67MgNZl#5VVZ^LDtt+de3#<%EDId-%3hBcUsTook zdFSzpsuq1N4T0RiOpXQ_N)t+mc_Bi~7@6&}40e>)VlcTda}t@vKoVSk7AW^cg@suF zN&`SL&;o8t9f*eM0JIdb{Tr6ToDr6{wuk!R_%MNJqVLw3z@--xO9$3g>6HAg5n zC+LHSD=LW-`f|t81=Qj{089ZcGYVuC46sgs(>fnv%=G5I4>VpsmBE$#M9*Crq_kU_M z!?tPivYKs=qOmR#o;JP+)j^dfempu?RK@YdesD!GZ)Z9d9RItPuFU-I~tg= zW!RGn<(5~SXE-WkSPBQilXVb4WxxYErsk1k%x+o_V^W;tB zFpk;V+iTrmeyBDFuO0`s@cSLDBz~!n=qC_KTu3oAjnI2^_xycZAhJ8CDl4@x1;Dm) z`rf$%sw@ZWe82?o?bRbUdNmmlk&vu{7WA*qfvVPaIuQK3B>;3I6(>GeTQhGArG^5j zf}`TzKa*EhmcxFwc0V47Jl7%EyJCO{>H_ne8ZbmBx&x(d z8JOC8fa@)yq@=_@_xQVp+E?;SK)m~r2SN3;|L{s9xUBvsTP>6p{X{YR+RBMcv8Afs zj`S^4u??2D|-51`{rwl zo4eDIA^Y^Ot(UaV|IP$`99ZM}^OknT`htj0={qnb{sGCMVr6Aj`f!p891i&ak&PB< z7WZzR>c(#v%evUx5N3swheG6Kwq)-z|e-?b2vR_3-7*C@+XfHoaJ5cs!YKk@8mccVm zjEtz@;g_y165}Y~_o z>9{AX^YV2}R&6aYd(yzC00@r)JP4?gA1o|r!J1D@?9;gM<-4_u`T~mdOgKZnNEo?cZa7Lnz?1U|D$V>eOHVdWzc2jZGBXNHmDw|oOUcxBK zQFLAe;UDsnnK&=;DxLIt={}5y*?7-7=5SW~@Y?-u4;Qs(-kc?Np}RqlZfWTnx?2GygaJW9x?rQzq#oadR zNR$Esp@S&OO6mAyAGZ4j5W{XH?kkRR8J{X*&=YKb$3z<5kk^1pEs$(a;MU~&bhbQu zet@XpAo9{AzS}SLhqhvrl~-JEO<2!d8y^u$;4hE%Y`d#TE-b09nT1eo29a4%P zERmi99I{*U^UEo?>vR^%R1h)@Ggf#u+5Gg|iACn|0?4X#G%Y4!eoYNgwnvXYnWch) z0-Q;4W9AkKz9u&{ksdv#lG_Yf#Yo?9Z$6onr!^l*=lap=&5cJT3@M%_uaVRwo@{h6 z#i0=;W0Z^U%<*20`E$5Lxe`PR@0e}9qoFCHv_j)ezk$gb6Na$J>0q-3s=yIwi+1VVZUq zdy~A(iRpZNd|wL-!;z}m$=fnA3+G00JL0)sHwY;GWM*deIq5=2=1xMH zXxF5e_+jVpNABg}?87$4vuA8CUp8wNCW`<3`BM`TG>fyUI-_kM7e^Y31JOU=e!JX6 z2>zuAA2Srb+U{>TktY?MlqA%qu1QQK@(u$Xj|x6u`5mj;@6sWjj8{5%9}f~IQ`hk` zzhr^3Pk`}cSKRx^SR2#r=Lh{M@P1@kewevAz3L}{p}OUKnJ6^z{us7=^0tDnMJz6o z#M4_LVlUjc6{gk1{Vwd*8O^&kTr@iRhwM#9)+&Vaq`?rW6Pg`9 zU%A5u=Rm^NAnvO=8OwPk^wo`8l_zLxhmn(hyr$>l*t=rtj{75a}%GONgG%sMl_ zIl1@=+!pd5Bzd*hDTXAz&kP%;oi>1Ud~k)0!E;nE{Z(}ZZdbrQJ67L(@3`*^GDAAVwWXq?QQ^5>KRlMLM-6m&)|+Xqh|=4YwN(MK-IQ{TLu9_#FSW zW+#YwMhrLM-Rt!*{HlPz?!SLjGZr$>W27>f|Ch=8wY1d1OuHa>sUYa;jtbj-rm`Hq4mUwGZJcm zy8aqckk!OhKVh)lj4oMo-wlF4k^B2KBBj)YqzP+vrDR;dQ$|1_eT-k}L0wmuX#-)p(vf0>NF@FmXf_rn{D9Nxzlz1WB$a`qVxdK}W zHVJSR`tjY*!8CIMH~o|@y&yd9u-?`qXAL z`ThHF%qJ{nN6XD*f(|-WuW2H^HLpM1H1c5Cp>!mzh9q)bEqf%Dd{!-1%V84|nxFXG zk5j`ry*-pB(-NCVBi8cfr=!o#M_Y@dv0<`;kNmb%%^tgwz~HpEEO@({(_G5*Hhb<3jV|E!oZG zulYPI|3AND-oJm3_F*#H^=SF!^1EqOVc#<=vpHS6?#rQmI`teF3j<%#sXcGhS${Z}l;P9}toE#cT*L9au7$hkYc6F~DVeS8}9T5@YP z99r>Zfrsu0?2rt;UnYjgt{+8tLk$CZG@|49FUC*_-e*$(|JS}hVhNFKWUKqFho(PG z5DaPK*BPCK|t zyiv0qLrCHKqeUfmE_@`FRfH#zXd#wxctdR1F12sB-+=6J2E((LiiCvZ`l{n00R+?5 z?}geQZN9bpcibKS_|l)7#DW#1=jWgP{J10_wCKddO3kA`$)14^brA0F$SxdsBOrc% zZTa>`6|}jzIpj?My{4XCWKB(t$n7zzROes{BQMxum5+R=fj1ZScG}!&#V^-EZX2|E zalLr^1HRJgJy3`@_OiigDbi`FVPn7evs_2e-DBdZOlp{@~#>tYMmBFnT}6*h!hA6Q{FC@K z=R2c4OD-KNKllp_@)lBAX*#DNG*TR~I-fuygGM+1YXNT`=OZ;d@sOO4AO0UsWFq-$ z2Mo%_35^{~xvCAwLC@x55$Kfv4`(!0lpn$waz${f7oqScC60l|Jrl0DMb47& zbmB1fCSsWVIrH0p_#0{5!VSN-Ph?;C^2^jRB;l@p)tRQK<{HJqbnCqSg{nfqy*?m> zrJ#z}6td7&h{KbP!jp9s+=WJfo9|x8@c2Qgnci0>|`A$65ST#Ybl!eTKDO= zD%Izo?m9Y=YYiZ;P~SMb*j5Y`a?ab>zyU(^zH-QbR@kdX7*b#b%XH^ywBI%L=9qile)Ugh>7ybNtlfwJobhE|q;SGi%dJ(Z$O1c5g-xHeo-P#NCd!rOM$ z5bhbAGQkoqWp#IRdA+Y5yvT&!wl{Q+hVUbMQTdy8irLwnpsT|r z-hi82)T`x+U{s{urfP!Cen>fCLW4A4Z?8Q;d3|W#=FTKiLx7c3rSlHTYL&SRlI8FD z>bDj=b}r8`R|kIwWs3(=@Y{~P{ryLP%rx+lgvM`=e%c=m-|R<=UiX8^w_n68QYzUv zHmy_FU76Bw0tuSXyG0}Y+Y7TFXDO(b@{v^iK_g^YM72ApA#;do42@jc;D2t$Z9dh! zOH*dVq@)&$b@r~!#*tSlw<&N{S_4{4E zA44ydcpTIE6iSilodq%tmNL0xI~~tqZP)SaAw>yvyv z1vo*l46>upl;S}%X%|!UsgP93KmVNaX8ai8GqDX9TJnSZ6==D6d;%*X^H~{%uR~wS zO1)#oz6@v?eW2P9dNHrGc=ia+8;2Rz+HCA(xg`3{T&ZTgFrO{ejresuCZtqnLOTpmz;pr!l4&pJ4416(%orN5d!yiQqUGC+&C%5>#4qtG(aKh0c z|Cs+_F-CdaDEZ&PYVDiZVh!r-G~1um8gTmTf5|wwEGLY{P?xx?GY`^YsIn;uf8Y5N z6#~bge~bDNs)CZat(x2!%A%a(2tRts!mzyGVb=O=&$A5G=K)`fx}6JxO>W7H+s**B zi-Xq_^23_Fk5?5Gz6NJie7|vsMv&EiV=vRM><6eObO#fu>Hrrr&<`beNaZbhl}MKn z^6`{s6J+O>`1d^AuqE;>t@m4`YnV^QG$zVXnh{F!-&ba-)wCwJUN{feY2l~*R>EQ9 z_U{mAnlhX1nUW9X{l@+d#Wm@7}lltzM6lxWLSd{m^jITfR&lBpqEy-BFHL?IlF zOGVJN;X)iZRK=e^hZPsuiC{WM@;6Vk6ZXoW1|>;Ui7ZS+_A^ zgNB?1Rs&NZuJ(7lKl?)Um!y~*&c2r7_S!N;39>|FY44c$=m;X-@R(l2mt1_j+>GP? z110c85`1+Q-b#FSP*#a3FXyyd?F_=Ni#}s+a8oG^ zA<3?jA=&%u`e>%hvxhuLbnfKVNJrO1z~(TJivA;alO z5-aj~U!@=uY;y`?6e4c}?=I&ft7GL&Io69PyZz;RxkF}%_M)Egm_|uskbOsbm*MK| zaO=s;4wt<-LxE$8BVh(1xs7j}96%;IgnCS-ah8~L_4U=Ebk?@E*psQ^NGfA5l1M0& zashm#+_^ibR=^2@G+$j~6#Qxv>7C?gTP$Z^zT&?AqMvX?EU7d@&{%|va`roJuT|9X zY=s2YteeX8T37c&I2*3%Xrk!Go~YcmqPiuSAZLwtx7Yx*etAAcut#BK?1RH@)a}tn zH3#1O$ms9par^gkOaydr(X|jCl*Ca;ibIJHfT=q_iyso_suRNdmZ|b6V^23O1<;8~c z*V)e@J}>`S#cA9aFC^tF7q2^cw64q}D>N8zo|cv>0g#8Qm5YkWr) zn-WJNP&dNBCkaf8C)}YU16|ZvQW|;l5*NMZL46{rB`rx*o=iz|XwY7cJQ0cWMbCeJ zsdJHoJVR!Co7s(GzGvIB5F#?mhNdRrYWlfd(l6)P!fxF>Yj)*AvfuDZ$;B1K@au0p zT)z;kvIjW98X_SZc(0NEky=X_Iu0|*t>?;UP-j7oN=tgwrv^QjVz%Osy_<~#NnrMj zBktML`*mQ3n_K$Bn2(D4+Qv5R&oT9W(m?UogAY~-Dkt#P4y2jdH;owiTnA&{8Inam zxsPJRWAw@%k7@nnZ1Trf;0l|cmzU*o|Erz%Ir(MH4Mn4>=6eoVO%+|;dL?35#;n7K z+S=L}-hM`tz>EnL*3-UFQECRPE)9O;nfuogOGJ_fu&-m^%q-9`(ktjn_{lh1=vm`G zWv(+~LkT3e(T0jUe?Eb9pmY{67FMo`91;eOS#9K-yhe))&!a>qfeq0ih!s-yrlTCyKaFL&t7`6@G^dzU%|A46566Alis`BPrBcXs2hEmcSOnwsyuWZ##vDSf2F zAOREV$JA7Y9>=n1g)>>tZ;Zc{ewCb)Ddsk?!BHr`T<{NficA^mIA~_H? z5iOH2tuIM>2I8i6ptJWuyd^8AXB1wFrCf&N?Ho|hh&12_nP|Qzh7845zlg6!$r`m0 z^FGQpplwx8OJ-f{(Tn=bUM2AM4F-FFU6%&3xyk(oIG1s)SULP zyys?_wo59coH%u0Xo;>(6DUE69*+`9!kr9Pvd5sOL z?|*~SfFa6@L-g%3{Cj!p#W#cTWbL6Bs+Ylra#kVux6%zlRhS+Y(@#m${svA3KUfjm zP!I7$c~hy2MWvP~lwM-cVASUumcW&bEdQOA>TmAJm1n=HNh;G*%m1Wh-9qVlu|o;Q z$!ZNbNqNP@Oe9fiD@f&Y&DfqN)S-UAl7XU_pDbu2z0$rljmW@wMn+s|#mkuFN4T12 zQp&wQQmVM>LKUwO{*Mvs$aguUX8=+AciN)5DO$lIej%DI(Sv5-Wh3D66u*4=BCV#T z_QP7Rbs_!Zn5AqkLV+0{Qjmxvm%bQ$OT;b}kcZsK4PgpXI2EZ$!%9}iO3|y(TnYa` ztG9Jy|5K9@4`RScN)GQOt0hV?A%?ZmVd&}VeiDHxpDC< zaf4B#c)~loQ5!SdNS1z99^%3OSGOElOP&k9E^3Ek29f36B4SHI?QG*L+6 z?PUoi3EEq5|1Pr#j9a6@F_?nUXHUsP7viGzuN8|36BXpR_6sP7>&)NIH|qMB9lh)o zAzO;Te(JZoyPHJKXEmrOMZifK*EdydDW4CB5l$hY4BmbMhvC)VgO)|-WVo*(RhJyy zdycrZVR?UebYI`>eNlB{q?IbbL}!oCD0{JIEMTCzP{^%*szfy?F8!BDpDU&aQ8OHd z{+TUO9#1_sk&G9q-S1LbS{muk(MsII-Er`VPj`}&FCb<%iq$`l{aUR5#XirCmZ8 zVdJv9@Rc>fGpzc)k=*oc0m9@L^xnO^KHj-=|_|Sl14FWH2UV% zy5j6OUy<{rt!)`;?ZC;)t3gs0LoUxo=xszzygGoS#~lwr6K(3>*L~jFgulX5D4BHX zgbbdbJ5PP;ZaX|Xctu5rDZMO6z`3GvevAE2ZFM1gp7k%1fLK%>ngUvQhmpr9lprE=*ut`mDnJNv1@6Vx{%> z{Z}DU5q7jtdgr%$Q#nhJpXFl>+KiI|n*leUm49Tp1ihU$@m>L>uMhoi7hO%wkiNL5 zS*vN8nFe$9j`QarK3$whQM#O}4cNrRX;PVnAn#{pbnH;3B-Rk`XdntoN_?XKy&Vvu zdMY!3ilJ4a53pZ|$V$DNm~sr^tY zvvBZg?|aDlFh}KojRu@RAliay#rt%95nXLXBytufid2Z686OFKYfIvQ z(bZKpH_wNqGSP!VJUBjHIqSCZYDk=6Vp7Pk+5(jdZ!4pdtY;!$W?U)NYI3lLOu5d0 z6O%NKdD5Z2f9cRGi*sZyJRD{6&?^pz31*;7t5k{#y&8Hl_T~~${+o-AVU(Uq+yp_5 zZQoDvW*1SgL|V__gAp31D0e4fm80J;v~QNFXMfZ$EkAz}lUuNH99&5ms-JYU(y9-r zK0Q(tDI%0EhFHXCWB-RvvN&yG=0p$Y45lF?LqmH_Vi@Q1=cG3m`#nYKpO1Ex(~^tx zPkkZlL#&?#{`3D;`Amu7O~pjs+z6ostPTM?QK<=jvEEyX*ApPlQkxjbBY2^rw_Nd?>xuc=6fuv%?6X{cTuFwLg}L8c@;tNro-@pc=a1(yY#IvV1)L*e7d!@ zWe7qX84jkbhd8H{9#Y71(CuNQZLlPbV(A2fXN;rJChMkpOtXx`zM21u3b7_4k|__x<9$LS(m4h{}W zzxHB9rg6ZK;-3GPPKRXfb$?wdNF|>nWCi==FdFL+Nu~NUR@S_NK9?nC)nrQ`@a^72 zex|USg%uY|H#%*AnV)*)n|9;>I$onNVTArkMPWE|Ib6@?hiDK-G_N?zV0#p#+0K<4 zM*7-FJwtW_!_Xm7lB8Yk3i1i6EnC%3T4st5<{A{;nI z^IP8cu_QdJAse+k{&+Wo`dizVfUUG<;UEN5U7}AUYPjC}+sj|*W6ecXZnPo9XV3Ra zAi{(t*&e#YFwiDHc#MIKQ`FVH`LpEWn1GWa%O3me{-Ri7?< zUJEQr!(z-4Z?XMf1BR5@6*1N9P9`2u>K8<6>!p5HL2oBcKLHXRSfVgTa z<;}+zje8k8MtHMI*Rywl(oY6bEfTaqSsiC7DQJt=p+n|cg~_XBKw$s-_xF4NB8)24 z#Dvie()T1EduB&3iuPH;?wvJp%B6Nsc5)C!48-$LVdt9IcG$OiVzqo@mKAQea zY=*EqIY0`1N3(-&l|OGC4O z)oH5AbZH(n5D32LiDdh*;44R*ll#sl{VturUN`=J<50RL6e(BUegWk4B>)l0Eo){S zbaixp0$t()Y}9QO2d=8cZVbuJ_~)c0=hji4hg!@T=(7aCA2ZJ^tu25Hxv6XnH`%4dMun@(I@ zeA8W5x36(6$T9PCW@a>~;ynF2YsuRz3;;cs;Odr^wvbysS4>?-u=$nim1&une9IN_ zjD1QT#8PE~Bg<3ryCcgC6d1-&TIBCOOnJrV1IxkWbG*t0!;po93 zG7q8@u*LDb8RKY7$jKn7NMM>xONTp_{qdMBsD==;BbBM{ywN5QBMAwVLZ6iAAU|r- zwhl1)a*e+tE~I}eTwGm(7I)NoT;aX27I>9o9|Sriogxv6)Co^NmH5!s46${8raS>k z5?d??DNc+~hk7I3tkEJ0Me0t;Gg&ga(uELSG${ht9$cYyAo};hp*!^JR;UIyxv?Qa z!#<~6B)YRjjq>qv8SrtBgrOcXM*?59Y+>_HpVQOP;`)#@iNPip(_zet2M&BIEq$&1 zURU>$r{Bsjon-8#y0UV?#l^cG4Tf)OXM6M*NPa+*1pJ|BxpmSr^hf!i#`$9Xilcxv zMtpH_gV%7l1;_{!?fzGO87^`(imAz;K4E2zqG=N6#Pde6xCJQHW<@W5+9pJiB87nx z@Bw+fi`W{MP&t<@$CJX-j$;79#vmw2a@=-~M-zB4Syg!OH?EVPyL69Nt6x>(!|9yq zEZRQo_?zJT@Nx5Qu3rXjW*RFqGb)Hm#-tvhx6J|Co{e-E&TFnL9%CfM(nogg^+trWw2$eD3$%-#ESK*w zKow^|9H#|`CkGcc5LX+unFqXTxI&vFMs=Bp*z{Oc53?-0Yr33!osybLbm~3rdbX`N z{SWNT;{&{Xff1?!?v1@Fer4A~0#P?M&Xy;JEgX84d9~)GrlsMMSmP2IRc}w`;hMU+ zxvggxxU7A@9(4%{W7jS+`+N2i*J$r@uybF!dtbV9Tizjhq1JZ3)frNB{|b_vk^*gA zsi2lWg3n9tp{*0bCK*R+z~w)BRQQO}%r*ke<^25d4&RKhBY1jzwt0d%;o73y^k!0%y zsN~!Q)mZZy582bF#jaz%jm|4Ro}QkA^w`%>oYx@)D9}`<*=Ckli*N6%GO9NHfi-rX2L|UNvB(*6Z(Qe1yY_Emb7%JM-0;h*vA)u zEN)I5ce4m+s1i*rH;Lj&-VWNKd>7gI69pIiR3^-LUZ6z}NCbQUVrxAi@06G5pC6W* z>?84_V7sH^;=N_ff;+Vua!e^=S$44}l4x<#o_HS>pz#P{Q9o?#U zvaB`$MPn9_LS;bo-|p+{d%R|bsHcjF1j^YpRhC(`L6zq(z5hN5VuL|8F(#-WiVh>1 zY0ix;oFU1EjRXZ4CupPy1R@M=;)k3YUxQ-G#m&tM0eS$;ajqP*%o!?4od7-tTc~Bz%S4(1vFOsP9YfaInz^(rLElYqXzRARNwB2A7u$V| zgtiI6CMqHE8Pxr5iW*4ec8LrP|9Ly-hz9YO#Hod;^>-q?;8w{?9*i(0B`jdsfXBz< zJlFy|1Q2ZwqOS@gq}i+u8I#m^-MWOPr-}|1-BBR3hhFmKKTw&~+4L2FckIJQY3=-~ zDuO4hYHyRRKk`Nb)y!hK$*t+MpOV*Vkf6?GO}r4yFE2B%-N4FPl3Nu{MAKnP))db+cf$1}x2n4P{pTg^8g zGF>CTrAPao?`%w~OW@w#Z*N2WgZ zOz${1$^@+S2L}#n} zp%Y_bQc?yNX&lhJW)#vHkqF<5=7@Om1?~a&!{xV1#gk=^adI>%j9$hh(NIChIe`)L zX9V(zRJ{NFn#e~6;5IocWz^Hqz zKMm8A)zqA{@6Rk}i)9$8L6klNy;*zIOFlgGPZ1uHU@^(O(O)&vtqpZ` z!Mt4$j1mrCaRWCbY3VIH#0$W5Q-m*P!EKNAd%2mPvt+u$r1fisyAbJr7F@`ZS4S&% zH+Y0O5##F3EiFcmnO-pXG(}Q9*Eq2Ma^BSCW?kap?|9@4rAOPJMMv|MM!QJ_sh}}* zmF+JW4g~*GS5Yaq-l9kF$MYI4rF{W?mV;v}r92NkELZel&NiN$zkg=67KEyeqrJG- zZ-c@0M7X!y88Ftpkd8ydfg+z@{@*oZDd_Iz@0Ua-Mc3Aqr~qZcSk948J^Eq!tm9G; z+q&T2UacFGvT*O1&5nmV7lB0r&qwc+d2{0Z=F<*4-dYZ^7cQ%BKS=;jO~*_M6DBat zEP=oQ`{OcSK|MY^ym?uFxJO7vQl5UnE0U@y<3ZIj_E{q71BZTO8#i!Yc2ORE!U9WQ z^cPrk=6}6n{v3S&_bQdC#IKlJKvHvVg6^!`w1dXk`y8w0bU@;sw|wKmR^rw$?j6TQ z_7*^V0{yXE+6VguEj|bCy4+`n**6B$k2o)Kgv-i-i z^=`JSZN5bjfam=9%q;;`lepUJ(5Q#MwLghA>E}p>kb4;-O5IJ}!RCX>{oDf9?`Y01P0_ zKx5onsAZK)ponyx4ORXT=(ckp+>7WS>*sHe_h_E}5(kM&&tBi_I(;6o|`mv~e>_Wt|G zucM)H5>=e*SGt0XFPbc1J4UsX$!p2vqkhi+>pVJjVG=rW@$l$41E~E4^CmD{8fC{h zC`$$f1r3hIXcwtWaw`r3qwSlD#zWDwK}JpL8pyc^>uSWjZjXNfU1lyDj?b0s3K+6S z9S&A0c!KW(roBOPd9CtE08nJ8sZ3p4r{xd8hK41$q-Jl={>X?D!i3zz$bE+)ZG{~? z01CbPH8)z<`Dmm2O>#Jbk&q;DPxfo>8-M$57dox1g$1G^9mcAbeVxWRQm7hCt%i#G>+Yy`hp#FmiD>RqPo+eK9sk?WueCU^?1&! z;%pJmVcL5MR#sM)KQXR3hC!I?dJre5%gotqnZD(ZD)XYU0}lNPSuHK1Rp82vY`j1Z z2S)Gp{3sf}_9)&+RBTeZ=g-F~*-YDfPG>=^8)9eif#8pgK$O!jYu}F{`xphA15cqr zn~&Qd`dZMf)1yuB(ryv|NfbV9f1UlbbbnCWR^lrjhZ#07S^)IyIws+Jd)O#ZzjO%L zn*+do?*n`+D{EF=6E9~*Y$00*b7|Vlo^Ep6lBU3iP5^A6nti@!G-gEZ_4|huaR1@| zeO}qLiM3d3i=QLx77Bc%U+e1X-h$}|*=9U9=U?v=^zM&||_;G|ahEKVIq z%7yjo9vI)SYi&l)XhVQnU;_Gv2jFsgHZ;JBbq&Om7mQZ1rT0P!QpTQS+Mp#a&<31< z7EhLrhXOBB`6vxUkc8C!i5l0&5;Ps9Ud7e0kwHI zMV?0SzoDUOZj+W7#JmTAjETH5F4uZv2)8v@)m}2}+b>M4tkY*dM-+8u*}PQGVTOj} zcL4P>WsQ>ldDXJ>$?*|8>1b(5KjnT?2qNkkH}``TSnDBRg{uSS6<2j25`feQ+cN;8q768@|3ByO@YkA} z)MwA0iJMegz=lD|I+TgRtGBYXjoUtqlVik#g^1EIghbzWbY|4sj?)}0H+#r(V?i$W zr=!5(i&+PS-j~H!Yr&seypL%n{mtSh&&MUy0S6ysuML2SuCDHbFL(^C-TyvXjtmW@ z)YR7YflSSRJU$`K6)zVW1#QF4nQdumA_J!_dH_mccwP?dXHCBo5dPfVO)H{8hQO-% zd!*kl2!})uUv`LQjKF|THclZ0PetFvlcXI$iP1OB&!0ap0(Cwu5})?X@$q9p{j%o3#;{{!lC(O9n75<-#E!HSxd2c~^y2adfkzMIsAu9sDQ@4mZQ zO9I9J#n%(@z?=0*O6N{op=J1FfqVf#X8E+SgkX(NfJ4uGzQW{3jWsqvF3Wl69Fcg` zu>c6?0l0YIqZ?p9pTa255_B;sPonzV>0LCcFC-5co&?32H+|Q-V$kGDpU!DWR4*e9 z2-aB50=azk&*FQl?X41?2Q%yrV@JSY77-C4?6HFbEb_5nw0*ptu+NE^P8-XUCr@}? zRz*esZl~REp$8PwGx(hY4}8#3g3#&-Kq#6z)ITVr4N}lgL{jgtIz7I3-2G_x7s~bB zA%DAA$FAoIph-TU0S5;MB?AIPiHL{}m|9m70cNoTc+yoQ$5oi>ejI$-4G=|-kMIb! z4BA*ViBD@k-{CwmS5wo|cd7kR7KZ7^!GC{dJbWWWvoliEMo-{*fk%g+?s^vv%GETm zt{c1D47B@+ zZR$lb``&26i=P>$2QdhBHC8WWTE;>O@~NMtB6|dtxAmW!#Q54Xz3FA5teZBs8i8 zBNTyP2za==s-FTKLlYb&0J9Xprqd9p9N=gb6%`efl^?I^cd-`&;+LW)61PWJ-^ALF z)DiL>dP3heDX%8CoNN=jBS(Xa(q!R>9$nAB!21;f40@^wwtK`k~ t%as>OG(PG9&#}%7QSv%7f=;f3E9YNO^5Drafb)0|MVOjwg|u1d{{T32bVC3D From 0e34c24d96d7b4224702a06dcddf7812b560719f Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 25 Oct 2015 14:37:28 +0100 Subject: [PATCH 42/74] For Travis ... --- .travis.yml | 2 +- radio/src/Makefile | 2 +- radio/util/font2png.py | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5ed6a1991..4b5ca6a54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_install: - sudo apt-get update install: - - sudo apt-get install -y lcov cmake cmake-data bc curl gcc-arm-none-eabi gcc-avr avr-libc avrdude libqtcore4 libqt4-dev qt4-qmake g++ libxerces-c-dev xsdcxx libsdl1.2-dev libusb-1.0-0 libphonon-dev phonon libqtwebkit-dev python-qt4 python-qt4-dev libfox-1.6-dev libgtest-dev + - sudo apt-get install -y xvfb lcov cmake cmake-data bc curl gcc-arm-none-eabi gcc-avr avr-libc avrdude libqtcore4 libqt4-dev qt4-qmake g++ libxerces-c-dev xsdcxx libsdl1.2-dev libusb-1.0-0 libphonon-dev phonon libqtwebkit-dev python-qt4 python-qt4-dev libfox-1.6-dev libgtest-dev script: - radio/util/commit-tests.sh diff --git a/radio/src/Makefile b/radio/src/Makefile index 515715e7d..0640d8ac4 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -393,7 +393,7 @@ endif IMG2LBM = -$(PYTHON) ../util/img2lbm.py BIN2LBM = -$(PYTHON) ../util/bin2lbm.py -FONT2PNG = -$(PYTHON) ../util/font2png.py +FONT2PNG = -xvfb-run $(PYTHON) ../util/font2png.py TRANSLATE = -$(PYTHON) ../util/translate.py VERSION = 2.2.0$(OPENTX_VERSION_SUFFIX) diff --git a/radio/util/font2png.py b/radio/util/font2png.py index 924b42b45..f9f5a3f20 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -5,11 +5,7 @@ import sys from PyQt4 import Qt, QtCore, QtGui import glob -try: - app = Qt.QCoreApplication(sys.argv) -except: - pass - # app = Qt.QCoreApplication(sys.argv) +app = Qt.QApplication(sys.argv) for f in glob.glob("fonts/*.ttf"): QtGui.QFontDatabase.addApplicationFont(f) From fcb845a6b21a1694216f03208e068c16894f7e40 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 25 Oct 2015 18:02:46 +0100 Subject: [PATCH 43/74] [Horus] Warning started. No transparency for now --- radio/src/Makefile | 2 +- radio/src/gui/Horus/bitmaps.cpp | 3 +++ radio/src/gui/Horus/gui.h | 2 ++ radio/src/gui/Horus/lcd.cpp | 2 +- radio/src/gui/Horus/menus.h | 8 ++++---- radio/src/gui/Horus/popups.cpp | 6 +++--- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/radio/src/Makefile b/radio/src/Makefile index 0640d8ac4..515715e7d 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -393,7 +393,7 @@ endif IMG2LBM = -$(PYTHON) ../util/img2lbm.py BIN2LBM = -$(PYTHON) ../util/bin2lbm.py -FONT2PNG = -xvfb-run $(PYTHON) ../util/font2png.py +FONT2PNG = -$(PYTHON) ../util/font2png.py TRANSLATE = -$(PYTHON) ../util/translate.py VERSION = 2.2.0$(OPENTX_VERSION_SUFFIX) diff --git a/radio/src/gui/Horus/bitmaps.cpp b/radio/src/gui/Horus/bitmaps.cpp index 0dcd39ba1..2b9e3d0cb 100644 --- a/radio/src/gui/Horus/bitmaps.cpp +++ b/radio/src/gui/Horus/bitmaps.cpp @@ -212,3 +212,6 @@ const uint8_t * const LBM_MODEL_ICONS[] = { LBM_TELEMETRY_ICON }; +const uint16_t LBM_ASTERISK[] = { +#include "../../bitmaps/Horus/asterisk.lbm" +}; diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index 9c4628839..fea301634 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -98,3 +98,5 @@ extern const uint8_t LBM_RSCALE[]; extern const uint8_t * const LBM_RADIO_ICONS[]; extern const uint8_t * const LBM_MODEL_ICONS[]; +extern const uint16_t LBM_ASTERISK[]; + diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index f27968745..c8350f003 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -143,7 +143,7 @@ uint8_t getStringInfo(const char *s) int getFontHeight(LcdFlags flags) { - static const int heightTable[16] = { 12, 10, 11, 14, 18, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 }; + static const int heightTable[16] = { 12, 10, 11, 14, 32, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 }; return heightTable[FONTSIZE(flags) >> 8]; } diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h index 8363231c2..3c33771cc 100644 --- a/radio/src/gui/Horus/menus.h +++ b/radio/src/gui/Horus/menus.h @@ -267,14 +267,14 @@ extern uint8_t s_warning_type; #define MENU_X 80 #define MENU_W LCD_W-(2*MENU_X) -#define POPUP_X 45 +#define POPUP_X 130 #define POPUP_Y 70 -#define POPUP_W LCD_W-(2*POPUP_X) -#define POPUP_H 90 +#define POPUP_W 300 +#define POPUP_H 130 #define WARNING_LINE_LEN 32 #define WARNING_LINE_X (POPUP_X+86) #define WARNING_LINE_Y (POPUP_Y+9) -#define WARNING_INFOLINE_Y (WARNING_LINE_Y+43) +#define WARNING_INFOLINE_Y (WARNING_LINE_Y+64) void displayBox(); void displayPopup(const char *title); diff --git a/radio/src/gui/Horus/popups.cpp b/radio/src/gui/Horus/popups.cpp index fdeb79ef1..7530e9770 100644 --- a/radio/src/gui/Horus/popups.cpp +++ b/radio/src/gui/Horus/popups.cpp @@ -52,7 +52,7 @@ void displayAlertBox() lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); - // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_ALERT); + lcdDrawBitmap(POPUP_X-80, POPUP_Y-30, LBM_ASTERISK); } void displayWarningBox() @@ -79,10 +79,10 @@ void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SO #if defined(TRANSLATIONS_FR) || defined(TRANSLATIONS_IT) || defined(TRANSLATIONS_CZ) lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y, STR_WARNING, ALARM_COLOR|DBLSIZE); - lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y+15, title, ALARM_COLOR|DBLSIZE); + lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y+25, title, ALARM_COLOR|DBLSIZE); #else lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y, title, ALARM_COLOR|DBLSIZE); - lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y+15, STR_WARNING, ALARM_COLOR|DBLSIZE); + lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y+25, STR_WARNING, ALARM_COLOR|DBLSIZE); #endif if (t) lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y, t); From 4605f9d61dbc896df99205610eae52e738ebb0b8 Mon Sep 17 00:00:00 2001 From: Damjan Adamic Date: Sun, 25 Oct 2015 20:09:26 +0100 Subject: [PATCH 44/74] Companion settings moved to 2.2 directory --- companion/src/appdata.cpp | 22 +++++++++++++++++++++- companion/src/appdata.h | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/companion/src/appdata.cpp b/companion/src/appdata.cpp index e6e45029d..86b7db3e9 100644 --- a/companion/src/appdata.cpp +++ b/companion/src/appdata.cpp @@ -604,8 +604,28 @@ AppData::AppData() for (int i=0; i #define COMPANY "OpenTX" -#define PRODUCT "Companion 2.1" +#define PRODUCT "Companion 2.2" #define MAX_PROFILES 15 #define MAX_JOYSTICKS 8 From 7e5374f31d7e4248bea1b9b370c60ca4725bbc10 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 26 Oct 2015 18:16:44 +0100 Subject: [PATCH 45/74] Should fix Travis builds --- radio/src/Makefile | 7 ++++++- radio/util/font2png.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/radio/src/Makefile b/radio/src/Makefile index 515715e7d..c7d077f69 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -393,8 +393,13 @@ endif IMG2LBM = -$(PYTHON) ../util/img2lbm.py BIN2LBM = -$(PYTHON) ../util/bin2lbm.py -FONT2PNG = -$(PYTHON) ../util/font2png.py +ifeq ($(OS), Windows_NT) + FONT2PNG = -$(PYTHON) ../util/font2png.py +else + FONT2PNG = -xvfb-run --server-args="-screen 0 1024x768x24" $(PYTHON) ../util/font2png.py +endif TRANSLATE = -$(PYTHON) ../util/translate.py + VERSION = 2.2.0$(OPENTX_VERSION_SUFFIX) THIRDPARTY = thirdparty diff --git a/radio/util/font2png.py b/radio/util/font2png.py index f9f5a3f20..47ddec9fd 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -44,7 +44,9 @@ def createFontBitmap(filename, fontname, fontsize, fontbold, foreground, backgro font.setPixelSize(fontsize) font.setBold(fontbold) font.setHintingPreference(QtGui.QFont.PreferNoHinting) + font.setStyleStrategy(QtGui.QFont.PreferAntialias) metrics = QtGui.QFontMetrics(font) + width = getFontWidth(fontsize, metrics) top, bottom = getFontTopBottom(fontsize, metrics) extraImage = QtGui.QImage("fonts/extra_%dpx.png" % (fontsize+1)) From 03fabd95ac4626839116ff1de3d84f242dd7aab5 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 26 Oct 2015 19:15:54 +0100 Subject: [PATCH 46/74] [Horus] Model Selection menu started --- ...ry_empty_img.png => mask_library_slot.png} | Bin radio/src/gui/Horus/bitmaps.cpp | 16 ++ radio/src/gui/Horus/gui.h | 5 + radio/src/gui/Horus/menu_model_select.cpp | 59 +++++- radio/src/gui/Horus/view_statistics.cpp | 11 +- radio/src/myeeprom.h | 2 +- radio/src/storage/sdcard_raw.cpp | 173 ++++++++++++++++++ 7 files changed, 258 insertions(+), 8 deletions(-) rename radio/src/bitmaps/Horus/{mask_library_empty_img.png => mask_library_slot.png} (100%) diff --git a/radio/src/bitmaps/Horus/mask_library_empty_img.png b/radio/src/bitmaps/Horus/mask_library_slot.png similarity index 100% rename from radio/src/bitmaps/Horus/mask_library_empty_img.png rename to radio/src/bitmaps/Horus/mask_library_slot.png diff --git a/radio/src/gui/Horus/bitmaps.cpp b/radio/src/gui/Horus/bitmaps.cpp index 2b9e3d0cb..df598671b 100644 --- a/radio/src/gui/Horus/bitmaps.cpp +++ b/radio/src/gui/Horus/bitmaps.cpp @@ -212,6 +212,22 @@ const uint8_t * const LBM_MODEL_ICONS[] = { LBM_TELEMETRY_ICON }; +/* + * Model selection screen bitmaps + */ + +const uint8_t LBM_LIBRARY_ICON[] = { +#include "../../bitmaps/Horus/mask_library.lbm" +}; + +const uint8_t LBM_LIBRARY_SLOT[] = { +#include "../../bitmaps/Horus/mask_library_slot.lbm" +}; + +/* + * Other + */ + const uint16_t LBM_ASTERISK[] = { #include "../../bitmaps/Horus/asterisk.lbm" }; diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index fea301634..83ace5c22 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -98,5 +98,10 @@ extern const uint8_t LBM_RSCALE[]; extern const uint8_t * const LBM_RADIO_ICONS[]; extern const uint8_t * const LBM_MODEL_ICONS[]; +// Model selection icons +extern const uint8_t LBM_LIBRARY_ICON[]; +extern const uint8_t LBM_LIBRARY_SLOT[]; + +// Other icons extern const uint16_t LBM_ASTERISK[]; diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp index fe0f450dc..1b03c8536 100644 --- a/radio/src/gui/Horus/menu_model_select.cpp +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -37,19 +37,76 @@ #define CATEGORIES_WIDTH 140 +void drawModel(coord_t x, coord_t y, const char * name) +{ + lcdDrawSolidRect(x, y, 153, 61, LINE_COLOR); + lcdDrawText(x+5, y+2, name, TEXT_COLOR); + lcdDrawSolidHorizontalLine(x+5, y+19, 143, LINE_COLOR); + lcdDrawBitmapPattern(x+5, y+23, LBM_LIBRARY_SLOT, TEXT_COLOR); +} + void menuModelSelect(evt_t event) { + switch(event) { + case 0: + // no need to refresh the screen + return; + + case EVT_KEY_FIRST(KEY_EXIT): + chainMenu(menuMainView); + return; + } + // Header lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); - // lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); + lcdDrawBitmapPattern(5, 7, LBM_LIBRARY_ICON, MENU_TITLE_COLOR); drawTopmenuDatetime(); // Categories lcdDrawSolidFilledRect(0, MENU_HEADER_HEIGHT, CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TITLE_BGCOLOR); + StorageModelsList storage; + const char * error = storageOpenModelsList(&storage); + if (!error) { + bool result = true; + coord_t y = MENU_HEADER_HEIGHT+10; + while (y < LCD_H) { + char line[256]; + result = storageReadNextCategory(&storage, line, sizeof(line)-1); + if (!result) + break; + lcdDrawText(MENUS_MARGIN_LEFT, y, line, MENU_TITLE_COLOR); + y += FH; + } + } + // Models lcdDrawSolidFilledRect(CATEGORIES_WIDTH, MENU_HEADER_HEIGHT, LCD_W-CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TEXT_BGCOLOR); + if (!error) { + bool result = storageSeekCategory(&storage, 0); + coord_t y = MENU_HEADER_HEIGHT+7; + unsigned int i = 0; + while (result) { + char line[256]; + result = storageReadNextModel(&storage, line, sizeof(line)-1); + if (!result) + break; + if (y < LCD_H) { + coord_t x; + if (i & 1) { + drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+162, y, line); + y += 66; + } + else { + drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+1, y, line); + } + } + i++; + } + } + + drawScrollbar(DEFAULT_SCROLLBAR_X, MENU_HEADER_HEIGHT+7, MENU_FOOTER_TOP-MENU_HEADER_HEIGHT-15, 0, 4, 2); // Footer lcdDrawSolidFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); diff --git a/radio/src/gui/Horus/view_statistics.cpp b/radio/src/gui/Horus/view_statistics.cpp index dee6c55ed..36763ab99 100644 --- a/radio/src/gui/Horus/view_statistics.cpp +++ b/radio/src/gui/Horus/view_statistics.cpp @@ -38,13 +38,10 @@ void menuStatisticsView(evt_t event) { - drawMenuTemplate("Statistics", event); - - switch(event) - { + switch(event) { case EVT_KEY_FIRST(KEY_UP): chainMenu(menuStatisticsDebug); - break; + return; case EVT_KEY_LONG(KEY_MENU): g_eeGeneral.globalTimer = 0; @@ -54,9 +51,11 @@ void menuStatisticsView(evt_t event) case EVT_KEY_FIRST(KEY_EXIT): chainMenu(menuMainView); - break; + return; } + drawMenuTemplate("Statistics", event); + lcdDrawText( 10, MENU_CONTENT_TOP + FH*0, "\037\145TOT:\037\317BATT:", HEADER_COLOR); lcdDrawText( 10, MENU_CONTENT_TOP + FH*1, "TM1:\037\145TM2:", HEADER_COLOR); lcdDrawText( 10, MENU_CONTENT_TOP + FH*2, "THR:\037\145TH%:", HEADER_COLOR); diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 7cb024650..3ad41c06d 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -119,7 +119,7 @@ #define MAX_INPUTS 32 #define NUM_TRAINER 16 #define NUM_POTS 3 - #define NUM_XPOTS 0 + #define NUM_XPOTS 3 #define MAX_SENSORS 32 #elif defined(PCBFLAMENCO) #define MAX_MODELS 60 diff --git a/radio/src/storage/sdcard_raw.cpp b/radio/src/storage/sdcard_raw.cpp index 8d2fd1ed1..f34e3be0b 100644 --- a/radio/src/storage/sdcard_raw.cpp +++ b/radio/src/storage/sdcard_raw.cpp @@ -143,6 +143,7 @@ const char * loadModel(const char * filename) } const char RADIO_SETTINGS_PATH[] = RADIO_PATH "/radio.bin"; +const char RADIO_MODELSLIST_PATH[] = RADIO_PATH "/models.txt"; const char * loadGeneralSettings() { @@ -215,3 +216,175 @@ void storageFormat() sdCheckAndCreateDirectory(RADIO_PATH); sdCheckAndCreateDirectory(MODELS_PATH); } + +struct StorageModelsList { + FIL file; +}; + +const char * storageOpenModelsList(StorageModelsList * storage) +{ + FRESULT result = f_open(&storage->file, RADIO_MODELSLIST_PATH, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + return NULL; +} + +bool storageReadNextLine(StorageModelsList * storage, char * line, int maxlen) +{ + char c; + unsigned int read; + int len=0; + while (1) { + FRESULT result = f_read(&storage->file, (uint8_t *)&c, 1, &read); + if (result != FR_OK || read != 1) { + line[len] = '\0'; + return false; + } + if (c == '\n') { + if (len > 0) { + // we skip empty lines + line[len] = '\0'; + return true; + } + } + else if (c != '\r' && len < maxlen) { + line[len++] = c; + } + } +} + +int storageGetCategoryLength(const char * line) +{ + int len = strlen(line); + if (len > 2 && line[0] == '[' && line[len-1] == ']') { + return len-2; + } + else { + return 0; + } +} + +bool storageReadNextCategory(StorageModelsList * storage, char * line, int maxlen) +{ + bool result = true; + while (result) { + result = storageReadNextLine(storage, line, maxlen); + int len = storageGetCategoryLength(line); + if (len > 0) { + memmove(line, &line[1], len); + line[len] = '\0'; + return result; + } + } + line[0] = '\0'; + return false; +} + +bool storageSeekCategory(StorageModelsList * storage, int category) +{ + f_lseek(&storage->file, 0); + char line[256] = ""; + int result = true; + for (int i=0; result && i<=category; i++) { + result = storageReadNextCategory(storage, line, sizeof(line)-1); + } + return line[0] != '\0'; +} + +bool storageReadNextModel(StorageModelsList * storage, char * line, int maxlen) +{ + bool result = true; + while (result) { + result = storageReadNextLine(storage, line, maxlen); + if (line[0] == '[') + return false; + else if (line[0] != '\0') + return result; + } + line[0] = '\0'; + return false; +} + +#define STORAGE_INSERT 1 +#define STORAGE_REMOVE 2 +#define STORAGE_RENAME 3 + +const char * storageModifyModel(unsigned int operation, int category, int position, const char * name="") +{ + StorageModelsList storage; + FIL file; + const char * error = storageOpenModelsList(&storage); + if (error) { + return error; + } + + { + FRESULT result = f_open(&file, RADIO_PATH "/models.tmp", FA_CREATE_ALWAYS | FA_WRITE); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + } + + bool operationdone = false; + bool result = true; + int categoryindex = -1; + int modelindex = 0; + + while (result) { + char line[256]; + result = storageReadNextLine(&storage, line, sizeof(line)-1); + if (!operationdone) { + int len = storageGetCategoryLength(line); + if (len > 0) { + if (categoryindex++ == category) { + operationdone = true; + if (operation == STORAGE_INSERT) { + f_puts(name, &file); + f_putc('\n', &file); + } + } + } + else if (categoryindex == category) { + if (modelindex++ == position) { + operationdone = true; + if (operation == STORAGE_INSERT) { + f_puts(name, &file); + f_putc('\n', &file); + } + else if (operation == STORAGE_RENAME) { + f_puts(name, &file); + f_putc('\n', &file); + continue; + } + } + } + } + f_puts(line, &file); + f_putc('\n', &file); + } + + if (!operationdone && categoryindex>=0 && operation==STORAGE_INSERT) { + f_puts(name, &file); + f_putc('\n', &file); + } + + f_close(&file); + f_rename(RADIO_PATH "/models.tmp", RADIO_MODELSLIST_PATH); + return NULL; +} + +const char * storageInsertModel(const char * name, int category, int position) +{ + return storageModifyModel(STORAGE_INSERT, category, position, name); +} + +const char * storageRemoveModel(int category, int position) +{ + return storageModifyModel(STORAGE_REMOVE, category, position); +} + +const char * storageRenameModel(const char * name, int category, int position) +{ + return storageModifyModel(STORAGE_RENAME, category, position, name); +} From a2c50ce2f8fd82a4cb0b35635c9a37d0cf5a7025 Mon Sep 17 00:00:00 2001 From: martin Date: Fri, 30 Oct 2015 00:17:52 +0100 Subject: [PATCH 47/74] font extra 15x17 --- radio/src/fonts/extra_17px.png | Bin 4792 -> 4871 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/radio/src/fonts/extra_17px.png b/radio/src/fonts/extra_17px.png index dea8be4c7319209d98545f2cfe6e78451aaa4aa3..5f3d9cb91fbbde5d4a3acddf28c67bbcd09d98d1 100644 GIT binary patch literal 4871 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 literal 4792 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 Date: Wed, 28 Oct 2015 19:11:36 +0100 Subject: [PATCH 48/74] [Horus] Massstorage (still not working) --- radio/src/targets/Horus/usbd_storage_msd.cpp | 642 ++----------------- 1 file changed, 39 insertions(+), 603 deletions(-) diff --git a/radio/src/targets/Horus/usbd_storage_msd.cpp b/radio/src/targets/Horus/usbd_storage_msd.cpp index 59fec18eb..5c7c2df1d 100644 --- a/radio/src/targets/Horus/usbd_storage_msd.cpp +++ b/radio/src/targets/Horus/usbd_storage_msd.cpp @@ -28,6 +28,7 @@ /* Includes ------------------------------------------------------------------*/ #include "../../opentx.h" #include "../../thirdparty/FatFs/diskio.h" +#include "sdio_sd.h" #include "board_horus.h" #if defined(__cplusplus) && !defined(SIMU) @@ -37,12 +38,8 @@ extern "C" { #include "usbd_msc_mem.h" #include "usb_conf.h" -#if defined(BOOT) -#define STORAGE_LUN_NBR 2 -#else /* SD card only when not running bootloader */ #define STORAGE_LUN_NBR 1 -#endif #define BLOCKSIZE 512 /* USB Mass storage Standard Inquiry Data */ @@ -58,30 +55,10 @@ const unsigned char STORAGE_Inquirydata[] = {//36 0x00, 0x00, 'F', 'r', 'S', 'k', 'y', ' ', ' ', ' ', /* Manufacturer : 8 bytes */ - 'T', 'a', 'r', 'a', 'n', 'i', 's', ' ', /* Product : 16 Bytes */ + 'H', 'o', 'r', 'u', 's', ' ', ' ', ' ', /* Product : 16 Bytes */ 'R', 'a', 'd', 'i', 'o', ' ', ' ', ' ', '1', '.', '0', '0', /* Version : 4 Bytes */ -#if defined(BOOT) - /* LUN 1 */ - 0x00, - 0x80, - 0x02, - 0x02, - (USBD_STD_INQUIRY_LENGTH - 5), - 0x00, - 0x00, - 0x00, - 'F', 'r', 'S', 'k', 'y', ' ', ' ', ' ', /* Manufacturer : 8 bytes */ - 'T', 'a', 'r', 'a', 'n', 'i', 's', ' ', /* Product : 16 Bytes */ - 'R', 'a', 'd', 'i', 'o', ' ', ' ', ' ', - '1', '.', '0' ,'0', /* Version : 4 Bytes */ -#endif -}; - -#if defined(BOOT) -int32_t fat12Write( const uint8_t *buffer, uint16_t sector, uint16_t count ) ; -int32_t fat12Read( uint8_t *buffer, uint16_t sector, uint16_t count ) ; -#endif +}; int8_t STORAGE_Init (uint8_t lun); @@ -151,49 +128,31 @@ int8_t STORAGE_Init (uint8_t lun) */ int8_t STORAGE_GetCapacity (uint8_t lun, uint32_t *block_num, uint32_t *block_size) { -#if defined(BOOT) - if (lun == 1) { - *block_size = BLOCKSIZE; - *block_num = 3 + EESIZE/BLOCKSIZE + FLASHSIZE/BLOCKSIZE; - } - else -#endif - { - if (!SD_CARD_PRESENT()) - return -1; + if (!SD_CARD_PRESENT()) + return -1; - *block_size = BLOCKSIZE; + *block_size = BLOCKSIZE; - static DWORD sector_count = 0; - if (sector_count == 0) { - if (disk_ioctl(0, GET_SECTOR_COUNT, §or_count) != RES_OK) { - sector_count = 0; - return -1; - } + static DWORD sector_count = 0; + if (sector_count == 0) { + if (disk_ioctl(0, GET_SECTOR_COUNT, §or_count) != RES_OK) { + sector_count = 0; + return -1; } - - *block_num = sector_count; } + *block_num = sector_count; // sector_count; + return 0; } -#if defined(BOOT) -uint8_t lunReady[2] ; -#else uint8_t lunReady[1] ; -#endif void usbPluggedIn() { if (lunReady[0] == 0) { lunReady[0] = 1; } -#if defined(BOOT) - if (lunReady[1] == 0) { - lunReady[1] = 1; - } -#endif } /** @@ -203,21 +162,9 @@ void usbPluggedIn() */ int8_t STORAGE_IsReady (uint8_t lun) { -#if defined(BOOT) - if (lun == 1) { - if (lunReady[1] == 0) { - return -1 ; - } - return 0 ; - } - else -#endif - { - if (lunReady[0] == 0) { - return -1 ; - } - return SD_CARD_PRESENT() ? 0 : -1; - } + if (lunReady[0] == 0) + return -1 ; + return SD_CARD_PRESENT() ? 0 : -1; } /** @@ -239,26 +186,24 @@ int8_t STORAGE_IsWriteProtected (uint8_t lun) * @retval Status */ -int8_t SD_ReadSectors(uint8_t *buff, uint32_t sector, uint32_t count); - int8_t STORAGE_Read (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { -#if defined(BOOT) - if (lun == 1) { - if (fat12Read(buf, blk_addr, blk_len) != 0) - return -1; - } - else -#endif - { - if (SD_ReadSectors(buf, blk_addr, blk_len) != 0) { - return -1; - } + if (SD_ReadMultiBlocksFIXED(buf, blk_addr, BLOCKSIZE, blk_len) != 0) { + return -1; } + SDTransferState State; + + SD_Error Status = SD_WaitReadOperation(); // Check if the Transfer is finished + + while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) + + if ((State == SD_TRANSFER_ERROR) || (Status != SD_OK)) + return -1; + return 0; } /** @@ -270,26 +215,24 @@ int8_t STORAGE_Read (uint8_t lun, * @retval Status */ -int8_t SD_WriteSectors(const uint8_t *buf, uint32_t sector, uint32_t count); - int8_t STORAGE_Write (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { -#if defined(BOOT) - if (lun == 1) { - if (fat12Write(buf, blk_addr, blk_len) != 0) - return -1; - } - else -#endif - { - if (SD_WriteSectors(buf, blk_addr, blk_len) != 0) - return -1; - } + if (SD_WriteMultiBlocksFIXED(buf, blk_addr, BLOCKSIZE, blk_len) != 0) + return -1; - return (0); + SDTransferState State; + + SD_Error Status = SD_WaitWriteOperation(); // Check if the Transfer is finished + + while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) + + if ((State == SD_TRANSFER_ERROR) || (Status != SD_OK)) + return -1; + + return 0; } /** @@ -302,510 +245,3 @@ int8_t STORAGE_GetMaxLun (void) { return STORAGE_LUN_NBR - 1; } - -#if defined(BOOT) -//------------------------------------------------------------------------------ -/** - * FAT12 boot sector partition. - */ -const char g_FATboot[BLOCKSIZE] = -{ - 0xeb, 0x3c, 0x90, // Jump instruction. - 0x39, 0x58, 0x20, 0x54, 0x45, 0x41, 0x4D, 0x00, // OEM Name - 0x00, 0x02, // Bytes per sector - 0x08, // Sectors per FS cluster. - 0x01, 0x00, // Reserved sector count - - 0x01, // Number of FATs - 0x10, 0x00, // Number of root directory entries - 3+(EESIZE/BLOCKSIZE), (FLASHSIZE/BLOCKSIZE)>>8, // Total sectors - 0xf8, // Media descriptor - 0x01, 0x00, // Sectors per FAT table - 0x20, 0x00, // Sectors per track - 0x40, 0x00, // Number of heads - 0x00, 0x00, 0x00, 0x00, // Number of hidden sectors - - 0x00, 0x00, 0x00, 0x00, // Large number of sectors. - 0x00, // Physical drive number - 0x00, // Reserved - 0x29, // Extended boot signature - 0xCE, 0xFA, 0x5C, 0xD1, // Disk ID (serial number) - 0x56, 0x49, 0x52, 0x54, 0x46, 0x53, 0x20, 0x20, 0x20, 0x20, 0x20, // Volume label - 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, // FAT file system type - 0x00, 0x00, // OS boot code - - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa -}; - -#if defined(REV4a) -const char g_FAT[BLOCKSIZE] = -{ - 0xF8, 0xFF, 0xFF, 0x03, 0x40, 0x00, 0x05, 0x60, 0x00, 0x07, 0x80, 0x00, 0x09, 0xA0, 0x00, 0x0B, - 0xC0, 0x00, 0x0D, 0xE0, 0x00, 0x0F, 0x00, 0x01, 0x11, 0xF0, 0xFF, 0x13, 0x40, 0x01, 0x15, 0x60, - 0x01, 0x17, 0x80, 0x01, 0x19, 0xA0, 0x01, 0x1B, 0xC0, 0x01, 0x1D, 0xE0, 0x01, 0x1F, 0x00, 0x02, - 0x21, 0x20, 0x02, 0x23, 0x40, 0x02, 0x25, 0x60, 0x02, 0x27, 0x80, 0x02, 0x29, 0xA0, 0x02, 0x2B, - 0xC0, 0x02, 0x2D, 0xE0, 0x02, 0x2F, 0x00, 0x03, 0x31, 0x20, 0x03, 0x33, 0x40, 0x03, 0x35, 0x60, - 0x03, 0x37, 0x80, 0x03, 0x39, 0xA0, 0x03, 0x3B, 0xC0, 0x03, 0x3D, 0xE0, 0x03, 0x3F, 0x00, 0x04, - 0x41, 0x20, 0x04, 0x43, 0x40, 0x04, 0x45, 0x60, 0x04, 0x47, 0x80, 0x04, 0x49, 0xA0, 0x04, 0x4B, - 0xC0, 0x04, 0x4D, 0xE0, 0x04, 0x4F, 0x00, 0x05, 0x51, 0x20, 0x05, 0x53, 0x40, 0x05, 0x55, 0x60, - 0x05, 0x57, 0x80, 0x05, 0x59, 0xA0, 0x05, 0x5B, 0xC0, 0x05, 0x5D, 0xE0, 0x05, 0x5F, 0x00, 0x06, - 0x61, 0x20, 0x06, 0x63, 0x40, 0x06, 0x65, 0x60, 0x06, 0x67, 0x80, 0x06, 0x69, 0xA0, 0x06, 0x6B, - 0xC0, 0x06, 0x6D, 0xE0, 0x06, 0x6F, 0x00, 0x07, 0x71, 0x20, 0x07, 0x73, 0x40, 0x07, 0x75, 0x60, - 0x07, 0x77, 0x80, 0x07, 0x79, 0xA0, 0x07, 0x7B, 0xC0, 0x07, 0x7D, 0xE0, 0x07, 0x7F, 0x00, 0x08, - 0x81, 0x20, 0x08, 0x83, 0x40, 0x08, 0x85, 0x60, 0x08, 0x87, 0x80, 0x08, 0x89, 0xA0, 0x08, 0x8B, - 0xC0, 0x08, 0x8D, 0xE0, 0x08, 0x8F, 0x00, 0x09, 0x91, 0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; -#else -const char g_FAT[BLOCKSIZE] = -{ - 0xF8, 0xFF, 0xFF, 0x03, 0x40, 0x00, 0x05, 0x60, 0x00, 0x07, 0x80, 0x00, 0x09, 0xF0, 0xFF, 0x0B, - 0xC0, 0x00, 0x0D, 0xE0, 0x00, 0x0F, 0x00, 0x01, 0x11, 0x20, 0x01, 0x13, 0x40, 0x01, 0x15, 0x60, - 0x01, 0x17, 0x80, 0x01, 0x19, 0xA0, 0x01, 0x1B, 0xC0, 0x01, 0x1D, 0xE0, 0x01, 0x1F, 0x00, 0x02, - 0x21, 0x20, 0x02, 0x23, 0x40, 0x02, 0x25, 0x60, 0x02, 0x27, 0x80, 0x02, 0x29, 0xA0, 0x02, 0x2B, - 0xC0, 0x02, 0x2D, 0xE0, 0x02, 0x2F, 0x00, 0x03, 0x31, 0x20, 0x03, 0x33, 0x40, 0x03, 0x35, 0x60, - 0x03, 0x37, 0x80, 0x03, 0x39, 0xA0, 0x03, 0x3B, 0xC0, 0x03, 0x3D, 0xE0, 0x03, 0x3F, 0x00, 0x04, - 0x41, 0x20, 0x04, 0x43, 0x40, 0x04, 0x45, 0x60, 0x04, 0x47, 0x80, 0x04, 0x49, 0xA0, 0x04, 0x4B, - 0xC0, 0x04, 0x4D, 0xE0, 0x04, 0x4F, 0x00, 0x05, 0x51, 0x20, 0x05, 0x53, 0x40, 0x05, 0x55, 0x60, - 0x05, 0x57, 0x80, 0x05, 0x59, 0xA0, 0x05, 0x5B, 0xC0, 0x05, 0x5D, 0xE0, 0x05, 0x5F, 0x00, 0x06, - 0x61, 0x20, 0x06, 0x63, 0x40, 0x06, 0x65, 0x60, 0x06, 0x67, 0x80, 0x06, 0x69, 0xA0, 0x06, 0x6B, - 0xC0, 0x06, 0x6D, 0xE0, 0x06, 0x6F, 0x00, 0x07, 0x71, 0x20, 0x07, 0x73, 0x40, 0x07, 0x75, 0x60, - 0x07, 0x77, 0x80, 0x07, 0x79, 0xA0, 0x07, 0x7B, 0xC0, 0x07, 0x7D, 0xE0, 0x07, 0x7F, 0x00, 0x08, - 0x81, 0x20, 0x08, 0x83, 0x40, 0x08, 0x85, 0x60, 0x08, 0x87, 0x80, 0x08, 0x89, 0xF0, 0xFF, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; -#endif - -// File Attributes -// 0 0x01 Read Only. -// 1 0x02 Hidden. -// 2 0x04 System. -// 3 0x08 Volume Label. -// 4 0x10 Subdirectory. -// 5 0x20 Archive. -// 6 0x40 Device. -// 7 0x80 Reserved. - -typedef struct -{ - uint8_t name[8]; - uint8_t ext[3]; - uint8_t attribute; - uint8_t reserved; - uint8_t create_time_ms; - uint16_t create_time; - uint16_t create_date; - uint16_t access_date; - uint16_t ea_index; - uint16_t modify_time; - uint16_t modify_date; - uint16_t start_cluster; - uint32_t file_size; -} FATDirEntry_t; - -// First 16 FAT root directory entries (1 sector) -const FATDirEntry_t g_DIRroot[16] = -{ - { - { 'T', 'A', 'R', 'A', 'N', 'I', 'S', ' '}, - { ' ', ' ', ' '}, - 0x08, // Volume - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { 'E', 'E', 'P', 'R', 'O', 'M', ' ', ' '}, - { 'B', 'I', 'N'}, - 0x20, // Archive - 0x00, - 0x3E, - 0xA301, - 0x3D55, - 0x3D55, - 0x0000, - 0xA302, - 0x3D55, - 0x0002, - EESIZE - }, - { - { 'F', 'I', 'R', 'M', 'W', 'A', 'R', 'E'}, - { 'B', 'I', 'N'}, - 0x20, // Archive - 0x00, - 0x3E, - 0xA301, - 0x3D55, - 0x3D55, - 0x0000, - 0xA302, - 0x3D55, - 0x0002 + (EESIZE/BLOCKSIZE)/8, - FLASHSIZE - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, - { - { '\x00', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, - { ' ', ' ', ' '}, - 0x00, - 0x00, - 0x00, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x0000, - 0x00000000 - }, -}; - -// count is number of 512 byte sectors -int32_t fat12Read( uint8_t *buffer, uint16_t sector, uint16_t count ) -{ - while ( count ) - { - if (sector == 0) { - memcpy(buffer, g_FATboot, BLOCKSIZE ) ; - } - else if (sector == 1/*Reserved sector count*/) { - // FAT table. - memcpy(buffer, g_FAT, BLOCKSIZE); - } - else if (sector == 2) { - memcpy(buffer, g_DIRroot, BLOCKSIZE ) ; - } - else if (sector < 3 + (EESIZE/BLOCKSIZE)) { - eepromReadBlock(buffer, (sector-3)*BLOCKSIZE, BLOCKSIZE); - } - else if (sector < 3 + (EESIZE/BLOCKSIZE) + (FLASHSIZE/BLOCKSIZE)) { - uint32_t address; - address = sector - 3 - (EESIZE/BLOCKSIZE); - address *= BLOCKSIZE; - address += FIRMWARE_ADDRESS; - memcpy(buffer, (uint8_t *)address, BLOCKSIZE); - } - buffer += BLOCKSIZE ; - sector++ ; - count-- ; - } - return 0 ; -} - -int32_t fat12Write(const uint8_t *buffer, uint16_t sector, uint16_t count) -{ - enum FatWriteOperation { - FATWRITE_NONE, - FATWRITE_EEPROM, - FATWRITE_FIRMWARE - }; - - static unsigned int operation = FATWRITE_NONE; - - TRACE("FAT12 Write(sector=%d, count=%d)", sector, count); - - if (sector < 3) { - // reserved, read-only - } - else if (sector < 3 + (EESIZE/BLOCKSIZE)) { - // eeprom - while (count) { - if (operation == FATWRITE_NONE && isEepromStart(buffer)) { - TRACE("EEPROM start found in sector %d", sector); - operation = FATWRITE_EEPROM; - } - if (operation == FATWRITE_EEPROM) { - eepromWriteBlock((uint8_t *)buffer, (sector-3)*BLOCKSIZE, BLOCKSIZE); - } - buffer += BLOCKSIZE; - sector++; - count--; - if (sector-3 >= (EESIZE/BLOCKSIZE)) { - TRACE("EEPROM end written at sector %d", sector-1); - operation = FATWRITE_NONE; - } - } - } - else if (sector < 3 + (EESIZE/BLOCKSIZE) + (FLASHSIZE/BLOCKSIZE)) { - // firmware - uint32_t address; - address = sector - 3 - (EESIZE/BLOCKSIZE); - address *= BLOCKSIZE; - address += FIRMWARE_ADDRESS; - while (count) { - for (uint32_t i=0; i= FIRMWARE_ADDRESS+BOOTLOADER_SIZE/*protect bootloader*/ && address <= FIRMWARE_ADDRESS+FLASHSIZE-FLASH_PAGESIZE) { - if (address == FIRMWARE_ADDRESS+BOOTLOADER_SIZE && isFirmwareStart(buffer)) { - TRACE("FIRMWARE start found in sector %d", sector); - operation = FATWRITE_FIRMWARE; - } - if (operation == FATWRITE_FIRMWARE) { - writeFlash((uint32_t *)address, (uint32_t *)buffer); - } - } - address += FLASH_PAGESIZE; - buffer += FLASH_PAGESIZE; - } - sector++; - count--; - if (sector-3-(EESIZE/BLOCKSIZE) >= (FLASHSIZE/BLOCKSIZE)) { - TRACE("FIRMWARE end written at sector %d", sector-1); - operation = FATWRITE_NONE; - } - } - } - return 0 ; -} -#endif From a556c50956db067f8450e8e9b2ff30874cc946ad Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 3 Nov 2015 19:53:55 +0100 Subject: [PATCH 49/74] [Horus] Next round of screen changes + refactoring --- companion/src/CMakeLists.txt | 26 +- companion/src/simulation/simulatorimport.h | 6 +- radio/src/Makefile | 11 +- radio/src/audio_arm.cpp | 4 +- radio/src/bmp.cpp | 176 +++++++++- radio/src/cli.cpp | 55 ++-- radio/src/gui/9X/lcd.h | 10 - radio/src/gui/9X/menu_general.cpp | 2 +- radio/src/gui/9X/menu_model.cpp | 2 +- radio/src/gui/9X/menus.h | 5 +- radio/src/gui/9X/view_mavlink.h | 2 +- radio/src/gui/Horus/bitmaps.cpp | 24 +- radio/src/gui/Horus/gui.h | 11 +- radio/src/gui/Horus/lcd.cpp | 111 ++++--- radio/src/gui/Horus/lcd.h | 53 ++-- radio/src/gui/Horus/menu_general.cpp | 16 +- radio/src/gui/Horus/menu_general_calib.cpp | 17 +- .../src/gui/Horus/menu_general_sdmanager.cpp | 163 +++++----- radio/src/gui/Horus/menu_general_setup.cpp | 40 +-- radio/src/gui/Horus/menu_general_trainer.cpp | 14 +- radio/src/gui/Horus/menu_general_version.cpp | 4 +- radio/src/gui/Horus/menu_model.cpp | 34 +- radio/src/gui/Horus/menu_model_curves.cpp | 26 +- .../gui/Horus/menu_model_custom_functions.cpp | 16 +- .../src/gui/Horus/menu_model_flightmodes.cpp | 12 +- radio/src/gui/Horus/menu_model_gvars.cpp | 10 +- radio/src/gui/Horus/menu_model_heli.cpp | 18 +- .../src/gui/Horus/menu_model_inputs_mixes.cpp | 30 +- radio/src/gui/Horus/menu_model_limits.cpp | 22 +- .../gui/Horus/menu_model_logical_switches.cpp | 16 +- radio/src/gui/Horus/menu_model_select.cpp | 201 ++++++++++-- radio/src/gui/Horus/menu_model_setup.cpp | 27 +- radio/src/gui/Horus/menu_model_telemetry.cpp | 32 +- radio/src/gui/Horus/menus.cpp | 4 +- radio/src/gui/Horus/menus.h | 81 +++-- radio/src/gui/Horus/navigation.cpp | 69 +++- radio/src/gui/Horus/popups.cpp | 53 ++-- radio/src/gui/Horus/splash.cpp | 4 +- radio/src/gui/Horus/view_about.cpp | 72 +++-- radio/src/gui/Horus/view_channels.cpp | 10 +- radio/src/gui/Horus/view_main.cpp | 83 ++--- radio/src/gui/Horus/view_statistics.cpp | 62 ++-- radio/src/gui/Horus/view_text.cpp | 18 +- radio/src/gui/Horus/widgets.cpp | 49 ++- radio/src/gui/Taranis/lcd.cpp | 14 +- radio/src/gui/Taranis/lcd.h | 23 +- radio/src/gui/Taranis/menu_general.cpp | 2 +- .../gui/Taranis/menu_general_sdmanager.cpp | 40 +-- .../src/gui/Taranis/menu_general_version.cpp | 2 +- radio/src/gui/Taranis/menu_model.cpp | 2 +- radio/src/gui/Taranis/menus.h | 5 +- radio/src/gui/gui.h | 5 + radio/src/keys.h | 6 +- radio/src/main_arm.cpp | 155 +++++++-- radio/src/main_avr.cpp | 1 - radio/src/myeeprom.h | 3 +- radio/src/opentx.cpp | 25 +- radio/src/opentx.h | 32 +- radio/src/sdcard.cpp | 106 ++++++- radio/src/sdcard.h | 6 +- radio/src/serial.cpp | 3 +- radio/src/simu.cpp | 12 +- radio/src/storage/eeprom_conversions.cpp | 4 +- radio/src/storage/sdcard_raw.cpp | 34 +- radio/src/storage/sdcard_raw.h | 4 + radio/src/storage/storage_common.cpp | 4 +- radio/src/strhelpers.cpp | 22 +- radio/src/strhelpers.h | 41 +++ radio/src/targets/Horus/adc_driver.cpp | 4 + radio/src/targets/Horus/board_horus.cpp | 23 +- radio/src/targets/Horus/board_horus.h | 10 +- radio/src/targets/Horus/diskio.cpp | 158 +++++---- radio/src/targets/Horus/hal.h | 40 +-- radio/src/targets/Horus/lcd_driver.cpp | 96 +++--- radio/src/targets/Horus/sdio_sd.c | 300 ++++-------------- radio/src/targets/Horus/sdio_sd.h | 2 + radio/src/targets/Horus/usb_conf.h | 2 +- radio/src/targets/Horus/usbd_desc.c | 8 +- radio/src/targets/Horus/usbd_desc.h | 3 +- radio/src/targets/Horus/usbd_storage_msd.cpp | 31 +- radio/src/targets/Taranis/diskio.cpp | 1 - radio/src/targets/Taranis/usb_bsp.c | 4 +- radio/src/targets/Taranis/usb_driver.c | 2 +- radio/src/targets/Taranis/usbd_cdc.cpp | 2 +- radio/src/targets/Taranis/usbd_conf.h | 2 +- radio/src/targets/Taranis/usbd_desc.c | 6 +- radio/src/targets/Taranis/usbd_hid_joystick.c | 2 +- .../src/targets/Taranis/usbd_storage_msd.cpp | 4 +- radio/src/targets/simu/simpgmspace.cpp | 31 +- radio/src/tasks_arm.cpp | 4 +- radio/src/tests/lcd.cpp | 4 +- radio/src/translations.cpp | 20 +- radio/src/translations.h | 7 +- radio/src/translations/cz.h.txt | 1 + radio/src/translations/de.h.txt | 1 + radio/src/translations/en.h.txt | 4 + radio/src/translations/es.h.txt | 1 + radio/src/translations/fi.h.txt | 1 + radio/src/translations/fr.h.txt | 1 + radio/src/translations/it.h.txt | 1 + radio/src/translations/nl.h.txt | 1 + radio/src/translations/pl.h.txt | 1 + radio/src/translations/pt.h.txt | 1 + radio/src/translations/se.h.txt | 1 + radio/util/font2png.py | 4 +- radio/util/img2lbm.py | 7 +- 106 files changed, 1848 insertions(+), 1195 deletions(-) create mode 100644 radio/src/strhelpers.h diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index 15f5c2b71..77105bb42 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -1,19 +1,19 @@ PROJECT( companion ) -SET(C9X_VERSION_MAJOR "2") -SET(C9X_VERSION_MINOR "2") -SET(C9X_VERSION_REVISION "0") -SET(C9X_VERSION_SUFFIX $ENV{OPENTX_VERSION_SUFFIX}) +SET(VERSION_MAJOR "2") +SET(VERSION_MINOR "2") +SET(VERSION_REVISION "0") +SET(VERSION_SUFFIX $ENV{OPENTX_VERSION_SUFFIX}) -SET(C9X_VERSION ${C9X_VERSION_MAJOR}.${C9X_VERSION_MINOR}.${C9X_VERSION_REVISION}${C9X_VERSION_SUFFIX}) -MESSAGE(STATUS "OpenTX Companion ${C9X_VERSION}") +SET(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}${VERSION_SUFFIX}) +MESSAGE(STATUS "OpenTX Companion ${VERSION}") CMAKE_MINIMUM_REQUIRED( VERSION 2.8.12 ) SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") - SET(C9X_NAME_SUFFIX ${C9X_VERSION_MAJOR}${C9X_VERSION_MINOR}) + SET(C9X_NAME_SUFFIX ${VERSION_MAJOR}${VERSION_MINOR}) SET(COMPANION_NAME "companion${C9X_NAME_SUFFIX}") SET(SIMULATOR_NAME "simulator${C9X_NAME_SUFFIX}") SET( SIMULATOR_LIB_PATH ${CMAKE_INSTALL_PREFIX}/lib/companion${C9X_NAME_SUFFIX} ) @@ -454,18 +454,18 @@ if(DPKG_PROGRAM) OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE ) - SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}_${C9X_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") + SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}_${VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Bertrand Songis ") - SET(CPACK_DEBIAN_PACKAGE_VERSION ${C9X_VERSION}) - SET(CPACK_DEBIAN_PACKAGE_VERSION_MAJOR ${C9X_VERSION_MAJOR}) - SET(CPACK_DEBIAN_PACKAGE_VERSION_MINOR ${C9X_VERSION_MINOR}) + SET(CPACK_DEBIAN_PACKAGE_VERSION ${VERSION}) + SET(CPACK_DEBIAN_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR}) + SET(CPACK_DEBIAN_PACKAGE_VERSION_MINOR ${VERSION_MINOR}) SET(CPACK_DEBIAN_ARCHITECTURE $(CMAKE_SYSTEM_PROCESSOR)) SET(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) else(DPKG_PROGRAM) SET(CPACK_GENERATOR "RPM") - SET(CPACK_RPM_PACKAGE_VERSION ${C9X_VERSION}) + SET(CPACK_RPM_PACKAGE_VERSION ${VERSION}) SET(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}) - SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}-${C9X_VERSION}-${CMAKE_SYSTEM_PROCESSOR}") + SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME_LOWERCASE}-${VERSION}-${CMAKE_SYSTEM_PROCESSOR}") endif(DPKG_PROGRAM) INCLUDE(CPack) diff --git a/companion/src/simulation/simulatorimport.h b/companion/src/simulation/simulatorimport.h index 59a835a57..01b86bd96 100644 --- a/companion/src/simulation/simulatorimport.h +++ b/companion/src/simulation/simulatorimport.h @@ -80,9 +80,9 @@ for (int fm=0; fm LCD_W) { + return STR_INCOMPATIBLE; + } + + FRESULT result = f_open(&bmpFile, filename, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + + if (f_size(&bmpFile) < 14) { + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + result = f_read(&bmpFile, buf, 14, &read); + if (result != FR_OK || read != 14) { + f_close(&bmpFile); + return SDCARD_ERROR(result); + } + + if (buf[0] != 'B' || buf[1] != 'M') { + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + uint32_t fsize = *((uint32_t *)&buf[2]); + uint32_t hsize = *((uint32_t *)&buf[10]); /* header size */ + + uint32_t len = limit((uint32_t)4, (uint32_t)(hsize-14), (uint32_t)32); + result = f_read(&bmpFile, buf, len, &read); + if (result != FR_OK || read != len) { + f_close(&bmpFile); + return SDCARD_ERROR(result); + } + + uint32_t ihsize = *((uint32_t *)&buf[0]); /* more header size */ + + /* invalid header size */ + if (ihsize + 14 > hsize) { + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + /* sometimes file size is set to some headers size, set a real size in that case */ + if (fsize == 14 || fsize == ihsize + 14) + fsize = f_size(&bmpFile) - 2; + + /* declared file size less than header size */ + if (fsize <= hsize) { + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + uint32_t w, h; + + switch (ihsize){ + case 40: // windib + case 56: // windib v3 + case 64: // OS/2 v2 + case 108: // windib v4 + case 124: // windib v5 + w = *((uint32_t *)&buf[4]); + h = *((uint32_t *)&buf[8]); + buf += 12; + break; + case 12: // OS/2 v1 + w = *((uint16_t *)&buf[4]); + h = *((uint16_t *)&buf[6]); + buf += 8; + break; + default: + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + if (*((uint16_t *)&buf[0]) != 1) { /* planes */ + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + if (w > width || h > height) { + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + uint16_t depth = *((uint16_t *)&buf[2]); + + buf = &bmpBuf[0]; + + if (depth == 4) { + if (f_lseek(&bmpFile, hsize-64) != FR_OK || f_read(&bmpFile, buf, 64, &read) != FR_OK || read != 64) { + f_close(&bmpFile); + return SDCARD_ERROR(result); + } + for (uint8_t i=0; i<16; i++) { + palette[i] = buf[4*i]; + } + } + else { + if (f_lseek(&bmpFile, hsize) != FR_OK) { + f_close(&bmpFile); + return SDCARD_ERROR(result); + } + } + + uint16_t * dest = (uint16_t *)bmp; + + *dest++ = w; + *dest++ = h; + + memset(dest, 0, BITMAP_BUFFER_SIZE(w, h) - 4); + + uint32_t rowSize; + + switch (depth) { + case 32: + for (int i=h-1; i>=0; i--) { + uint8_t * dst = ((uint8_t *)dest) + i*w*3; + for (unsigned int j=0; j>24) & 0xff, (pixel>>16) & 0xff, (pixel>>8) & 0xff); + dst += 2; + *dst++ = 0x0F; + } + } + break; + case 1: + break; + + case 4: + rowSize = ((4*w+31)/32)*4; + for (int32_t i=h-1; i>=0; i--) { + result = f_read(&bmpFile, buf, rowSize, &read); + if (result != FR_OK || read != rowSize) { + f_close(&bmpFile); + return SDCARD_ERROR(result); + } + uint8_t * dst = ((uint8_t *)dest) + i*w*3; + for (uint32_t j=0; j> ((j & 1) ? 0 : 4)) & 0x0F; + uint8_t val = palette[index]; + *((uint16_t *)dst) = RGB(val, val, val); + dst += 2; + *dst++ = 0x0F; + } + } + break; + + default: + f_close(&bmpFile); + return STR_INCOMPATIBLE; + } + + f_close(&bmpFile); + return 0; +} +#endif const uint8_t bmpHeader[] = { 0x42, 0x4d, 0xF8, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x28, 0x00, @@ -236,7 +408,7 @@ const char *writeScreenshot() return error; } - char *tmp = strAppend(&filename[sizeof(SCREENSHOTS_PATH)-1], "/screen"); + char * tmp = strAppend(&filename[sizeof(SCREENSHOTS_PATH)-1], "/screen"); tmp = strAppendDate(tmp, true); strcpy(tmp, BITMAPS_EXT); diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index 119fc156f..f217cde9d 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -160,39 +160,11 @@ int cliTrace(const char ** argv) int cliStackInfo(const char ** argv) { - int tid = 0; - if (toInt(argv, 1, &tid) > 0) { - int available = 0; - int total = 0; - switch(tid) { - case MENU_TASK_INDEX: - total = menusStack.size(); - available = menusStack.available(); - break; - case MIXER_TASK_INDEX: - total = mixerStack.size(); - available = mixerStack.available(); - break; - case AUDIO_TASK_INDEX: - total = audioStack.size(); - available = audioStack.available(); - break; - case CLI_TASK_INDEX: - total = cliStack.size(); - available = cliStack.available(); - break; - case MAIN_TASK_INDEX: - total = stackAvailable(); - available = stackSize(); - break; - default: - break; - } - serialPrint("%d available (%d total)", available, total); - } - else { - serialPrint("%s: Invalid argument \"%s\"", argv[0], argv[1]); - } + serialPrint("[MAIN] %d available / %d", stackAvailable(), stackSize()); + serialPrint("[MENUS] %d available / %d", menusStack.available(), menusStack.size()); + serialPrint("[MIXER] %d available / %d", mixerStack.available(), mixerStack.size()); + serialPrint("[AUDIO] %d available / %d", audioStack.available(), audioStack.size()); + serialPrint("[CLI] %d available / %d", cliStack.available(), cliStack.size()); return 0; } @@ -269,7 +241,7 @@ int cliDisplay(const char ** argv) name[len] = '\0'; serialPrint("[%s] = %s", name, switchState(EnumKeys(i)) ? "on" : "off"); } -#if defined(ROTARY_ENCODER_NAVIGATION) +#if defined(ROTARY_ENCODER_NAVIGATION) || defined(REV9E) || defined(PCBHORUS) || defined(PCBFLAMENCO) serialPrint("[Enc.] = %d", rotencValue / 2); #endif for (int i=TRM_BASE; i<=TRM_LAST; i++) { @@ -342,6 +314,18 @@ int cliDisplay(const char ** argv) return 0; } +int cliDebugVars(const char ** argv) +{ + extern unsigned int ioMutexReq, ioMutexRel; + extern unsigned int sdReadRetries; + + serialPrint("ioMutexReq=%d", ioMutexReq); + serialPrint("ioMutexRel=%d", ioMutexRel); + serialPrint("sdReadRetries=%d", sdReadRetries); + + return 0; +} + int cliHelp(const char ** argv); const CliCommand cliCommands[] = { @@ -349,7 +333,7 @@ const CliCommand cliCommands[] = { { "ls", cliLs, "" }, { "play", cliPlay, "" }, { "print", cliDisplay, "
[] | " }, - { "stackinfo", cliStackInfo, "" }, + { "stackinfo", cliStackInfo, "" }, { "trace", cliTrace, "on | off" }, { "volume", cliVolume, "" }, #if defined(PCBFLAMENCO) @@ -357,6 +341,7 @@ const CliCommand cliCommands[] = { { "write_bq24195", cliWriteBQ24195, " " }, #endif { "help", cliHelp, "[]" }, + { "debugvars", cliDebugVars, "" }, { NULL, NULL, NULL } /* sentinel */ }; diff --git a/radio/src/gui/9X/lcd.h b/radio/src/gui/9X/lcd.h index d0bd79e5f..6a1e7ba86 100644 --- a/radio/src/gui/9X/lcd.h +++ b/radio/src/gui/9X/lcd.h @@ -268,14 +268,4 @@ void lcdRefresh(); #define BLINK_ON_PHASE (g_blinkTmr10ms & (1<<6)) #endif -#if defined(SIMU) - extern bool lcd_refresh; - extern display_t lcd_buf[DISPLAY_BUFFER_SIZE]; -#endif - -char *strAppend(char * dest, const char * source, int len=0); -char *strSetCursor(char *dest, int position); -char *strAppendDate(char * str, bool time=false); -char *strAppendFilename(char * dest, const char * filename, const int size); - #endif diff --git a/radio/src/gui/9X/menu_general.cpp b/radio/src/gui/9X/menu_general.cpp index d7d56ec13..461f15fbe 100644 --- a/radio/src/gui/9X/menu_general.cpp +++ b/radio/src/gui/9X/menu_general.cpp @@ -88,7 +88,7 @@ enum EnumTabDiag { e_Calib }; -const MenuFuncP_PROGMEM menuTabGeneral[] PROGMEM = { +const MenuFuncP menuTabGeneral[] PROGMEM = { menuGeneralSetup, CASE_SDCARD(menuGeneralSdManager) CASE_CPUARM(menuGeneralCustomFunctions) diff --git a/radio/src/gui/9X/menu_model.cpp b/radio/src/gui/9X/menu_model.cpp index 446334295..5a4a28bc8 100644 --- a/radio/src/gui/9X/menu_model.cpp +++ b/radio/src/gui/9X/menu_model.cpp @@ -99,7 +99,7 @@ uint8_t editDelay(const coord_t y, const uint8_t event, const uint8_t attr, cons } #define EDIT_DELAY(x, y, event, attr, str, delay) editDelay(y, event, attr, str, delay) -const MenuFuncP_PROGMEM menuTabModel[] PROGMEM = { +const MenuFuncP menuTabModel[] PROGMEM = { menuModelSelect, menuModelSetup, CASE_HELI(menuModelHeli) diff --git a/radio/src/gui/9X/menus.h b/radio/src/gui/9X/menus.h index a68d6f8bf..703b59acf 100644 --- a/radio/src/gui/9X/menus.h +++ b/radio/src/gui/9X/menus.h @@ -67,9 +67,8 @@ extern uint8_t calibrationState; void menu_lcd_onoff(coord_t x, coord_t y, uint8_t value, LcdFlags attr); typedef void (*MenuFuncP)(uint8_t event); -typedef void (*MenuFuncP_PROGMEM)(uint8_t event); -extern const MenuFuncP_PROGMEM menuTabModel[]; -extern const MenuFuncP_PROGMEM menuTabGeneral[]; +extern const MenuFuncP menuTabModel[]; +extern const MenuFuncP menuTabGeneral[]; extern MenuFuncP g_menuStack[5]; extern uint8_t g_menuPos[4]; diff --git a/radio/src/gui/9X/view_mavlink.h b/radio/src/gui/9X/view_mavlink.h index 9032124f2..64cf00cd0 100644 --- a/radio/src/gui/9X/view_mavlink.h +++ b/radio/src/gui/9X/view_mavlink.h @@ -83,7 +83,7 @@ enum mavlink_menu_ { }; //! \brief Pointer array to mavlink settings menus. -const MenuFuncP_PROGMEM menuTabMav[] PROGMEM = { +const MenuFuncP menuTabMav[] PROGMEM = { menuTelemetryMavlinkSetup }; diff --git a/radio/src/gui/Horus/bitmaps.cpp b/radio/src/gui/Horus/bitmaps.cpp index df598671b..47e2a9660 100644 --- a/radio/src/gui/Horus/bitmaps.cpp +++ b/radio/src/gui/Horus/bitmaps.cpp @@ -64,7 +64,7 @@ const uint8_t LBM_CURRENT_DOT[] = { * Main view bitmaps */ -const uint16_t LBM_MAINVIEW_BACKGROUND[] = { +const uint8_t LBM_MAINVIEW_BACKGROUND[] = { #include "../../bitmaps/Horus/background.lbm" }; @@ -224,10 +224,30 @@ const uint8_t LBM_LIBRARY_SLOT[] = { #include "../../bitmaps/Horus/mask_library_slot.lbm" }; +const uint8_t LBM_LIBRARY_CURSOR[] = { +#include "../../bitmaps/Horus/mask_library_category_index.lbm" +}; + +const uint8_t LBM_SCORE0[] = { +#include "../../bitmaps/Horus/mask_library_score_0.lbm" +}; + +const uint8_t LBM_SCORE1[] = { +#include "../../bitmaps/Horus/mask_library_score_1.lbm" +}; + +const uint8_t LBM_STAR0[] = { +#include "../../bitmaps/Horus/mask_library_star_0.lbm" +}; + +const uint8_t LBM_STAR1[] = { +#include "../../bitmaps/Horus/mask_library_star_1.lbm" +}; + /* * Other */ -const uint16_t LBM_ASTERISK[] = { +const uint8_t LBM_ASTERISK[] = { #include "../../bitmaps/Horus/asterisk.lbm" }; diff --git a/radio/src/gui/Horus/gui.h b/radio/src/gui/Horus/gui.h index 83ace5c22..e7600c403 100644 --- a/radio/src/gui/Horus/gui.h +++ b/radio/src/gui/Horus/gui.h @@ -74,6 +74,8 @@ void drawFooter(); #define drawStatusLine(...) void drawProgressBar(const char *label); void updateProgressBar(int num, int den); +void drawTopmenuDatetime(); + // Header bitmaps extern const uint8_t LBM_TOPMENU_POLYGON[]; @@ -83,7 +85,7 @@ extern const uint8_t LBM_CURRENT_SHADOW[]; extern const uint8_t LBM_CURRENT_DOT[]; // Main view icons -extern const uint16_t LBM_MAINVIEW_BACKGROUND[]; +extern const uint8_t LBM_MAINVIEW_BACKGROUND[]; extern const uint8_t LBM_TOPMENU_OPENTX[]; extern const uint8_t LBM_TOPMENU_USB[]; extern const uint8_t LBM_HTRIM_FRAME[]; @@ -101,7 +103,12 @@ extern const uint8_t * const LBM_MODEL_ICONS[]; // Model selection icons extern const uint8_t LBM_LIBRARY_ICON[]; extern const uint8_t LBM_LIBRARY_SLOT[]; +extern const uint8_t LBM_LIBRARY_CURSOR[]; +extern const uint8_t LBM_SCORE0[]; +extern const uint8_t LBM_SCORE1[]; +extern const uint8_t LBM_STAR0[]; +extern const uint8_t LBM_STAR1[]; // Other icons -extern const uint16_t LBM_ASTERISK[]; +extern const uint8_t LBM_ASTERISK[]; diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index c8350f003..09a6a4a09 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -147,6 +147,27 @@ int getFontHeight(LcdFlags flags) return heightTable[FONTSIZE(flags) >> 8]; } +int getBitmapScale(const uint8_t * bmp, int dstWidth, int dstHeight) +{ + int widthScale, heightScale; + + int bmpWidth = getBitmapWidth(bmp); + int bmpHeight = getBitmapHeight(bmp); + + if (bmpWidth == 0 || bmpHeight == 0) + return 0; + + if (bmpWidth > dstWidth) + widthScale = -((bmpWidth+dstWidth-1) / dstWidth); + else + widthScale = (dstWidth / bmpWidth); + if (bmpHeight > dstHeight) + heightScale = -((bmpHeight+dstHeight-1) / dstHeight); + else + heightScale = (dstHeight / bmpHeight); + return min(widthScale, heightScale); +} + int getTextWidth(const pm_char * s, int len, LcdFlags flags) { const uint16_t * specs = fontspecsTable[FONTSIZE(flags) >> 8]; @@ -228,24 +249,14 @@ void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s, uint8_t len, Lc lcdNextPos = x; } -void lcd_putsn(coord_t x, coord_t y, const pm_char * s, uint8_t len) -{ - lcdDrawTextWithLen(x, y, s, len, TEXT_COLOR); -} - void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags flags) { lcdDrawTextWithLen(x, y, s, 255, flags); } -void lcd_puts(coord_t x, coord_t y, const pm_char * s) -{ - lcdDrawText(x, y, s, 0); -} - void lcd_putsLeft(coord_t y, const pm_char * s) { - lcd_puts(MENUS_MARGIN_LEFT, y, s); + lcdDrawText(MENUS_MARGIN_LEFT, y, s); } void lcd_putsCenter(coord_t y, const pm_char * s, LcdFlags attr) @@ -277,17 +288,7 @@ void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags flags) lcdDrawText(x, y, s, flags); } -void lcd_outdez8(coord_t x, coord_t y, int8_t val) -{ - lcd_outdezAtt(x, y, val); -} - -void lcd_outdezAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, const char *suffix, const char *prefix) -{ - lcd_outdezNAtt(x, y, val, flags, 0, suffix, prefix); -} - -void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, int len, const char *suffix, const char *prefix) +void lcdDrawNumber(coord_t x, coord_t y, int32_t val, LcdFlags flags, uint8_t len, const char * prefix, const char * suffix) { char str[48+1]; // max=16 for the prefix, 16 chars for the number, 16 chars for the suffix char *s = str+32; @@ -455,7 +456,7 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att) // TODO to be optimized with putsValueWithUnit void putsVolts(coord_t x, coord_t y, uint16_t volts, LcdFlags att) { - lcd_outdezAtt(x, y, (int16_t)volts, (~NO_UNIT) & (att | ((att&PREC2)==PREC2 ? 0 : PREC1)), (~att & NO_UNIT) ? "v" : NULL); + lcdDrawNumber(x, y, (int16_t)volts, (~NO_UNIT) & (att | ((att&PREC2)==PREC2 ? 0 : PREC1)), 0, NULL, (~att & NO_UNIT) ? "v" : NULL); } void putsVBat(coord_t x, coord_t y, LcdFlags att) @@ -477,7 +478,7 @@ void putsMixerSource(coord_t x, coord_t y, uint8_t idx, LcdFlags att) lcdDrawTextAtIndex(x, y, STR_VSRCRAW, 0, att); // TODO macro } else if (idx <= MIXSRC_LAST_INPUT) { - char s[32] = "\323"; + char s[32] = "\314"; if (ZEXIST(g_model.inputNames[idx-MIXSRC_FIRST_INPUT])) { zchar2str(s+1, g_model.inputNames[idx-MIXSRC_FIRST_INPUT], LEN_INPUT_NAME); s[1+LEN_INPUT_NAME] = '\0'; @@ -713,16 +714,16 @@ const pm_uint8_t bchunit_ar[] PROGMEM = { UNIT_DIST, // GPS Alt }; -void putsValueWithUnit(coord_t x, coord_t y, lcdint_t val, uint8_t unit, LcdFlags att) +void putsValueWithUnit(coord_t x, coord_t y, int32_t val, uint8_t unit, LcdFlags att) { // convertUnit(val, unit); if (!(att & NO_UNIT) && unit != UNIT_RAW) { char unitStr[8]; strAppend(unitStr, STR_VTELEMUNIT+1+unit*STR_VTELEMUNIT[0], STR_VTELEMUNIT[0]); - lcd_outdezAtt(x, y, val, att, unitStr); + lcdDrawNumber(x, y, val, att, 0, NULL, unitStr); } else { - lcd_outdezAtt(x, y, val, att); + lcdDrawNumber(x, y, val, att); } } @@ -734,7 +735,7 @@ void displayGpsCoords(coord_t x, coord_t y, TelemetryItem & telemetryItem, LcdFl { } -void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, lcdint_t value, LcdFlags att) +void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, int32_t value, LcdFlags att) { TelemetryItem & telemetryItem = telemetryItems[channel]; TelemetrySensor & telemetrySensor = g_model.telemetrySensors[channel]; @@ -755,7 +756,7 @@ void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, lcdint_t v } } -void putsChannelValue(coord_t x, coord_t y, source_t channel, lcdint_t value, LcdFlags att) +void putsChannelValue(coord_t x, coord_t y, source_t channel, int32_t value, LcdFlags att) { if (channel >= MIXSRC_FIRST_TELEM) { channel = (channel-MIXSRC_FIRST_TELEM) / 3; @@ -765,20 +766,20 @@ void putsChannelValue(coord_t x, coord_t y, source_t channel, lcdint_t value, Lc putsTimer(x, y, value, att); } else if (channel == MIXSRC_TX_VOLTAGE) { - lcd_outdezAtt(x, y, value, att|PREC1); + lcdDrawNumber(x, y, value, att|PREC1); } else if (channel < MIXSRC_FIRST_CH) { - lcd_outdezAtt(x, y, calcRESXto100(value), att); + lcdDrawNumber(x, y, calcRESXto100(value), att); } else if (channel <= MIXSRC_LAST_CH) { #if defined(PPM_UNIT_PERCENT_PREC1) - lcd_outdezAtt(x, y, calcRESXto1000(value), att|PREC1); + lcdDrawNumber(x, y, calcRESXto1000(value), att|PREC1); #else - lcd_outdezAtt(x, y, calcRESXto100(value), att); + lcdDrawNumber(x, y, calcRESXto100(value), att); #endif } else { - lcd_outdezAtt(x, y, value, att); + lcdDrawNumber(x, y, value, att); } } @@ -883,11 +884,10 @@ void lcdDrawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags } #if !defined(BOOT) -void lcdDrawBitmap(coord_t x, coord_t y, const uint16_t * img, coord_t offset, coord_t width) +void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * bmp, coord_t offset, coord_t width, int scale) { - const uint16_t * q = img; - coord_t w = *q++; - coord_t height = *q++; + int w = getBitmapWidth(bmp); + int height = getBitmapHeight(bmp); if (!width || width > w) { width = w; @@ -897,12 +897,33 @@ void lcdDrawBitmap(coord_t x, coord_t y, const uint16_t * img, coord_t offset, c width = LCD_W-x; } - for (coord_t row=0; row= -1 && scale <= 1) + return size; + else if (scale < 0) + return -(size / scale); + else + return size * scale; +} + +int getBitmapScale(const uint8_t * bmp, int dstWidth, int dstHeight); int getTextWidth(const pm_char *s, int len=0, LcdFlags flags=0); -void lcdDrawBitmap(coord_t x, coord_t y, const uint16_t * img, coord_t offset=0, coord_t width=0); +void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * img, coord_t offset=0, coord_t width=0, int scale=0); void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags flags=0, coord_t offset=0, coord_t width=0); #define lcdSetRefVolt(...) void lcdSetContrast(); #define lcdOff(...) -const pm_char * bmpLoad(uint8_t *dest, const char *filename, const unsigned int width, const unsigned int height); +const char * bmpLoad(uint8_t * dest, const char * filename, uint16_t width, uint16_t height); #if defined(BOOT) #define BLINK_ON_PHASE (0) @@ -304,14 +315,4 @@ const pm_char * bmpLoad(uint8_t *dest, const char *filename, const unsigned int #define BLINK_ON_PHASE (g_blinkTmr10ms & (1<<6)) #endif -#ifdef SIMU - extern bool lcd_refresh; - extern display_t lcd_buf[DISPLAY_BUFFER_SIZE]; -#endif - -char *strAppend(char * dest, const char * source, int len=0); -char *strSetCursor(char *dest, int position); -char *strAppendDate(char * str, bool time=false); -char *strAppendFilename(char * dest, const char * filename, const int size); - #endif // _LCD_H_ diff --git a/radio/src/gui/Horus/menu_general.cpp b/radio/src/gui/Horus/menu_general.cpp index 959c7e5c0..9be272d63 100644 --- a/radio/src/gui/Horus/menu_general.cpp +++ b/radio/src/gui/Horus/menu_general.cpp @@ -64,12 +64,12 @@ const LanguagePack * LP_CONST languagePacks[] = { NULL }; -void menuGeneralSetup(evt_t event); -void menuGeneralSdManager(evt_t event); -void menuGeneralCustomFunctions(evt_t event); -void menuGeneralTrainer(evt_t event); -void menuGeneralVersion(evt_t event); -void menuGeneralCalib(evt_t event); +bool menuGeneralSetup(evt_t event); +bool menuGeneralSdManager(evt_t event); +bool menuGeneralCustomFunctions(evt_t event); +bool menuGeneralTrainer(evt_t event); +bool menuGeneralVersion(evt_t event); +bool menuGeneralCalib(evt_t event); enum EnumTabDiag { e_Setup, @@ -80,7 +80,7 @@ enum EnumTabDiag { e_Vers, }; -const MenuFuncP_PROGMEM menuTabGeneral[] PROGMEM = { +const MenuFuncP menuTabGeneral[] PROGMEM = { menuGeneralSetup, menuGeneralSdManager, menuGeneralCustomFunctions, @@ -89,7 +89,7 @@ const MenuFuncP_PROGMEM menuTabGeneral[] PROGMEM = { menuGeneralVersion, }; -void menuGeneralCustomFunctions(evt_t event) +bool menuGeneralCustomFunctions(evt_t event) { MENU(STR_MENUGLOBALFUNCS, menuTabGeneral, e_GeneralCustomFunctions, NUM_CFN, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|4/*repeated*/ }); return menuCustomFunctions(event, g_eeGeneral.customFn, globalFunctionsContext); diff --git a/radio/src/gui/Horus/menu_general_calib.cpp b/radio/src/gui/Horus/menu_general_calib.cpp index c06d4bd82..bd6dd7674 100644 --- a/radio/src/gui/Horus/menu_general_calib.cpp +++ b/radio/src/gui/Horus/menu_general_calib.cpp @@ -69,7 +69,7 @@ void drawSticksPositions() drawStick(RBOX_CENTERX, calibratedStick[CONVERT_MODE(3)], calibStickVert); } -void menuCommonCalib(evt_t event) +bool menuCommonCalib(evt_t event) { for (uint8_t i=0; icount + 1; } if (steps > 0 && steps <= XPOTS_MULTIPOS_COUNT) { - lcd_outdezAtt(LCD_W/2-2+(i-POT1)*5, LCD_H-6, steps, TINSIZE); + lcdDrawNumber(LCD_W/2-2+(i-POT1)*5, LCD_H-6, steps, TINSIZE); } } #endif + + return true; } -void menuGeneralCalib(evt_t event) +bool menuGeneralCalib(evt_t event) { SIMPLE_MENU(STR_MENUCALIBRATION, menuTabGeneral, e_Calib, 0, DEFAULT_SCROLLBAR_X); @@ -232,17 +234,18 @@ void menuGeneralCalib(evt_t event) calibrationState = 0; } - menuCommonCalib(READ_ONLY() ? 0 : event); + return menuCommonCalib(READ_ONLY() ? 0 : event); } -void menuFirstCalib(evt_t event) +bool menuFirstCalib(evt_t event) { if (event == EVT_KEY_BREAK(KEY_EXIT) || reusableBuffer.calib.state == 4) { calibrationState = 0; chainMenu(menuMainView); + return false; } else { - drawMenuTemplate(STR_MENUCALIBRATION, event); - menuCommonCalib(event); + drawScreenTemplate(STR_MENUCALIBRATION); + return menuCommonCalib(event); } } diff --git a/radio/src/gui/Horus/menu_general_sdmanager.cpp b/radio/src/gui/Horus/menu_general_sdmanager.cpp index f3f8021eb..81679ca18 100644 --- a/radio/src/gui/Horus/menu_general_sdmanager.cpp +++ b/radio/src/gui/Horus/menu_general_sdmanager.cpp @@ -36,38 +36,52 @@ #include "../../opentx.h" -#define REFRESH_FILES() reusableBuffer.sdmanager.offset = 65535 +#define REFRESH_FILES() do { reusableBuffer.sdmanager.offset = 65535; lastBitmap = -1; } while (0) +#define NODE_TYPE(fname) fname[SD_SCREEN_FILE_LENGTH+1] +#define IS_DIRECTORY(fname) ((bool)(!NODE_TYPE(fname))) +#define IS_FILE(fname) ((bool)(NODE_TYPE(fname))) -void menuGeneralSdManagerInfo(evt_t event) +int lastBitmap; + +bool menuGeneralSdManagerInfo(evt_t event) { SIMPLE_SUBMENU(STR_SD_INFO_TITLE, 1, DEFAULT_SCROLLBAR_X); lcd_putsLeft(2*FH, STR_SD_TYPE); - lcd_puts(100, 2*FH, SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD); + lcdDrawText(100, 2*FH, SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD); lcd_putsLeft(3*FH, STR_SD_SIZE); - lcd_outdezAtt(100, 3*FH, sdGetSize(), LEFT, "M"); + lcdDrawNumber(100, 3*FH, sdGetSize(), LEFT, 0, NULL, "M"); lcd_putsLeft(4*FH, STR_SD_SECTORS); #if defined(SD_GET_FREE_BLOCKNR) - lcd_outdezAtt(100, 4*FH, SD_GET_FREE_BLOCKNR()/1000, LEFT, "/"); - lcd_outdezAtt(150, 4*FH, sdGetNoSectors()/1000, LEFT); + lcdDrawNumber(100, 4*FH, SD_GET_FREE_BLOCKNR()/1000, LEFT, 0, NULL, "/"); + lcdDrawNumber(150, 4*FH, sdGetNoSectors()/1000, LEFT); #else - lcd_outdezAtt(100, 4*FH, sdGetNoSectors()/1000, LEFT, "k"); + lcdDrawNumber(100, 4*FH, sdGetNoSectors()/1000, LEFT, 0, NULL, "k"); #endif lcd_putsLeft(5*FH, STR_SD_SPEED); - lcd_outdezAtt(100, 5*FH, SD_GET_SPEED()/1000, LEFT, "kb/s"); + lcdDrawNumber(100, 5*FH, SD_GET_SPEED()/1000, LEFT, 0, NULL, "kb/s"); + + return true; } inline bool isFilenameGreater(bool isfile, const char * fn, const char * line) { - return (isfile && !line[SD_SCREEN_FILE_LENGTH+1]) || (isfile==(bool)line[SD_SCREEN_FILE_LENGTH+1] && strcasecmp(fn, line) > 0); + return (isfile && IS_DIRECTORY(line)) || (isfile==IS_FILE(line) && strcasecmp(fn, line) > 0); } inline bool isFilenameLower(bool isfile, const char * fn, const char * line) { - return (!isfile && line[SD_SCREEN_FILE_LENGTH+1]) || (isfile==(bool)line[SD_SCREEN_FILE_LENGTH+1] && strcasecmp(fn, line) < 0); + return (!isfile && IS_FILE(line)) || (isfile==IS_FILE(line) && strcasecmp(fn, line) < 0); +} + +void getSelectionFullPath(char *lfn) +{ + f_getcwd(lfn, _MAX_LFN); + strcat(lfn, PSTR("/")); + strcat(lfn, reusableBuffer.sdmanager.lines[m_posVert - s_pgOfs]); } void onSdManagerMenu(const char *result) @@ -92,39 +106,39 @@ void onSdManagerMenu(const char *result) } else if (result == STR_PASTE) { f_getcwd(lfn, _MAX_LFN); - POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, lfn)); - REFRESH_FILES(); + // if destination is dir, copy into that dir + if (IS_DIRECTORY(line)) { + strcat(lfn, PSTR("/")); + strcat(lfn, line); + } + if (strcmp(clipboard.data.sd.directory, lfn)) { // prevent copying to the same directory + POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, clipboard.data.sd.filename, lfn)); + REFRESH_FILES(); + } } else if (result == STR_RENAME_FILE) { - char *ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); + memcpy(reusableBuffer.sdmanager.originalName, line, sizeof(reusableBuffer.sdmanager.originalName)); + char * ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); if (ext) { - memcpy(reusableBuffer.sdmanager.originalName, line, sizeof(reusableBuffer.sdmanager.originalName)); // write spaces to allow a longer filename memset(ext, ' ', SD_SCREEN_FILE_LENGTH-LEN_FILE_EXTENSION-(ext-line)); line[SD_SCREEN_FILE_LENGTH-LEN_FILE_EXTENSION] = '\0'; - s_editMode = EDIT_MODIFY_STRING; - editNameCursorPos = 0; } + else { + int len = strlen(line); + memset(line + strlen(line), ' ', SD_SCREEN_FILE_LENGTH - len); + line[SD_SCREEN_FILE_LENGTH] = '\0'; + } + s_editMode = EDIT_MODIFY_STRING; + editNameCursorPos = 0; } else if (result == STR_DELETE_FILE) { - f_getcwd(lfn, _MAX_LFN); - strcat(lfn, PSTR("/")); - strcat(lfn, line); + getSelectionFullPath(lfn); f_unlink(lfn); REFRESH_FILES(); - if (m_posVert == reusableBuffer.sdmanager.count-1) - m_posVert--; } - /* TODO else if (result == STR_LOAD_FILE) { - f_getcwd(lfn, _MAX_LFN); - strcat(lfn, "/"); - strcat(lfn, line); - POPUP_WARNING(eeLoadModelSD(lfn)); - } */ else if (result == STR_PLAY_FILE) { - f_getcwd(lfn, _MAX_LFN); - strcat(lfn, "/"); - strcat(lfn, line); + getSelectionFullPath(lfn); audioQueue.stopAll(); audioQueue.playFile(lfn, 0, ID_PLAY_FROM_SD_MANAGER); } @@ -136,38 +150,25 @@ void onSdManagerMenu(const char *result) } #endif else if (result == STR_VIEW_TEXT) { - f_getcwd(lfn, _MAX_LFN); - strcat(lfn, "/"); - strcat(lfn, line); + getSelectionFullPath(lfn); pushMenuTextView(lfn); } #if 0 else if (result == STR_FLASH_BOOTLOADER) { - f_getcwd(lfn, _MAX_LFN); - strcat(lfn, "/"); - strcat(lfn, line); + getSelectionFullPath(lfn); flashBootloader(lfn); } #endif #if defined(LUA) else if (result == STR_EXECUTE_FILE) { - f_getcwd(lfn, _MAX_LFN); - strcat(lfn, "/"); - strcat(lfn, line); + getSelectionFullPath(lfn); luaExec(lfn); } #endif } -void menuGeneralSdManager(evt_t _event) +bool menuGeneralSdManager(evt_t _event) { - FILINFO fno; - DIR dir; - char *fn; /* This function is assuming non-Unicode cfg. */ - TCHAR lfn[_MAX_LFN + 1]; - fno.lfname = lfn; - fno.lfsize = sizeof(lfn); - if (s_warning_result) { s_warning_result = 0; displayPopup(STR_FORMATTING); @@ -197,7 +198,7 @@ void menuGeneralSdManager(evt_t _event) case EVT_KEY_LONG(KEY_MENU): if (!READ_ONLY() && s_editMode == 0) { killEvents(_event); - MENU_ADD_ITEM(STR_SD_INFO); TODO: Implement + MENU_ADD_ITEM(STR_SD_INFO); MENU_ADD_ITEM(STR_SD_FORMAT); menuHandler = onSdManagerMenu; } @@ -219,26 +220,26 @@ void menuGeneralSdManager(evt_t _event) break; } else { - if (!reusableBuffer.sdmanager.lines[index][SD_SCREEN_FILE_LENGTH+1]) { + if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[index])) { f_chdir(reusableBuffer.sdmanager.lines[index]); s_pgOfs = 0; m_posVert = 1; index = 1; REFRESH_FILES(); killEvents(_event); - return; + return true; } } // no break case EVT_KEY_LONG(KEY_ENTER): - killEvents(_event); - if (m_posVert < 0) { - break; - } if (s_editMode == 0) { - char *line = reusableBuffer.sdmanager.lines[index]; - char *ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); + killEvents(_event); + char * line = reusableBuffer.sdmanager.lines[index]; + char * ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); + if (!strcmp(line, "..")) { + break; // no menu for parent dir + } if (ext) { if (!strcasecmp(ext, SOUNDS_EXT)) { MENU_ADD_ITEM(STR_PLAY_FILE); @@ -246,9 +247,6 @@ void menuGeneralSdManager(evt_t _event) else if (!strcasecmp(ext, TEXT_EXT)) { MENU_ADD_ITEM(STR_VIEW_TEXT); } - else if (!strcasecmp(ext, FIRMWARE_EXT) && !READ_ONLY()) { - MENU_ADD_ITEM(STR_FLASH_BOOTLOADER); - } #if defined(LUA) else if (!strcasecmp(ext, SCRIPTS_EXT)) { MENU_ADD_ITEM(STR_EXECUTE_FILE); @@ -256,19 +254,27 @@ void menuGeneralSdManager(evt_t _event) #endif } if (!READ_ONLY()) { - if (line[SD_SCREEN_FILE_LENGTH+1]) // it's a file + if (IS_FILE(line)) MENU_ADD_ITEM(STR_COPY_FILE); if (clipboard.type == CLIPBOARD_TYPE_SD_FILE) MENU_ADD_ITEM(STR_PASTE); - // MENU_ADD_ITEM(STR_RENAME_FILE); - MENU_ADD_ITEM(STR_DELETE_FILE); + MENU_ADD_ITEM(STR_RENAME_FILE); + if (IS_FILE(line)) + MENU_ADD_ITEM(STR_DELETE_FILE); } + menuHandler = onSdManagerMenu; } - menuHandler = onSdManagerMenu; break; } if (reusableBuffer.sdmanager.offset != s_pgOfs) { + FILINFO fno; + DIR dir; + char *fn; /* This function is assuming non-Unicode cfg. */ + TCHAR lfn[_MAX_LFN + 1]; + fno.lfname = lfn; + fno.lfsize = sizeof(lfn); + if (s_pgOfs == 0) { reusableBuffer.sdmanager.offset = 0; memset(reusableBuffer.sdmanager.lines, 0, sizeof(reusableBuffer.sdmanager.lines)); @@ -280,7 +286,7 @@ void menuGeneralSdManager(evt_t _event) else if (s_pgOfs > reusableBuffer.sdmanager.offset) { memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0])); memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0xff, SD_SCREEN_FILE_LENGTH); - reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1][SD_SCREEN_FILE_LENGTH+1] = 1; + NODE_TYPE(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1]) = 1; } else { memmove(reusableBuffer.sdmanager.lines[1], reusableBuffer.sdmanager.lines[0], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0])); @@ -311,9 +317,9 @@ void menuGeneralSdManager(evt_t _event) char *line = reusableBuffer.sdmanager.lines[i]; if (line[0] == '\0' || isFilenameLower(isfile, fn, line)) { if (i < NUM_BODY_LINES-1) memmove(reusableBuffer.sdmanager.lines[i+1], line, sizeof(reusableBuffer.sdmanager.lines[i]) * (NUM_BODY_LINES-1-i)); - memset(line, 0, sizeof(reusableBuffer.sdmanager.lines[i])); + memset(line, 0, sizeof(reusableBuffer.sdmanager.lines[0])); strcpy(line, fn); - line[SD_SCREEN_FILE_LENGTH+1] = isfile; + NODE_TYPE(line) = isfile; break; } } @@ -323,9 +329,9 @@ void menuGeneralSdManager(evt_t _event) char *line = reusableBuffer.sdmanager.lines[i]; if (line[0] == '\0' || isFilenameGreater(isfile, fn, line)) { if (i > 0) memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], sizeof(reusableBuffer.sdmanager.lines[0]) * i); - memset(line, 0, sizeof(reusableBuffer.sdmanager.lines[i])); + memset(line, 0, sizeof(reusableBuffer.sdmanager.lines[0])); strcpy(line, fn); - line[SD_SCREEN_FILE_LENGTH+1] = isfile; + NODE_TYPE(line) = isfile; break; } } @@ -334,14 +340,14 @@ void menuGeneralSdManager(evt_t _event) if (isFilenameGreater(isfile, fn, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-2]) && isFilenameLower(isfile, fn, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1])) { memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0, sizeof(reusableBuffer.sdmanager.lines[0])); strcpy(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], fn); - reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1][SD_SCREEN_FILE_LENGTH+1] = isfile; + NODE_TYPE(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1]) = isfile; } } else { if (isFilenameLower(isfile, fn, reusableBuffer.sdmanager.lines[1]) && isFilenameGreater(isfile, fn, reusableBuffer.sdmanager.lines[0])) { memset(reusableBuffer.sdmanager.lines[0], 0, sizeof(reusableBuffer.sdmanager.lines[0])); strcpy(reusableBuffer.sdmanager.lines[0], fn); - reusableBuffer.sdmanager.lines[0][SD_SCREEN_FILE_LENGTH+1] = isfile; + NODE_TYPE(reusableBuffer.sdmanager.lines[0]) = isfile; } } } @@ -354,8 +360,8 @@ void menuGeneralSdManager(evt_t _event) coord_t y = MENU_CONTENT_TOP + i*FH; LcdFlags attr = (index == i ? INVERS : 0); if (reusableBuffer.sdmanager.lines[i][0]) { - if (!reusableBuffer.sdmanager.lines[i][SD_SCREEN_FILE_LENGTH+1]) { - char s[SD_SCREEN_FILE_LENGTH+1+2]; + if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[i])) { + char s[sizeof(reusableBuffer.sdmanager.lines[0])+2]; sprintf(s, "[%s]", reusableBuffer.sdmanager.lines[i]); lcdDrawText(MENUS_MARGIN_LEFT, y, s, attr); } @@ -364,4 +370,17 @@ void menuGeneralSdManager(evt_t _event) } } } + + char * ext = getFileExtension(reusableBuffer.sdmanager.lines[index], SD_SCREEN_FILE_LENGTH+1); + if (ext && !strcasecmp(ext, BITMAPS_EXT)) { + if (lastBitmap != m_posVert) { + if (bmpLoad(modelBitmap, reusableBuffer.sdmanager.lines[index], MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT)) { + ((uint32_t *)modelBitmap)[0] = 0; + lastBitmap = m_posVert; + } + } + lcdDrawBitmap(LCD_W/2, (LCD_H-MODEL_BITMAP_HEIGHT)/2, modelBitmap); + } + + return true; } diff --git a/radio/src/gui/Horus/menu_general_setup.cpp b/radio/src/gui/Horus/menu_general_setup.cpp index abb443876..a0d2f41eb 100644 --- a/radio/src/gui/Horus/menu_general_setup.cpp +++ b/radio/src/gui/Horus/menu_general_setup.cpp @@ -104,7 +104,7 @@ enum menuGeneralSetupItems { ITEM_SETUP_MAX }; -void menuGeneralSetup(evt_t event) +bool menuGeneralSetup(evt_t event) { #if defined(RTCLOCK) struct gtm t; @@ -151,11 +151,11 @@ void menuGeneralSetup(evt_t event) uint8_t rowattr = (m_posHorz==j ? attr : 0); switch (j) { case 0: - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, t.tm_year+1900, LEFT|flags|rowattr); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, t.tm_year+1900, LEFT|flags|rowattr); if (rowattr && s_editMode>0) t.tm_year = checkIncDec(event, t.tm_year, 112, 200, 0); break; case 1: - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+MONTH_OFFSET, y, t.tm_mon+1, LEFT|flags|rowattr|LEADING0, 2); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN+MONTH_OFFSET, y, t.tm_mon+1, LEFT|flags|rowattr|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_mon = checkIncDec(event, t.tm_mon, 0, 11, 0); break; case 2: @@ -164,7 +164,7 @@ void menuGeneralSetup(evt_t event) int8_t dlim = (((((year%4==0) && (year%100!=0)) || (year%400==0)) && (t.tm_mon==1)) ? 1 : 0); static const pm_uint8_t dmon[] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; dlim += pgm_read_byte(&dmon[t.tm_mon]); - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+DAY_OFFSET, y, t.tm_mday, LEFT|flags|rowattr|LEADING0, 2); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN+DAY_OFFSET, y, t.tm_mday, LEFT|flags|rowattr|LEADING0, 2); if (rowattr && s_editMode>0) t.tm_mday = checkIncDec(event, t.tm_mday, 1, dlim, 0); break; } @@ -191,15 +191,15 @@ void menuGeneralSetup(evt_t event) switch (j) { case 0: if (rowattr && s_editMode>0) t.tm_hour = checkIncDec(event, t.tm_hour, 0, 23, 0); - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN, y, t.tm_hour, flags|rowattr|LEFT|LEADING0, 2); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, t.tm_hour, flags|rowattr|LEFT|LEADING0, 2); break; case 1: if (rowattr && s_editMode>0) t.tm_min = checkIncDec(event, t.tm_min, 0, 59, 0); - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+MINUTE_OFFSET, y, t.tm_min, flags|rowattr|LEFT|LEADING0, 2); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN+MINUTE_OFFSET, y, t.tm_min, flags|rowattr|LEFT|LEADING0, 2); break; case 2: if (rowattr && s_editMode>0) t.tm_sec = checkIncDec(event, t.tm_sec, 0, 59, 0); - lcd_outdezNAtt(RADIO_SETUP_2ND_COLUMN+SECOND_OFFSET, y, t.tm_sec, flags|rowattr|LEFT|LEADING0, 2); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN+SECOND_OFFSET, y, t.tm_sec, flags|rowattr|LEFT|LEADING0, 2); break; } } @@ -218,7 +218,7 @@ void menuGeneralSetup(evt_t event) lcdDrawSolidFilledRect(RADIO_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 90, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } putsVolts(RADIO_SETUP_2ND_COLUMN, y, 90+g_eeGeneral.vBatMin, color|(m_posHorz==0 ? attr : 0)|LEFT|NO_UNIT); - lcd_puts(RADIO_SETUP_2ND_COLUMN+20, y, "-"); + lcdDrawText(RADIO_SETUP_2ND_COLUMN+20, y, "-"); putsVolts(RADIO_SETUP_2ND_COLUMN+28, y, 120+g_eeGeneral.vBatMax, color|(m_posHorz>0 ? attr : 0)|LEFT|NO_UNIT); if (attr && s_editMode>0) { if (m_posHorz==0) @@ -271,7 +271,7 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_SPEAKER_PITCH: lcd_putsLeft( y, STR_SPKRPITCH); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.speakerPitch*15, attr|LEFT, "Hz", "+"); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.speakerPitch*15, attr|LEFT, 0, "+", "Hz"); if (attr) { CHECK_INCDEC_GENVAR(event, g_eeGeneral.speakerPitch, 0, 20); } @@ -286,17 +286,17 @@ void menuGeneralSetup(evt_t event) break; case ITEM_SETUP_VARIO_PITCH: lcd_putsLeft(y, STR_PITCH_AT_ZERO); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10), attr|LEFT, "Hz"); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10), attr|LEFT, 0, NULL, "Hz"); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioPitch, -40, 40); break; case ITEM_SETUP_VARIO_RANGE: lcd_putsLeft(y, STR_PITCH_AT_MAX); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10)+VARIO_FREQUENCY_RANGE+(g_eeGeneral.varioRange*10), attr|LEFT, "Hz"); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10)+VARIO_FREQUENCY_RANGE+(g_eeGeneral.varioRange*10), attr|LEFT, 0, NULL, "Hz"); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRange, -80, 80); break; case ITEM_SETUP_VARIO_REPEAT: lcd_putsLeft(y, STR_REPEAT_AT_ZERO); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, VARIO_REPEAT_ZERO+(g_eeGeneral.varioRepeat*10), attr|LEFT, STR_MS); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, VARIO_REPEAT_ZERO+(g_eeGeneral.varioRepeat*10), attr|LEFT, 0, NULL, STR_MS); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRepeat, -30, 50); break; #endif @@ -322,7 +322,7 @@ void menuGeneralSetup(evt_t event) #if 0 case ITEM_SETUP_CONTRAST: lcd_putsLeft(y, STR_CONTRAST); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.contrast, attr|LEFT); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.contrast, attr|LEFT); if (attr) { CHECK_INCDEC_GENVAR(event, g_eeGeneral.contrast, CONTRAST_MIN, CONTRAST_MAX); lcdSetContrast(); @@ -374,7 +374,7 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_INACTIVITY_ALARM: lcd_putsLeft( y,STR_INACTIVITYALARM); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.inactivityTimer, attr|LEFT, "m"); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.inactivityTimer, attr|LEFT, 0, NULL, "m"); if (attr) g_eeGeneral.inactivityTimer = checkIncDec(event, g_eeGeneral.inactivityTimer, 0, 250, EE_GENERAL); //0..250minutes break; @@ -393,13 +393,13 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_BACKLIGHT_DELAY: lcd_putsLeft(y, STR_BLDELAY); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.lightAutoOff*5, attr|LEFT, "s"); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.lightAutoOff*5, attr|LEFT, 0, NULL, "s"); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.lightAutoOff, 0, 600/5); break; case ITEM_SETUP_BRIGHTNESS: lcd_putsLeft(y, STR_BRIGHTNESS); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, 100-g_eeGeneral.backlightBright, attr|LEFT) ; + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, 100-g_eeGeneral.backlightBright, attr|LEFT) ; if (attr) { uint8_t b = 100 - g_eeGeneral.backlightBright; CHECK_INCDEC_GENVAR(event, b, 0, 100); @@ -419,7 +419,7 @@ void menuGeneralSetup(evt_t event) { lcd_putsLeft(y, STR_SPLASHSCREEN); if (SPLASH_NEEDED()) { - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, SPLASH_TIMEOUT/100, attr|LEFT, "s"); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, SPLASH_TIMEOUT/100, attr|LEFT, 0, NULL, "s"); } else { lcdDrawTextAtIndex(RADIO_SETUP_2ND_COLUMN, y, STR_MMMINV, 0, attr); // TODO define @@ -431,7 +431,7 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_TIMEZONE: lcd_putsLeft(y, STR_TIMEZONE); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.timezone, attr|LEFT); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.timezone, attr|LEFT); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.timezone, -12, 12); break; @@ -479,7 +479,7 @@ void menuGeneralSetup(evt_t event) case ITEM_SETUP_SWITCHES_DELAY: lcd_putsLeft(y, STR_SWITCHES_DELAY); - lcd_outdezAtt(RADIO_SETUP_2ND_COLUMN, y, 10*SWITCHES_DELAY(), attr|LEFT, STR_MS); + lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, 10*SWITCHES_DELAY(), attr|LEFT, 0, NULL, STR_MS); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.switchesDelay, -15, +15); break; @@ -519,4 +519,6 @@ void menuGeneralSetup(evt_t event) } } } + + return true; } diff --git a/radio/src/gui/Horus/menu_general_trainer.cpp b/radio/src/gui/Horus/menu_general_trainer.cpp index 888ba9b30..f005c8cd3 100644 --- a/radio/src/gui/Horus/menu_general_trainer.cpp +++ b/radio/src/gui/Horus/menu_general_trainer.cpp @@ -43,7 +43,7 @@ #define TRAINER_CALIB_POS 12 -void menuGeneralTrainer(evt_t event) +bool menuGeneralTrainer(evt_t event) { uint8_t y; bool slave = SLAVE_MODE(); @@ -52,7 +52,7 @@ void menuGeneralTrainer(evt_t event) if (slave) { lcd_putsCenter(5*FH, STR_SLAVE, TEXT_COLOR); - return; + return true; } uint8_t attr; @@ -83,7 +83,7 @@ void menuGeneralTrainer(evt_t event) break; case 1: - lcd_outdezAtt(TRAINER_COLUMN_2, y, td->studWeight, LEFT|attr, "%"); + lcdDrawNumber(TRAINER_COLUMN_2, y, td->studWeight, LEFT|attr, 0, NULL, "%"); if (attr&BLINK) CHECK_INCDEC_GENVAR(event, td->studWeight, -125, 125); break; @@ -98,7 +98,7 @@ void menuGeneralTrainer(evt_t event) attr = (sub==5) ? blink : 0; lcd_putsLeft(MENU_CONTENT_TOP + 5*FH, STR_MULTIPLIER); - lcd_outdezAtt(TRAINER_COLUMN_1, MENU_CONTENT_TOP + 5*FH, g_eeGeneral.PPM_Multiplier+10, LEFT|attr|PREC1); + lcdDrawNumber(TRAINER_COLUMN_1, MENU_CONTENT_TOP + 5*FH, g_eeGeneral.PPM_Multiplier+10, LEFT|attr|PREC1); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.PPM_Multiplier, -10, 40); attr = (sub==6) ? INVERS : 0; @@ -106,9 +106,9 @@ void menuGeneralTrainer(evt_t event) lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 6*FH, STR_CAL, attr); for (int i=0; i<4; i++) { #if defined (PPM_UNIT_PERCENT_PREC1) - lcd_outdezAtt(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])*2, LEFT|PREC1); + lcdDrawNumber(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])*2, LEFT|PREC1); #else - lcd_outdezAtt(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])/5, LEFT); + lcdDrawNumber(TRAINER_COLUMN_1+i*TRAINER_COLUMN_WIDTH, MENU_CONTENT_TOP + 6*FH, (ppmInput[i]-g_eeGeneral.trainer.calib[i])/5, LEFT); #endif } @@ -119,4 +119,6 @@ void menuGeneralTrainer(evt_t event) AUDIO_WARNING1(); } } + + return true; } diff --git a/radio/src/gui/Horus/menu_general_version.cpp b/radio/src/gui/Horus/menu_general_version.cpp index 7b01ba41e..f30e8ab27 100644 --- a/radio/src/gui/Horus/menu_general_version.cpp +++ b/radio/src/gui/Horus/menu_general_version.cpp @@ -36,7 +36,7 @@ #include "../../opentx.h" -void menuGeneralVersion(evt_t event) +bool menuGeneralVersion(evt_t event) { SIMPLE_MENU(STR_MENUVERSION, menuTabGeneral, e_Vers, 1, DEFAULT_SCROLLBAR_X); @@ -50,4 +50,6 @@ void menuGeneralVersion(evt_t event) // if (event == EVT_KEY_LONG(KEY_ENTER)) { // backupEeprom(); // } + + return true; } diff --git a/radio/src/gui/Horus/menu_model.cpp b/radio/src/gui/Horus/menu_model.cpp index 971ec92cf..ce0a2f1a9 100644 --- a/radio/src/gui/Horus/menu_model.cpp +++ b/radio/src/gui/Horus/menu_model.cpp @@ -53,21 +53,21 @@ enum EnumTabModel { CASE_FRSKY(e_Telemetry) }; -void menuModelSelect(evt_t event); -void menuModelSetup(evt_t event); -void menuModelHeli(evt_t event); -void menuModelFlightModesAll(evt_t event); -void menuModelExposAll(evt_t event); -void menuModelMixAll(evt_t event); -void menuModelLimits(evt_t event); -void menuModelCurvesAll(evt_t event); -void menuModelCurveOne(evt_t event); -void menuModelGVars(evt_t event); -void menuModelLogicalSwitches(evt_t event); -void menuModelCustomFunctions(evt_t event); -void menuModelCustomScripts(evt_t event); -void menuModelTelemetry(evt_t event); -void menuModelExpoOne(evt_t event); +bool menuModelSelect(evt_t event); +bool menuModelSetup(evt_t event); +bool menuModelHeli(evt_t event); +bool menuModelFlightModesAll(evt_t event); +bool menuModelExposAll(evt_t event); +bool menuModelMixAll(evt_t event); +bool menuModelLimits(evt_t event); +bool menuModelCurvesAll(evt_t event); +bool menuModelCurveOne(evt_t event); +bool menuModelGVars(evt_t event); +bool menuModelLogicalSwitches(evt_t event); +bool menuModelCustomFunctions(evt_t event); +bool menuModelCustomScripts(evt_t event); +bool menuModelTelemetry(evt_t event); +bool menuModelExpoOne(evt_t event); extern uint8_t s_curveChan; @@ -80,13 +80,13 @@ void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t a uint8_t editDelay(const coord_t x, const coord_t y, const evt_t event, const uint8_t attr, const pm_char *str, uint8_t delay) { lcdDrawText(x+MENUS_MARGIN_LEFT, y, str, TEXT_COLOR); - lcd_outdezAtt(x+MIXES_2ND_COLUMN, y, (10/DELAY_STEP)*delay, attr|PREC1|LEFT); + lcdDrawNumber(x+MIXES_2ND_COLUMN, y, (10/DELAY_STEP)*delay, attr|PREC1|LEFT); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, delay, DELAY_MAX); return delay; } #define EDIT_DELAY(x, y, event, attr, str, delay) editDelay(x, y, event, attr, str, delay) -const MenuFuncP_PROGMEM menuTabModel[] PROGMEM = { +const MenuFuncP menuTabModel[] = { // menuModelSelect, menuModelSetup, CASE_HELI(menuModelHeli) diff --git a/radio/src/gui/Horus/menu_model_curves.cpp b/radio/src/gui/Horus/menu_model_curves.cpp index b89ac5085..c0c550e22 100644 --- a/radio/src/gui/Horus/menu_model_curves.cpp +++ b/radio/src/gui/Horus/menu_model_curves.cpp @@ -97,7 +97,7 @@ bool moveCurve(uint8_t index, int8_t shift) void displayPresetChoice(evt_t event) { displayWarning(event); - lcd_outdezAtt(WARNING_LINE_X, WARNING_INFOLINE_Y-10, 45*s_warning_input_value/4, LEFT|INVERS, "@"); + lcdDrawNumber(WARNING_LINE_X, WARNING_INFOLINE_Y-10, 45*s_warning_input_value/4, LEFT|INVERS, 0, NULL, "@"); if (s_warning_result) { s_warning_result = 0; @@ -135,7 +135,7 @@ void onCurveOneMenu(const char *result) } } -void menuModelCurveOne(evt_t event) +bool menuModelCurveOne(evt_t event) { static uint8_t pointsOfs = 0; CurveInfo & crv = g_model.curves[s_curveChan]; @@ -143,7 +143,7 @@ void menuModelCurveOne(evt_t event) SIMPLE_SUBMENU(STR_MENUCURVE, 4 + 5+crv.points + (crv.type==CURVE_TYPE_CUSTOM ? 5+crv.points-2 : 0), 0); - lcd_outdezAtt(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, s_curveChan+1, LEFT|HEADER_COLOR); + lcdDrawNumber(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, s_curveChan+1, LEFT|HEADER_COLOR); lcd_putsLeft(MENU_CONTENT_TOP, STR_NAME); editName(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + FH, g_model.curveNames[s_curveChan], sizeof(g_model.curveNames[s_curveChan]), event, m_posVert==0); @@ -167,7 +167,7 @@ void menuModelCurveOne(evt_t event) attr = (m_posVert==2 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); lcd_putsLeft(MENU_CONTENT_TOP + 4*FH, STR_COUNT); - lcd_outdezAtt(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 5*FH, 5+crv.points, LEFT|attr, STR_PTS); + lcdDrawNumber(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 5*FH, 5+crv.points, LEFT|attr, 0, NULL, STR_PTS); if (attr) { int count = checkIncDecModel(event, crv.points, -3, 12); // 2pts - 17pts if (checkIncDec_Ret) { @@ -233,9 +233,9 @@ void menuModelCurveOne(evt_t event) if (i>=pointsOfs && i0 && i<5+crv.points-1) x = points[5+crv.points+i-1]; - lcd_outdezAtt(120, posY, i+1, LEFT|TEXT_DISABLE_COLOR); - lcd_outdezAtt(150, posY, x, LEFT|(selectionMode==1?attr:0)); - lcd_outdezAtt(200, posY, points[i], LEFT|(selectionMode==2?attr:0)); + lcdDrawNumber(120, posY, i+1, LEFT|TEXT_DISABLE_COLOR); + lcdDrawNumber(150, posY, x, LEFT|(selectionMode==1 ? attr : 0)); + lcdDrawNumber(200, posY, points[i], LEFT|(selectionMode==2 ? attr : 0)); posY += FH; } @@ -255,9 +255,9 @@ void menuModelCurveOne(evt_t event) } } - if (5+crv.points > NUM_BODY_LINES) { - drawScrollbar(250, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); - } + drawScrollbar(250, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, pointsOfs, 5+crv.points, NUM_BODY_LINES); + + return true; } void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t attr) @@ -291,7 +291,7 @@ void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t a } } -void menuModelCurvesAll(evt_t event) +bool menuModelCurvesAll(evt_t event) { SIMPLE_MENU(STR_MENUCURVES, menuTabModel, e_CurvesAll, MAX_CURVES, DEFAULT_SCROLLBAR_X); @@ -314,7 +314,7 @@ void menuModelCurvesAll(evt_t event) putsStrIdx(MENUS_MARGIN_LEFT, y, STR_CV, k+1, attr); editName(50, y, g_model.curveNames[k], sizeof(g_model.curveNames[k]), 0, 0); CurveInfo & crv = g_model.curves[k]; - lcd_outdezAtt(100, y, 5+crv.points, LEFT, STR_PTS); + lcdDrawNumber(100, y, 5+crv.points, LEFT, 0, NULL, STR_PTS); } } @@ -322,4 +322,6 @@ void menuModelCurvesAll(evt_t event) s_curveChan = sub; DrawCurve(23); } + + return true; } diff --git a/radio/src/gui/Horus/menu_model_custom_functions.cpp b/radio/src/gui/Horus/menu_model_custom_functions.cpp index 6f94ec96a..109ec5da6 100644 --- a/radio/src/gui/Horus/menu_model_custom_functions.cpp +++ b/radio/src/gui/Horus/menu_model_custom_functions.cpp @@ -111,7 +111,7 @@ void onCustomFunctionsMenu(const char *result) } } -void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext) +bool menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext) { uint32_t sub = m_posVert; @@ -217,7 +217,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc #if defined(OVERRIDE_CHANNEL_FUNCTION) else if (func == FUNC_OVERRIDE_CHANNEL) { val_min = -LIMIT_EXT_PERCENT; val_max = +LIMIT_EXT_PERCENT; - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + lcdDrawNumber(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); } #endif #if defined(DANGEROUS_MODULE_FUNCTIONS) @@ -237,7 +237,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc #if defined(HAPTIC) else if (func == FUNC_HAPTIC) { val_max = 3; - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + lcdDrawNumber(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); } #endif #if defined(SDCARD) @@ -281,7 +281,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc } else if (func == FUNC_LOGS) { if (val_displayed) { - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|PREC1|LEFT, "s"); + lcdDrawNumber(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|PREC1|LEFT, 0, NULL, "s"); } else { lcdDrawTextAtIndex(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, STR_MMMINV, 0, attr); @@ -301,7 +301,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc case FUNC_ADJUST_GVAR_CONSTANT: val_displayed = (int16_t)CFN_PARAM(cfn); val_min = -CFN_GVAR_CST_MAX; val_max = +CFN_GVAR_CST_MAX; - lcd_outdezAtt(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); + lcdDrawNumber(MODEL_CUSTOM_FUNC_3RD_COLUMN, y, val_displayed, attr|LEFT); break; case FUNC_ADJUST_GVAR_SOURCE: val_max = MIXSRC_LAST_CH; @@ -353,7 +353,7 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc lcdDrawText(MODEL_CUSTOM_FUNC_4TH_COLUMN-1, y, "!1x", attr); } else { - lcd_outdezAtt(MODEL_CUSTOM_FUNC_4TH_COLUMN+12, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr, "s"); + lcdDrawNumber(MODEL_CUSTOM_FUNC_4TH_COLUMN+12, y, CFN_PLAY_REPEAT(cfn)*CFN_PLAY_REPEAT_MUL, attr, 0, NULL, "s"); } } else if (attr) { @@ -363,9 +363,11 @@ void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunc } } } + + return true; } -void menuModelCustomFunctions(evt_t event) +bool menuModelCustomFunctions(evt_t event) { MENU(STR_MENUCUSTOMFUNC, menuTabModel, e_CustomFunctions, NUM_CFN, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|4/*repeated*/ }); return menuCustomFunctions(event, g_model.customFn, modelFunctionsContext); diff --git a/radio/src/gui/Horus/menu_model_flightmodes.cpp b/radio/src/gui/Horus/menu_model_flightmodes.cpp index 938c696df..a95a4dec6 100644 --- a/radio/src/gui/Horus/menu_model_flightmodes.cpp +++ b/radio/src/gui/Horus/menu_model_flightmodes.cpp @@ -60,7 +60,7 @@ bool isTrimModeAvailable(int mode) #define FLIGHT_MODES_FADEIN_COLUMN 400 #define FLIGHT_MODES_FADEOUT_COLUMN 450 -void menuModelFlightModesAll(evt_t event) +bool menuModelFlightModesAll(evt_t event) { MENU(STR_MENUFLIGHTPHASES, menuTabModel, e_FlightModesAll, MAX_FLIGHT_MODES+1, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, NAVIGATION_LINE_BY_LINE|ITEM_FLIGHT_MODES_LAST, 0}); @@ -106,7 +106,7 @@ void menuModelFlightModesAll(evt_t event) char s[32]; sprintf(s, "Check FM%d Trims", mixerCurrentFlightMode); lcd_putsCenter(y, s, attr); - return; + return true; } FlightModeData *p = flightModeAddress(k); @@ -124,7 +124,7 @@ void menuModelFlightModesAll(evt_t event) case ITEM_FLIGHT_MODES_SWITCH: if (active) CHECK_INCDEC_MODELSWITCH(event, p->swtch, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); if (k == 0) - lcd_puts(FLIGHT_MODES_SWITCH_COLUMN, y, "N/A"); + lcdDrawText(FLIGHT_MODES_SWITCH_COLUMN, y, "N/A"); else putsSwitches(FLIGHT_MODES_SWITCH_COLUMN, y, p->swtch, attr); break; @@ -145,15 +145,17 @@ void menuModelFlightModesAll(evt_t event) case ITEM_FLIGHT_MODES_FADE_IN: if (active) p->fadeIn = checkIncDec(event, p->fadeIn, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); - lcd_outdezAtt(FLIGHT_MODES_FADEIN_COLUMN, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); + lcdDrawNumber(FLIGHT_MODES_FADEIN_COLUMN, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); break; case ITEM_FLIGHT_MODES_FADE_OUT: if (active) p->fadeOut = checkIncDec(event, p->fadeOut, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); - lcd_outdezAtt(FLIGHT_MODES_FADEOUT_COLUMN, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); + lcdDrawNumber(FLIGHT_MODES_FADEOUT_COLUMN, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); break; } } } + + return true; } diff --git a/radio/src/gui/Horus/menu_model_gvars.cpp b/radio/src/gui/Horus/menu_model_gvars.cpp index f8f6ce39f..5afeeecb8 100644 --- a/radio/src/gui/Horus/menu_model_gvars.cpp +++ b/radio/src/gui/Horus/menu_model_gvars.cpp @@ -57,7 +57,7 @@ void onGVARSMenu(const char *result) #define GVARS_FM_COLUMN(p) (123 + (p)*24) -void menuModelGVars(evt_t event) +bool menuModelGVars(evt_t event) { MENU(STR_MENUGLOBALVARS, menuTabModel, e_GVars/* TODO, first2seconds ? CHECK_FLAG_NO_SCREEN_INDEX : 0*/, MAX_GVARS, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES, NAVIGATION_LINE_BY_LINE|MAX_FLIGHT_MODES}); @@ -66,7 +66,7 @@ void menuModelGVars(evt_t event) for (int l=0; l= 1000) - lcd_outdezAtt(x, y+1, v, TINSIZE|attr); + lcdDrawNumber(x, y+1, v, TINSIZE|attr); else - lcd_outdezAtt(x, y, v, attr); + lcdDrawNumber(x, y, v, attr); vmin = -GVAR_MAX; vmax = GVAR_MAX; } if (attr) { @@ -120,4 +120,6 @@ void menuModelGVars(evt_t event) MENU_ADD_ITEM(STR_CLEAR); menuHandler = onGVARSMenu; } + + return true; } diff --git a/radio/src/gui/Horus/menu_model_heli.cpp b/radio/src/gui/Horus/menu_model_heli.cpp index 28f861f69..bf84518e0 100644 --- a/radio/src/gui/Horus/menu_model_heli.cpp +++ b/radio/src/gui/Horus/menu_model_heli.cpp @@ -49,7 +49,7 @@ enum menuModelHeliItems { #define MODEL_HELI_2ND_COLUMN (230) -void menuModelHeli(evt_t event) +bool menuModelHeli(evt_t event) { SIMPLE_MENU(STR_MENUHELISETUP, menuTabModel, e_Heli, ITEM_HELI_MAX, DEFAULT_SCROLLBAR_X); @@ -68,7 +68,7 @@ void menuModelHeli(evt_t event) case ITEM_HELI_SWASHRING: lcd_putsLeft(y, STR_SWASHRING); - lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.value, LEFT|attr); + lcdDrawNumber(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.value, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.swashR.value, 100); break; @@ -79,8 +79,8 @@ void menuModelHeli(evt_t event) break; case ITEM_HELI_ELE_WEIGHT: - lcd_puts(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); - lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.elevatorWeight, LEFT|attr); + lcdDrawText(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcdDrawNumber(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.elevatorWeight, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.elevatorWeight, -100, 100); break; @@ -91,8 +91,8 @@ void menuModelHeli(evt_t event) break; case ITEM_HELI_AIL_WEIGHT: - lcd_puts(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); - lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.aileronWeight, LEFT|attr); + lcdDrawText(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcdDrawNumber(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.aileronWeight, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.aileronWeight, -100, 100); break; @@ -103,11 +103,13 @@ void menuModelHeli(evt_t event) break; case ITEM_HELI_COL_WEIGHT: - lcd_puts(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); - lcd_outdezAtt(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.collectiveWeight, LEFT|attr); + lcdDrawText(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, STR_WEIGHT); + lcdDrawNumber(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.collectiveWeight, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR(event, g_model.swashR.collectiveWeight, -100, 100); break; } } + + return true; } diff --git a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp index 4695f83fe..92cd5596f 100644 --- a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp @@ -338,7 +338,7 @@ enum ExposFields { #define CURVE_ROWS 1 -void menuModelExpoOne(evt_t event) +bool menuModelExpoOne(evt_t event) { ExpoData *ed = expoAddress(s_currIdx); @@ -359,12 +359,12 @@ void menuModelExpoOne(evt_t event) if (ed->scale > 0) x = (x * 1024) / convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale); } else { - lcd_outdezAtt(CURVE_CENTER_X+CURVE_SIDE_WIDTH, CURVE_CENTER_Y+CURVE_SIDE_WIDTH-FH+6, calcRESXto1000(x), PREC1); + lcdDrawNumber(CURVE_CENTER_X+CURVE_SIDE_WIDTH, CURVE_CENTER_Y+CURVE_SIDE_WIDTH-FH+6, calcRESXto1000(x), PREC1); } x = limit(-1024, x, 1024); int y = limit(-1024, expoFn(x), 1024); - lcd_outdezAtt(CURVE_CENTER_X-CURVE_SIDE_WIDTH, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, calcRESXto1000(y), LEFT|PREC1); + lcdDrawNumber(CURVE_CENTER_X-CURVE_SIDE_WIDTH, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, calcRESXto1000(y), LEFT|PREC1); x = divRound(x*CURVE_SIDE_WIDTH, RESX); y = getYCoord(expoFn, x); @@ -444,6 +444,8 @@ void menuModelExpoOne(evt_t event) } y += FH; } + + return true; } enum MixFields { @@ -481,8 +483,8 @@ void drawOffsetBar(uint8_t x, uint8_t y, MixData * md) int barMin = offset - weight; int barMax = offset + weight; if (y > 15) { - lcd_outdezAtt(x-((barMin >= 0) ? 2 : 3), y-6, barMin, TINSIZE|LEFT); - lcd_outdezAtt(x+GAUGE_WIDTH+1, y-6, barMax, TINSIZE); + lcdDrawNumber(x-((barMin >= 0) ? 2 : 3), y-6, barMin, TINSIZE|LEFT); + lcdDrawNumber(x+GAUGE_WIDTH+1, y-6, barMax, TINSIZE); } if (barMin < -101) barMin = -101; @@ -516,7 +518,7 @@ void drawOffsetBar(uint8_t x, uint8_t y, MixData * md) #undef GAUGE_WIDTH #undef GAUGE_HEIGHT -void menuModelMixOne(evt_t event) +bool menuModelMixOne(evt_t event) { MixData *md2 = mixAddress(s_currIdx) ; @@ -594,7 +596,7 @@ void menuModelMixOne(evt_t event) case MIX_FIELD_WARNING: lcd_putsColumnLeft(x+MIXES_2ND_COLUMN, y, STR_MIXWARNING); if (md2->mixWarn) - lcd_outdezAtt(x+MIXES_2ND_COLUMN, y, md2->mixWarn, attr|LEFT); + lcdDrawNumber(x+MIXES_2ND_COLUMN, y, md2->mixWarn, attr|LEFT); else lcdDrawText(x+MIXES_2ND_COLUMN, y, STR_OFF, attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, md2->mixWarn, 3); @@ -617,6 +619,8 @@ void menuModelMixOne(evt_t event) break; } } + + return true; } static uint8_t s_copySrcIdx; @@ -709,7 +713,7 @@ void displayExpoInfos(coord_t y, ExpoData *ed) putsSwitches(EXPO_LINE_SWITCH_POS, y, ed->swtch); } if (ed->mode != 3) { - lcd_puts(EXPO_LINE_SIDE_POS, y, ed->mode == 2 ? "\176" : "\177"); + lcdDrawText(EXPO_LINE_SIDE_POS, y, ed->mode == 2 ? "\176" : "\177"); } } @@ -725,7 +729,7 @@ void displayExpoLine(coord_t y, ExpoData *ed) } } -void menuModelExpoMix(uint8_t expo, evt_t event) +bool menuModelExpoMix(uint8_t expo, evt_t event) { int sub = m_posVert; @@ -919,7 +923,7 @@ void menuModelExpoMix(uint8_t expo, evt_t event) cs[0] = 'S'; if (md->delayUp || md->delayDown) cs[0] = (cs[0] =='S' ? '*' : 'D'); - lcd_puts(MIX_LINE_DELAY_POS, y, cs); + lcdDrawText(MIX_LINE_DELAY_POS, y, cs); } if (s_copyMode) { if ((s_copyMode==COPY_MODE || s_copyTgtOfs == 0) && s_copySrcCh == ch && i == (s_copySrcIdx + (s_copyTgtOfs<0))) { @@ -967,14 +971,16 @@ void menuModelExpoMix(uint8_t expo, evt_t event) } if (sub >= linesCount-1) m_posVert = linesCount-1; + + return true; } -void menuModelExposAll(evt_t event) +bool menuModelExposAll(evt_t event) { return menuModelExpoMix(1, event); } -void menuModelMixAll(evt_t event) +bool menuModelMixAll(evt_t event) { return menuModelExpoMix(0, event); } diff --git a/radio/src/gui/Horus/menu_model_limits.cpp b/radio/src/gui/Horus/menu_model_limits.cpp index 27c3f46c1..9bfbd447b 100644 --- a/radio/src/gui/Horus/menu_model_limits.cpp +++ b/radio/src/gui/Horus/menu_model_limits.cpp @@ -117,7 +117,7 @@ void onLimitsMenu(const char *result) } } -void menuModelLimits(evt_t event) +bool menuModelLimits(evt_t event) { MENU(STR_MENULIMITS, menuTabModel, e_Limits, NUM_CHNOUT+1, DEFAULT_SCROLLBAR_X, { NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, NAVIGATION_LINE_BY_LINE|ITEM_LIMITS_MAXROW, @@ -130,9 +130,9 @@ void menuModelLimits(evt_t event) if (sub < NUM_CHNOUT) { #if defined(PPM_CENTER_ADJUSTABLE) || defined(PPM_UNIT_US) - lcd_outdezAtt(100, MENU_FOOTER_TOP, PPM_CH_CENTER(sub)+channelOutputs[sub]/2, HEADER_COLOR, STR_US); + lcdDrawNumber(100, MENU_FOOTER_TOP, PPM_CH_CENTER(sub)+channelOutputs[sub]/2, HEADER_COLOR, 0, NULL, STR_US); #else - lcd_outdezAtt(100, MENU_FOOTER_TOP, calcRESXto1000(channelOutputs[sub]), HEADER_COLOR|PREC1); + lcdDrawNumber(100, MENU_FOOTER_TOP, calcRESXto1000(channelOutputs[sub]), HEADER_COLOR|PREC1); #endif } @@ -164,7 +164,7 @@ void menuModelLimits(evt_t event) moveTrimsToOffsets(); // if highlighted and menu pressed - move trims to offsets } } - return; + return true; } LimitData *ld = limitAddress(k); @@ -173,7 +173,7 @@ void menuModelLimits(evt_t event) char swVal[2] = "-"; // '-', '<', '>' if ((channelOutputs[k] - v) > 50) swVal[0] = (ld->revert ? 127 : 126); // Switch to raw inputs? - remove trim! if ((channelOutputs[k] - v) < -50) swVal[0] = (ld->revert ? 126 : 127); - lcd_puts(LIMITS_DIRECTION_POS, y, swVal); + lcdDrawText(LIMITS_DIRECTION_POS, y, swVal); int limit = (g_model.extendedLimits ? LIMIT_EXT_MAX : 1000); @@ -203,9 +203,9 @@ void menuModelLimits(evt_t event) } #if defined(PPM_UNIT_US) - lcd_outdezAtt(LIMITS_OFFSET_POS, y, ((int32_t)ld->offset*128) / 25, attr|PREC1); + lcdDrawNumber(LIMITS_OFFSET_POS, y, ((int32_t)ld->offset*128) / 25, attr|PREC1); #else - lcd_outdezAtt(LIMITS_OFFSET_POS, y, ld->offset, attr|PREC1); + lcdDrawNumber(LIMITS_OFFSET_POS, y, ld->offset, attr|PREC1); #endif if (active) { ld->offset = checkIncDec(event, ld->offset, -1000, 1000, EE_MODEL, NULL, stops1000); @@ -221,7 +221,7 @@ void menuModelLimits(evt_t event) ld->min = GVAR_MENU_ITEM(LIMITS_MIN_POS, y, ld->min, -LIMIT_EXT_MAX, LIMIT_EXT_MAX, attr|PREC1, 0, event); break; } - lcd_outdezAtt(LIMITS_MIN_POS, y, MIN_MAX_DISPLAY(ld->min-LIMITS_MIN_MAX_OFFSET), attr|PREC1); + lcdDrawNumber(LIMITS_MIN_POS, y, MIN_MAX_DISPLAY(ld->min-LIMITS_MIN_MAX_OFFSET), attr|PREC1); if (active) ld->min = LIMITS_MIN_MAX_OFFSET + checkIncDec(event, ld->min-LIMITS_MIN_MAX_OFFSET, -limit, 0, EE_MODEL, NULL, stops1000); break; @@ -230,7 +230,7 @@ void menuModelLimits(evt_t event) ld->max = GVAR_MENU_ITEM(LIMITS_MAX_POS, y, ld->max, -LIMIT_EXT_MAX, LIMIT_EXT_MAX, attr|PREC1, 0, event); break; } - lcd_outdezAtt(LIMITS_MAX_POS, y, MIN_MAX_DISPLAY(ld->max+LIMITS_MIN_MAX_OFFSET), attr|PREC1); + lcdDrawNumber(LIMITS_MAX_POS, y, MIN_MAX_DISPLAY(ld->max+LIMITS_MIN_MAX_OFFSET), attr|PREC1); if (active) ld->max = -LIMITS_MIN_MAX_OFFSET + checkIncDec(event, ld->max+LIMITS_MIN_MAX_OFFSET, 0, +limit, EE_MODEL, NULL, stops1000); break; @@ -269,7 +269,7 @@ void menuModelLimits(evt_t event) #if defined(PPM_CENTER_ADJUSTABLE) case ITEM_LIMITS_PPM_CENTER: - lcd_outdezAtt(LIMITS_PPM_CENTER_POS, y, PPM_CENTER+ld->ppmCenter, attr); + lcdDrawNumber(LIMITS_PPM_CENTER_POS, y, PPM_CENTER+ld->ppmCenter, attr); if (active) { CHECK_INCDEC_MODELVAR(event, ld->ppmCenter, -PPM_CENTER_MAX, +PPM_CENTER_MAX); } @@ -287,4 +287,6 @@ void menuModelLimits(evt_t event) } } } + + return true; } diff --git a/radio/src/gui/Horus/menu_model_logical_switches.cpp b/radio/src/gui/Horus/menu_model_logical_switches.cpp index 17179a60c..cad4a33b2 100644 --- a/radio/src/gui/Horus/menu_model_logical_switches.cpp +++ b/radio/src/gui/Horus/menu_model_logical_switches.cpp @@ -69,7 +69,7 @@ void putsEdgeDelayParam(coord_t x, coord_t y, LogicalSwitchData *cs, uint8_t lat char sright[10]; sprintf(sright, "%d.%d", right.quot, right.rem); sprintf(s, "[%s:%s]", sleft, sright); - lcd_puts(x-4, y, s); + lcdDrawText(x-4, y, s); /* if (cs->v3 < 0) lcdDrawText(lcdLastPos+3, y, "<<", rattr); else if (cs->v3 == 0) @@ -95,7 +95,7 @@ void onLogicalSwitchesMenu(const char *result) } } -void menuModelLogicalSwitches(evt_t event) +bool menuModelLogicalSwitches(evt_t event) { INCDEC_DECLARE_VARS(EE_MODEL); @@ -173,8 +173,8 @@ void menuModelLogicalSwitches(evt_t event) INCDEC_ENABLE_CHECK(isSourceAvailable); } else if (cstate == LS_FAMILY_TIMER) { - lcd_outdezAtt(CSW_2ND_COLUMN, y, lswTimerValue(cs->v1), LEFT|PREC1|attr1); - lcd_outdezAtt(CSW_3RD_COLUMN, y, lswTimerValue(cs->v2), LEFT|PREC1|attr2); + lcdDrawNumber(CSW_2ND_COLUMN, y, lswTimerValue(cs->v1), LEFT|PREC1|attr1); + lcdDrawNumber(CSW_3RD_COLUMN, y, lswTimerValue(cs->v2), LEFT|PREC1|attr2); v1_min = v2_min = -128; v1_max = v2_max = 122; INCDEC_SET_FLAG(EE_MODEL); @@ -201,19 +201,19 @@ void menuModelLogicalSwitches(evt_t event) // CSW duration if (cs->duration > 0) - lcd_outdezAtt(CSW_5TH_COLUMN, y, cs->duration, (horz==LS_FIELD_DURATION ? attr : 0)|PREC1|LEFT); + lcdDrawNumber(CSW_5TH_COLUMN, y, cs->duration, (horz==LS_FIELD_DURATION ? attr : 0)|PREC1|LEFT); else lcdDrawTextAtIndex(CSW_5TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DURATION ? attr : 0); // CSW delay if (cstate == LS_FAMILY_EDGE) { - lcd_puts(CSW_6TH_COLUMN, y, STR_NA); + lcdDrawText(CSW_6TH_COLUMN, y, STR_NA); if (attr && horz == LS_FIELD_DELAY) { REPEAT_LAST_CURSOR_MOVE(); } } else if (cs->delay > 0) { - lcd_outdezAtt(CSW_6TH_COLUMN, y, cs->delay, (horz==LS_FIELD_DELAY ? attr : 0)|PREC1|LEFT); + lcdDrawNumber(CSW_6TH_COLUMN, y, cs->delay, (horz==LS_FIELD_DELAY ? attr : 0)|PREC1|LEFT); } else { lcdDrawTextAtIndex(CSW_6TH_COLUMN, y, STR_MMMINV, 0, horz==LS_FIELD_DELAY ? attr : 0); @@ -272,4 +272,6 @@ void menuModelLogicalSwitches(evt_t event) } } } + + return true; } diff --git a/radio/src/gui/Horus/menu_model_select.cpp b/radio/src/gui/Horus/menu_model_select.cpp index 1b03c8536..0691ac5de 100644 --- a/radio/src/gui/Horus/menu_model_select.cpp +++ b/radio/src/gui/Horus/menu_model_select.cpp @@ -35,26 +35,147 @@ #include "../../opentx.h" -#define CATEGORIES_WIDTH 140 +#define CATEGORIES_WIDTH 140 -void drawModel(coord_t x, coord_t y, const char * name) +#define MODELCELL_WIDTH 153 +#define MODELCELL_HEIGHT 61 + +enum ModelSelectMode { + MODE_SELECT_CATEGORY, + MODE_SELECT_MODEL, +}; + +uint8_t selectMode; +uint8_t currentCategory; +char selectedFilename[LEN_MODEL_FILENAME+1]; + +void drawCategory(coord_t y, const char * name, bool selected) { - lcdDrawSolidRect(x, y, 153, 61, LINE_COLOR); - lcdDrawText(x+5, y+2, name, TEXT_COLOR); - lcdDrawSolidHorizontalLine(x+5, y+19, 143, LINE_COLOR); - lcdDrawBitmapPattern(x+5, y+23, LBM_LIBRARY_SLOT, TEXT_COLOR); + if (selected) { + lcdDrawSolidFilledRect(0, y-INVERT_VERT_MARGIN, CATEGORIES_WIDTH, INVERT_LINE_HEIGHT, HEADER_BGCOLOR); + lcdDrawBitmapPattern(CATEGORIES_WIDTH-12, y, LBM_LIBRARY_CURSOR, MENU_TITLE_COLOR); + if (selectMode == MODE_SELECT_CATEGORY) { + drawShadow(0, y-INVERT_VERT_MARGIN, CATEGORIES_WIDTH, INVERT_LINE_HEIGHT); + } + } + lcdDrawText(MENUS_MARGIN_LEFT, y, name, MENU_TITLE_COLOR); } -void menuModelSelect(evt_t event) +void drawModel(coord_t x, coord_t y, const char * name, bool selected) { + ModelHeader header; + const char * error = readModel(name, (uint8_t *)&header, sizeof(header)); + if (error) { + lcdDrawText(x+5, y+2, "(Invalid Model)", TEXT_COLOR); + lcdDrawBitmapPattern(x+5, y+23, LBM_LIBRARY_SLOT, TEXT_COLOR); + } + else { + lcdDrawTextWithLen(x+5, y+2, header.name, LEN_MODEL_NAME, ZCHAR|TEXT_COLOR); + putsTimer(x+104, y+41, 0, TEXT_COLOR|LEFT); + for (int i=0; i<4; i++) { + lcdDrawBitmapPattern(x+104+i*11, y+25, LBM_SCORE0, TITLE_BGCOLOR); + } + loadModelBitmap(header.bitmap, modelBitmap); + lcdDrawBitmap(x+5, y+24, modelBitmap, 0, 0, getBitmapScale(modelBitmap, 64, 32)); + } + lcdDrawSolidHorizontalLine(x+5, y+19, 143, LINE_COLOR); + if (selectMode == MODE_SELECT_MODEL && selected) { + lcdDrawSolidRect(x, y, MODELCELL_WIDTH, MODELCELL_HEIGHT, TITLE_BGCOLOR); + drawShadow(x, y, MODELCELL_WIDTH, MODELCELL_HEIGHT); + } +} + +void onModelSelectMenu(const char * result) +{ + if (result == STR_SELECT_MODEL) { + memcpy(g_eeGeneral.currModelFilename, selectedFilename, LEN_MODEL_FILENAME); + storageDirty(EE_GENERAL); + loadModel(g_eeGeneral.currModelFilename); + chainMenu(menuMainView); + } + else if (result == STR_DELETE_MODEL) { + POPUP_CONFIRMATION(STR_DELETEMODEL); + SET_WARNING_INFO(selectedFilename, LEN_MODEL_FILENAME, 0); + } + else if (result == STR_DUPLICATE_MODEL) { + char duplicatedFilename[LEN_MODEL_FILENAME+1]; + memcpy(duplicatedFilename, selectedFilename, sizeof(duplicatedFilename)); + if (findNextFileIndex(duplicatedFilename, MODELS_PATH)) { + sdCopyFile(selectedFilename, MODELS_PATH, duplicatedFilename, MODELS_PATH); + storageInsertModel(duplicatedFilename, currentCategory, -1); + } + else { + POPUP_WARNING("Invalid Filename"); + } + } +} + +#define MODEL_INDEX() (m_posVert*2+m_posHorz) + +bool menuModelSelect(evt_t event) +{ + if (s_warning_result) { + s_warning_result = 0; + int modelIndex = MODEL_INDEX(); + storageRemoveModel(currentCategory, modelIndex); + s_copyMode = 0; + event = EVT_REFRESH; + if (modelIndex > 0) { + modelIndex--; + m_posVert = modelIndex / 2; + m_posHorz = modelIndex & 1; + } + } + switch(event) { case 0: // no need to refresh the screen - return; + return false; + + case EVT_ENTRY: + selectMode = MODE_SELECT_CATEGORY; + currentCategory = m_posVert = 0; + break; case EVT_KEY_FIRST(KEY_EXIT): - chainMenu(menuMainView); - return; + switch (selectMode) { + case MODE_SELECT_CATEGORY: + chainMenu(menuMainView); + return false; + case MODE_SELECT_MODEL: + selectMode = MODE_SELECT_CATEGORY; + m_posVert = currentCategory; + break; + } + break; + + case EVT_KEY_BREAK(KEY_ENTER): + case EVT_KEY_BREAK(KEY_RIGHT): + switch (selectMode) { + case MODE_SELECT_CATEGORY: + selectMode = MODE_SELECT_MODEL; + m_posVert = 0; + break; + } + break; + + case EVT_KEY_LONG(KEY_ENTER): + killEvents(event); + if (selectMode == MODE_SELECT_MODEL) { + ModelHeader header; + const char * error = readModel(selectedFilename, (uint8_t *)&header, sizeof(header)); + if (!error) { + MENU_ADD_ITEM(STR_SELECT_MODEL); + MENU_ADD_ITEM(STR_DUPLICATE_MODEL); + } + // MENU_ADD_SD_ITEM(STR_BACKUP_MODEL); + // MENU_ADD_ITEM(STR_MOVE_MODEL); + MENU_ADD_ITEM(STR_DELETE_MODEL); + // MENU_ADD_ITEM(STR_CREATE_MODEL); + // MENU_ADD_ITEM(STR_RESTORE_MODEL); + menuHandler = onModelSelectMenu; + } + break; } // Header @@ -63,51 +184,71 @@ void menuModelSelect(evt_t event) lcdDrawBitmapPattern(5, 7, LBM_LIBRARY_ICON, MENU_TITLE_COLOR); drawTopmenuDatetime(); - // Categories + // Body lcdDrawSolidFilledRect(0, MENU_HEADER_HEIGHT, CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TITLE_BGCOLOR); + lcdDrawSolidFilledRect(CATEGORIES_WIDTH, MENU_HEADER_HEIGHT, LCD_W-CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TEXT_BGCOLOR); + // Footer + lcdDrawSolidFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); + + // Categories StorageModelsList storage; const char * error = storageOpenModelsList(&storage); if (!error) { bool result = true; coord_t y = MENU_HEADER_HEIGHT+10; - while (y < LCD_H) { - char line[256]; - result = storageReadNextCategory(&storage, line, sizeof(line)-1); + int index = 0; + while (1) { + char line[LEN_MODEL_FILENAME+1]; + result = storageReadNextCategory(&storage, line, LEN_MODEL_FILENAME); if (!result) break; - lcdDrawText(MENUS_MARGIN_LEFT, y, line, MENU_TITLE_COLOR); + if (y < LCD_H) { + drawCategory(y, line, currentCategory==index); + } y += FH; + index++; + } + if (selectMode == MODE_SELECT_CATEGORY) { + if (navigate(event, index, 9)) { + putEvent(EVT_REFRESH); + currentCategory = m_posVert; + } } } // Models - lcdDrawSolidFilledRect(CATEGORIES_WIDTH, MENU_HEADER_HEIGHT, LCD_W-CATEGORIES_WIDTH, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TEXT_BGCOLOR); if (!error) { - bool result = storageSeekCategory(&storage, 0); + bool result = storageSeekCategory(&storage, currentCategory); coord_t y = MENU_HEADER_HEIGHT+7; - unsigned int i = 0; + int index = 0; while (result) { - char line[256]; - result = storageReadNextModel(&storage, line, sizeof(line)-1); + char line[LEN_MODEL_FILENAME+1]; + result = storageReadNextModel(&storage, line, LEN_MODEL_FILENAME); if (!result) break; - if (y < LCD_H) { - coord_t x; - if (i & 1) { - drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+162, y, line); + if (index >= s_pgOfs*2 && index < (s_pgOfs+3)*2) { + bool selected = (m_posVert*2+m_posHorz==index); + if (index & 1) { + drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+162, y, line, selected); y += 66; } else { - drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+1, y, line); + drawModel(CATEGORIES_WIDTH+MENUS_MARGIN_LEFT+1, y, line, selected); + } + if (selected) { + memcpy(selectedFilename, line, sizeof(selectedFilename)); } } - i++; + index++; } + if (selectMode == MODE_SELECT_MODEL) { + if (navigate(event, index, 3, 2)) { + putEvent(EVT_REFRESH); + } + } + drawScrollbar(DEFAULT_SCROLLBAR_X, MENU_HEADER_HEIGHT+7, MENU_FOOTER_TOP-MENU_HEADER_HEIGHT-15, s_pgOfs, (index+1)/2, 3); } - drawScrollbar(DEFAULT_SCROLLBAR_X, MENU_HEADER_HEIGHT+7, MENU_FOOTER_TOP-MENU_HEADER_HEIGHT-15, 0, 4, 2); - - // Footer - lcdDrawSolidFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); + return true; } diff --git a/radio/src/gui/Horus/menu_model_setup.cpp b/radio/src/gui/Horus/menu_model_setup.cpp index e96191e6c..a62b614b7 100644 --- a/radio/src/gui/Horus/menu_model_setup.cpp +++ b/radio/src/gui/Horus/menu_model_setup.cpp @@ -36,7 +36,7 @@ #include "../../opentx.h" uint8_t g_moduleIdx; -void menuModelFailsafe(evt_t event); +bool menuModelFailsafe(evt_t event); enum menuModelSetupItems { ITEM_MODEL_NAME, @@ -113,6 +113,7 @@ void onModelSetupBitmapMenu(const char *result) else { // The user choosed a bmp file in the list copySelection(g_model.header.bitmap, result, sizeof(g_model.header.bitmap)); + loadModelBitmap(g_model.header.bitmap, modelBitmap); storageDirty(EE_MODEL); } } @@ -170,7 +171,7 @@ int getSwitchWarningsCount() return count; } -void menuModelSetup(evt_t event) +bool menuModelSetup(evt_t event) { horzpos_t l_posHorz = m_posHorz; #define IF_EXTERNAL_MODULE_ON(x) (g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_NONE ? HIDDEN_ROW : (uint8_t)(x)) @@ -550,7 +551,7 @@ void menuModelSetup(evt_t event) lcd_putsLeft(y, STR_CHANNELRANGE); if ((int8_t)PORT_CHANNELS_ROWS(moduleIdx) >= 0) { putsStrIdx(MODEL_SETUP_2ND_COLUMN, y, STR_CH, moduleData.channelsStart+1, m_posHorz==0 ? attr : 0); - lcd_puts(MODEL_SETUP_2ND_COLUMN+30, y, "-"); + lcdDrawText(MODEL_SETUP_2ND_COLUMN+30, y, "-"); putsStrIdx(MODEL_SETUP_2ND_COLUMN+40, y, STR_CH, moduleData.channelsStart+NUM_CHANNELS(moduleIdx), m_posHorz==1 ? attr : 0); if (attr && s_editMode>0) { switch (m_posHorz) { @@ -577,8 +578,8 @@ void menuModelSetup(evt_t event) ModuleData & moduleData = g_model.moduleData[moduleIdx]; if (IS_MODULE_PPM(moduleIdx)) { lcd_putsLeft(y, STR_PPMFRAME); - lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN, y, (int16_t)moduleData.ppmFrameLength*5 + 225, (m_posHorz<=0 ? attr : 0) | PREC1|LEFT, STR_MS); - lcd_outdezAtt(MODEL_SETUP_2ND_COLUMN+70, y, (moduleData.ppmDelay*50)+300, (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0, "us"); + lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, (int16_t)moduleData.ppmFrameLength*5 + 225, (m_posHorz<=0 ? attr : 0) | PREC1|LEFT, 0, NULL, STR_MS); + lcdDrawNumber(MODEL_SETUP_2ND_COLUMN+70, y, (moduleData.ppmDelay*50)+300, (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0, 0, NULL, "us"); lcdDrawText(MODEL_SETUP_2ND_COLUMN+90, y, moduleData.ppmPulsePol ? "+" : "-", (CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0); if (attr && s_editMode>0) { @@ -607,7 +608,7 @@ void menuModelSetup(evt_t event) lcd_putsLeft(y, STR_RXNUM); } if (IS_MODULE_XJT(moduleIdx) || IS_MODULE_DSM2(moduleIdx)) { - if (xOffsetBind) lcd_outdezNAtt(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0|LEFT, 2); + if (xOffsetBind) lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0|LEFT, 2); if (attr && l_posHorz==0 && s_editMode>0) { CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], IS_MODULE_DSM2(moduleIdx) ? 20 : 63); } @@ -663,11 +664,13 @@ void menuModelSetup(evt_t event) if (IS_RANGECHECK_ENABLE()) { displayPopup("RSSI :"); - lcd_outdezAtt(WARNING_LINE_X, WARNING_INFOLINE_Y, TELEMETRY_RSSI(), DBLSIZE|LEFT); + lcdDrawNumber(WARNING_LINE_X, WARNING_INFOLINE_Y, TELEMETRY_RSSI(), DBLSIZE|LEFT); } + + return true; } -void menuModelFailsafe(evt_t event) +bool menuModelFailsafe(evt_t event) { static bool longNames = false; bool newLongNames = false; @@ -731,13 +734,13 @@ void menuModelFailsafe(evt_t event) } #if defined(PPM_UNIT_US) uint8_t wbar = (longNames ? SLIDER_W-10 : SLIDER_W); - lcd_outdezAtt(x+COL_W-4-wbar-ofs, y, PPM_CH_CENTER(ch)+val/2, flags); + lcdDrawNumber(x+COL_W-4-wbar-ofs, y, PPM_CH_CENTER(ch)+val/2, flags); #elif defined(PPM_UNIT_PERCENT_PREC1) uint8_t wbar = (longNames ? SLIDER_W-16 : SLIDER_W-6); - lcd_outdezAtt(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val), PREC1|flags); + lcdDrawNumber(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val), PREC1|flags); #else uint8_t wbar = (longNames ? SLIDER_W-10 : SLIDER_W); - lcd_outdezAtt(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val)/10, flags); + lcdDrawNumber(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val)/10, flags); #endif // Gauge @@ -751,4 +754,6 @@ void menuModelFailsafe(evt_t event) } longNames = newLongNames; + + return true; } diff --git a/radio/src/gui/Horus/menu_model_telemetry.cpp b/radio/src/gui/Horus/menu_model_telemetry.cpp index 6a4818c91..daf2222f3 100644 --- a/radio/src/gui/Horus/menu_model_telemetry.cpp +++ b/radio/src/gui/Horus/menu_model_telemetry.cpp @@ -208,13 +208,13 @@ bool isSensorAvailable(int sensor) #define SENSOR_FILTER_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) #define SENSOR_PERSISTENT_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) -void menuModelSensor(evt_t event) +bool menuModelSensor(evt_t event) { TelemetrySensor *sensor = &g_model.telemetrySensors[s_currIdx]; SUBMENU(STR_MENUSENSOR, SENSOR_FIELD_MAX, DEFAULT_SCROLLBAR_X, { 0, 0, sensor->type == TELEM_TYPE_CALCULATED ? (uint8_t)0 : (uint8_t)1, SENSOR_UNIT_ROWS, SENSOR_PREC_ROWS, SENSOR_PARAM1_ROWS, SENSOR_PARAM2_ROWS, SENSOR_PARAM3_ROWS, SENSOR_PARAM4_ROWS, 0 }); - lcd_outdezAtt(MENUS_MARGIN_LEFT+getTextWidth(TR_MENUSENSOR)+5, MENU_FOOTER_TOP, s_currIdx+1, HEADER_COLOR|LEFT); + lcdDrawNumber(MENUS_MARGIN_LEFT+getTextWidth(TR_MENUSENSOR)+5, MENU_FOOTER_TOP, s_currIdx+1, HEADER_COLOR|LEFT); putsTelemetryChannelValue(SENSOR_2ND_COLUMN, MENU_FOOTER_TOP, s_currIdx, getValue(MIXSRC_FIRST_TELEM+3*s_currIdx), HEADER_COLOR|LEFT); int sub = m_posVert; @@ -252,7 +252,7 @@ void menuModelSensor(evt_t event) if (sensor->type == TELEM_TYPE_CUSTOM) { lcd_putsLeft(y, STR_ID); lcd_outhex4(SENSOR_2ND_COLUMN, y, sensor->id, LEFT|(m_posHorz==0 ? attr : 0)); - lcd_outdezAtt(SENSOR_3RD_COLUMN, y, sensor->instance, LEFT|(m_posHorz==1 ? attr : 0)); + lcdDrawNumber(SENSOR_3RD_COLUMN, y, sensor->instance, LEFT|(m_posHorz==1 ? attr : 0)); if (attr) { switch (m_posHorz) { case 0: @@ -337,7 +337,7 @@ void menuModelSensor(evt_t event) if (sensor->custom.ratio == 0) lcdDrawText(SENSOR_2ND_COLUMN, y, "-", attr); else - lcd_outdezAtt(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); + lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); break; } // no break @@ -361,7 +361,7 @@ void menuModelSensor(evt_t event) lcd_putsLeft(y, NO_INDENT(STR_OFFSET)); if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.offset, -30000, +30000); if (sensor->prec > 0) attr |= (sensor->prec == 2 ? PREC2 : PREC1); - lcd_outdezAtt(SENSOR_2ND_COLUMN, y, sensor->custom.offset, LEFT|attr); + lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.offset, LEFT|attr); break; } // no break @@ -400,6 +400,8 @@ void menuModelSensor(evt_t event) } } + + return true; } void onSensorMenu(const char *result) @@ -442,7 +444,7 @@ void onTelemetryScriptFileSelectionMenu(const char *result) } #endif -void menuModelTelemetry(evt_t event) +bool menuModelTelemetry(evt_t event) { MENU(STR_MENUTELEMETRY, menuTabModel, e_Telemetry, ITEM_TELEMETRY_MAX, DEFAULT_SCROLLBAR_X, { TELEMETRY_TYPE_ROWS RSSI_ROWS SENSORS_ROWS USRDATA_ROWS CASE_VARIO(LABEL(Vario)) CASE_VARIO(0) CASE_VARIO(VARIO_RANGE_ROWS) LABEL(TopBar), 0, 0, TELEMETRY_SCREEN_ROWS(0), TELEMETRY_SCREEN_ROWS(1), TELEMETRY_SCREEN_ROWS(2), TELEMETRY_SCREEN_ROWS(3) }); @@ -461,10 +463,10 @@ void menuModelTelemetry(evt_t event) if (k>=ITEM_TELEMETRY_SENSOR1 && k0) { CHECK_INCDEC_MODELVAR(event, g_model.frsky.rssiAlarms[alarm].value, -30, 30); } @@ -541,10 +543,10 @@ void menuModelTelemetry(evt_t event) case ITEM_TELEMETRY_VARIO_RANGE: lcd_putsLeft(y, STR_LIMIT); - lcd_outdezAtt(TELEM_COL2, y, -10+g_model.frsky.varioMin, (m_posHorz<=0 ? attr : 0)|LEFT); - lcd_outdezAtt(TELEM_COL2+70, y, -5+g_model.frsky.varioCenterMin, ((CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0)|PREC1); - lcd_outdezAtt(TELEM_COL2+115, y, 5+g_model.frsky.varioCenterMax, ((CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0)|PREC1); - lcd_outdezAtt(TELEM_COL2+160, y, 10+g_model.frsky.varioMax, ((CURSOR_ON_LINE() || m_posHorz==3) ? attr : 0)); + lcdDrawNumber(TELEM_COL2, y, -10+g_model.frsky.varioMin, (m_posHorz<=0 ? attr : 0)|LEFT); + lcdDrawNumber(TELEM_COL2+70, y, -5+g_model.frsky.varioCenterMin, ((CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0)|PREC1); + lcdDrawNumber(TELEM_COL2+115, y, 5+g_model.frsky.varioCenterMax, ((CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0)|PREC1); + lcdDrawNumber(TELEM_COL2+160, y, 10+g_model.frsky.varioMax, ((CURSOR_ON_LINE() || m_posHorz==3) ? attr : 0)); if (attr && s_editMode>0) { switch (m_posHorz) { case 0: @@ -725,4 +727,6 @@ void menuModelTelemetry(evt_t event) } } } + + return true; } diff --git a/radio/src/gui/Horus/menus.cpp b/radio/src/gui/Horus/menus.cpp index 1ba534a53..a0bbf645b 100644 --- a/radio/src/gui/Horus/menus.cpp +++ b/radio/src/gui/Horus/menus.cpp @@ -76,7 +76,7 @@ void pushMenu(MenuFuncP newMenu) menuEvent = EVT_ENTRY; } -void menuModelNotes(evt_t event) +bool menuModelNotes(evt_t event) { if (event == EVT_ENTRY) { strcpy(s_text_file, MODELS_PATH "/"); @@ -84,7 +84,7 @@ void menuModelNotes(evt_t event) strcpy(buf, TEXT_EXT); } - menuTextView(event); + return menuTextView(event); } void pushModelNotes() diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h index 3c33771cc..0eb9f8edf 100644 --- a/radio/src/gui/Horus/menus.h +++ b/radio/src/gui/Horus/menus.h @@ -50,7 +50,7 @@ #define MENU_COLUMN2_X 280 -#define lcd_putsColumnLeft(x, y, str) lcd_puts((x > MENU_COLUMN2_X+MENUS_MARGIN_LEFT+68) ? MENU_COLUMN2_X+MENUS_MARGIN_LEFT : MENUS_MARGIN_LEFT, y, str) +#define lcd_putsColumnLeft(x, y, str) lcdDrawText((x > MENU_COLUMN2_X+MENUS_MARGIN_LEFT+68) ? MENU_COLUMN2_X+MENUS_MARGIN_LEFT : MENUS_MARGIN_LEFT, y, str) typedef int16_t vertpos_t; @@ -68,10 +68,9 @@ extern uint8_t calibrationState; void drawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr); -typedef void (*MenuFuncP)(evt_t event); -typedef void (*MenuFuncP_PROGMEM)(evt_t event); -extern const MenuFuncP_PROGMEM menuTabModel[]; -extern const MenuFuncP_PROGMEM menuTabGeneral[]; +typedef bool (*MenuFuncP)(evt_t event); +extern const MenuFuncP menuTabModel[]; +extern const MenuFuncP menuTabGeneral[]; extern MenuFuncP g_menuStack[5]; extern uint8_t g_menuPos[4]; @@ -85,33 +84,28 @@ void pushMenu(MenuFuncP newMenu); /// return to last menu in menustack void popMenu(); -void menuFirstCalib(evt_t event); +bool menuFirstCalib(evt_t event); -void onMainViewMenu(const char *result); -void menuMainView(evt_t event); -void menuGeneralDiagAna(evt_t event); - -void menuTelemetryFrsky(evt_t event); -void menuGeneralSetup(evt_t event); -void menuGeneralCalib(evt_t event); -void menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext); - -void menuModelSelect(evt_t event); -void menuModelSetup(evt_t event); -void menuModelCustomFunctions(evt_t event); -void menuStatisticsView(evt_t event); -void menuStatisticsDebug(evt_t event); -void menuAboutView(evt_t event); +bool menuMainView(evt_t event); +bool menuGeneralDiagAna(evt_t event); +bool menuGeneralSetup(evt_t event); +bool menuGeneralCalib(evt_t event); +bool menuCustomFunctions(evt_t event, CustomFunctionData * functions, CustomFunctionsContext & functionsContext); +bool menuModelSelect(evt_t event); +bool menuModelSetup(evt_t event); +bool menuModelCustomFunctions(evt_t event); +bool menuStatisticsView(evt_t event); +bool menuStatisticsDebug(evt_t event); +bool menuAboutView(evt_t event); +bool menuMainViewChannelsMonitor(evt_t event); +bool menuChannelsView(evt_t event); +bool menuChannelsView(evt_t event); +bool menuTextView(evt_t event); #if defined(DEBUG_TRACE_BUFFER) void menuTraceBuffer(evt_t event); #endif -void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); - -void menuMainViewChannelsMonitor(evt_t event); -void menuChannelsView(evt_t event); - extern int8_t checkIncDec_Ret; // global helper vars #define EDIT_SELECT_MENU -1 @@ -200,36 +194,40 @@ int8_t checkIncDecMovedSwitch(int8_t val); #define CURSOR_ON_LINE() (m_posHorz<0) #define CHECK_FLAG_NO_SCREEN_INDEX 1 -bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t maxrow, uint8_t flags=0); -bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, vertpos_t maxrow); +bool navigate(evt_t event, int count, int rows, int columns=1); +bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t maxrow, uint8_t flags=0); +bool check_simple(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t menuTabSize, vertpos_t maxrow); bool check_submenu_simple(check_event_t event, uint8_t maxrow); #define MENU_TAB(...) const uint8_t mstate_tab[] = __VA_ARGS__ -void drawMenuTemplate(const char *title, evt_t event, uint16_t scrollbar_X=0); +void drawShadow(coord_t x, coord_t y, coord_t w, coord_t h); +void drawScreenTemplate(const char * title); +void drawMenuTemplate(const char * title, uint16_t scrollbar_X=0); +void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); #define MENU(title, tab, menu, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ - if (!check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count)) return; \ - drawMenuTemplate(title, event, scrollbar_X); \ + if (!check(event, menu, tab, DIM(tab), mstate_tab, DIM(mstate_tab)-1, lines_count)) return false; \ + drawMenuTemplate(title, scrollbar_X); \ #define SIMPLE_MENU(title, tab, menu, lines_count, scrollbar_X) \ if (event == EVT_ENTRY || event == EVT_ENTRY_UP) TRACE("Menu %s displayed ...", title); \ - if (!check_simple(event, menu, tab, DIM(tab), lines_count)) return; \ - drawMenuTemplate(title, event, scrollbar_X); \ + if (!check_simple(event, menu, tab, DIM(tab), lines_count)) return false; \ + drawMenuTemplate(title, scrollbar_X); \ #define SUBMENU(title, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ - if (!check(event, 0, NULL, 0, mstate_tab, DIM(mstate_tab)-1, lines_count)) return; \ - drawMenuTemplate(title, event, scrollbar_X); + if (!check(event, 0, NULL, 0, mstate_tab, DIM(mstate_tab)-1, lines_count)) return false; \ + drawMenuTemplate(title, scrollbar_X); #define SIMPLE_SUBMENU_NOTITLE(lines_count) \ - if (!check_submenu_simple(event, lines_count)) return + if (!check_submenu_simple(event, lines_count)) return false #define SIMPLE_SUBMENU(title, lines_count, scrollbar_X) \ SIMPLE_SUBMENU_NOTITLE(lines_count); \ - drawMenuTemplate(title, event, scrollbar_X) + drawMenuTemplate(title, scrollbar_X) typedef int select_menu_value_t; @@ -250,7 +248,7 @@ int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, evt_t e #define displayGVar(x, y, v, min, max) GVAR_MENU_ITEM(x, y, v, min, max, 0, 0, 0) #else int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t max, LcdFlags attr, evt_t event); - #define displayGVar(x, y, v, min, max) lcd_outdez8(x, y, v) + #define displayGVar(x, y, v, min, max) lcdDrawNumber(x, y, v) #endif void editName(coord_t x, coord_t y, char *name, uint8_t size, evt_t event, uint8_t active); @@ -272,9 +270,9 @@ extern uint8_t s_warning_type; #define POPUP_W 300 #define POPUP_H 130 #define WARNING_LINE_LEN 32 -#define WARNING_LINE_X (POPUP_X+86) +#define WARNING_LINE_X (POPUP_X+76) #define WARNING_LINE_Y (POPUP_Y+9) -#define WARNING_INFOLINE_Y (WARNING_LINE_Y+64) +#define WARNING_INFOLINE_Y (WARNING_LINE_Y+68) void displayBox(); void displayPopup(const char *title); @@ -308,12 +306,9 @@ extern void (*menuHandler)(const char *result); #define TEXT_FILENAME_MAXLEN 40 extern char s_text_file[TEXT_FILENAME_MAXLEN]; -void menuTextView(evt_t event); void pushMenuTextView(const char *filename); void pushModelNotes(); -void menuChannelsView(evt_t event); - #define LABEL(...) (uint8_t)-1 #define CURSOR_MOVED_LEFT(event) (event==EVT_ROTARY_LEFT || EVT_KEY_MASK(event) == KEY_LEFT) diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp index 31d5c893b..08b27b681 100644 --- a/radio/src/gui/Horus/navigation.cpp +++ b/radio/src/gui/Horus/navigation.cpp @@ -222,7 +222,74 @@ uint8_t menuPageIndex; uint8_t menuPageCount; uint16_t linesCount; -bool check(check_event_t event, uint8_t curr, const MenuFuncP *menuTab, uint8_t menuTabSize, const pm_uint8_t *horTab, uint8_t horTabMax, vertpos_t rowcount, uint8_t flags) +bool navigate(evt_t event, int count, int rows, int columns) +{ + int prevPosHorz = m_posHorz; + int prevPosVert = m_posVert; + + int maxrow = ((count+columns-1) / columns) - 1; + int maxlastcol = count-maxrow*columns-1; + int maxcol = (m_posVert != maxrow ? columns-1 : maxlastcol); + + switch (event) { + case EVT_KEY_FIRST(KEY_RIGHT): + case EVT_KEY_REPT(KEY_RIGHT): + INC(m_posHorz, 0, maxcol); + break; + + case EVT_KEY_FIRST(KEY_LEFT): + case EVT_KEY_REPT(KEY_LEFT): + DEC(m_posHorz, 0, maxcol); + break; + + case EVT_KEY_FIRST(KEY_DOWN): + case EVT_KEY_REPT(KEY_DOWN): + INC(m_posVert, 0, maxrow); + if (m_posVert == maxrow && m_posHorz > maxlastcol) { + m_posHorz = maxlastcol; + } + break; + + case EVT_KEY_FIRST(KEY_UP): + case EVT_KEY_REPT(KEY_UP): + DEC(m_posVert, 0, maxrow); + if (m_posVert == maxrow && m_posHorz > maxlastcol) { + m_posHorz = maxlastcol; + } + break; + + case EVT_ROTARY_LEFT: + if (m_posHorz > 0) { + m_posHorz--; + } + else { + DEC(m_posVert, 0, maxrow); + m_posHorz = (m_posVert != maxrow ? columns-1 : maxlastcol); + } + break; + + case EVT_ROTARY_RIGHT: + if (m_posHorz < maxcol) { + m_posHorz++; + } + else { + INC(m_posVert, 0, maxrow); + m_posHorz = 0; + } + break; + } + + if (s_pgOfs > m_posVert) { + s_pgOfs = m_posVert; + } + else if (s_pgOfs <= m_posVert - rows) { + s_pgOfs = m_posVert - rows + 1; + } + + return (prevPosHorz != m_posHorz || prevPosVert != m_posVert); +} + +bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t menuTabSize, const pm_uint8_t * horTab, uint8_t horTabMax, vertpos_t rowcount, uint8_t flags) { uint8_t maxcol = MAXCOL(m_posVert); diff --git a/radio/src/gui/Horus/popups.cpp b/radio/src/gui/Horus/popups.cpp index 7530e9770..5614994d3 100644 --- a/radio/src/gui/Horus/popups.cpp +++ b/radio/src/gui/Horus/popups.cpp @@ -48,7 +48,7 @@ int16_t s_warning_input_max; void displayAlertBox() { - lcdDrawBlackOverlay(); + lcdClear(); lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); @@ -57,7 +57,7 @@ void displayAlertBox() void displayWarningBox() { - lcdDrawBlackOverlay(); + // lcdDrawBlackOverlay(); lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); @@ -66,7 +66,7 @@ void displayWarningBox() void displayMessageBox() { - lcdDrawBlackOverlay(); + // lcdDrawBlackOverlay(); lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, WARNING_COLOR); lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, WARNING_COLOR); @@ -85,9 +85,9 @@ void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SO lcdDrawText(WARNING_LINE_X, WARNING_LINE_Y+25, STR_WARNING, ALARM_COLOR|DBLSIZE); #endif - if (t) lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y, t); + if (t) lcdDrawText(WARNING_LINE_X, WARNING_INFOLINE_Y, t); if (last) { - lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y+12, last); + lcdDrawText(WARNING_LINE_X, WARNING_INFOLINE_Y+16, last); AUDIO_ERROR_MESSAGE(sound); } @@ -98,7 +98,7 @@ void message(const pm_char *title, const pm_char *t, const char *last MESSAGE_SO void displayPopup(const char *title) { - displayMessageBox(); + // displayMessageBox(); lcdDrawTextWithLen(WARNING_LINE_X, WARNING_LINE_Y, title, WARNING_LINE_LEN, DBLSIZE|WARNING_COLOR); lcdRefresh(); } @@ -114,7 +114,7 @@ void displayWarning(evt_t event) if (s_warning_info) { lcdDrawTextWithLen(WARNING_LINE_X, WARNING_INFOLINE_Y, s_warning_info, s_warning_info_len, WARNING_INFO_FLAGS); } - lcd_puts(WARNING_LINE_X, WARNING_INFOLINE_Y+12, s_warning_type == WARNING_TYPE_ASTERISK ? STR_EXIT : STR_POPUPS); + lcdDrawText(WARNING_LINE_X, WARNING_INFOLINE_Y+20, s_warning_type == WARNING_TYPE_ASTERISK ? STR_EXIT : STR_POPUPS); switch (event) { case EVT_KEY_BREAK(KEY_ENTER): if (s_warning_type == WARNING_TYPE_ASTERISK) @@ -146,25 +146,6 @@ const char * displayMenu(evt_t event) { const char * result = NULL; uint8_t display_count = min(s_menu_count, (uint16_t)MENU_MAX_LINES); - int y = (LCD_H - (display_count*(FH+1))) / 2; - - lcdDrawBlackOverlay(); - lcdDrawSolidFilledRect(MENU_X, y, MENU_W, display_count * (FH+1), TEXT_BGCOLOR); - lcdDrawSolidRect(MENU_X, y, MENU_W, display_count * (FH+1) + 1, ALARM_COLOR); - - for (uint8_t i=0; i display_count) { - drawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); - } switch (event) { case EVT_ROTARY_LEFT: @@ -215,5 +196,25 @@ const char * displayMenu(evt_t event) break; } + int y = (LCD_H - (display_count*(FH+1))) / 2; + + // lcdDrawBlackOverlay(); + lcdDrawSolidFilledRect(MENU_X, y, MENU_W, display_count * (FH+1), TEXT_BGCOLOR); + lcdDrawSolidRect(MENU_X, y, MENU_W, display_count * (FH+1) + 1, ALARM_COLOR); + + for (uint8_t i=0; i display_count) { + drawScrollbar(MENU_X+MENU_W-1, y+1, MENU_MAX_LINES * (FH+1), s_menu_offset, s_menu_count, MENU_MAX_LINES); + } + return result; } diff --git a/radio/src/gui/Horus/splash.cpp b/radio/src/gui/Horus/splash.cpp index 176514f8a..58c5d61be 100644 --- a/radio/src/gui/Horus/splash.cpp +++ b/radio/src/gui/Horus/splash.cpp @@ -36,7 +36,7 @@ #include "../../opentx.h" -const uint16_t LBM_SPLASH[] = { +const uint8_t LBM_SPLASH[] = { #include "../../bitmaps/Horus/splash.lbm" }; @@ -50,7 +50,7 @@ void displaySplash() #if MENUS_LOCK == 1 if (readonly == false) { lcdDrawSolidFilledRect((LCD_W-(sizeof(TR_UNLOCKED)-1)*FW)/2 - 9, 50, (sizeof(TR_UNLOCKED)-1)*FW+16, 11, SOLID, ERASE|ROUND); - lcd_puts((LCD_W-(sizeof(TR_UNLOCKED)-1)*FW)/2 , 53, STR_UNLOCKED); + lcdDrawText((LCD_W-(sizeof(TR_UNLOCKED)-1)*FW)/2 , 53, STR_UNLOCKED); } #endif diff --git a/radio/src/gui/Horus/view_about.cpp b/radio/src/gui/Horus/view_about.cpp index a204fbf35..8d0fcb36f 100644 --- a/radio/src/gui/Horus/view_about.cpp +++ b/radio/src/gui/Horus/view_about.cpp @@ -54,7 +54,7 @@ enum AboutScreens { #define ABOUT_X 62 #define ABOUT_INDENT 16 -void menuAboutView(evt_t event) +bool menuAboutView(evt_t event) { static uint8_t screenIndex = 0; static uint8_t greyIndex = 0; @@ -78,74 +78,74 @@ void menuAboutView(evt_t event) break; } - drawMenuTemplate("About", event); + drawScreenTemplate("About"); uint8_t screenDuration = 150; switch (screenIndex) { case ABOUT_OPENTX: case ABOUT_END: - lcd_puts(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_OPENTX_1); - lcd_puts(ABOUT_X, MENU_CONTENT_TOP + FH, STR_ABOUT_OPENTX_2); - lcd_puts(ABOUT_X, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_OPENTX_3); - lcd_puts(ABOUT_X, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_OPENTX_4); - lcd_puts(ABOUT_X, MENU_CONTENT_TOP + 4*FH, STR_ABOUT_OPENTX_5); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_OPENTX_1); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP + FH, STR_ABOUT_OPENTX_2); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_OPENTX_3); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_OPENTX_4); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP + 4*FH, STR_ABOUT_OPENTX_5); screenDuration = 255; break; case ABOUT_BERTRAND: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_BERTRAND_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_BERTRAND_2); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_BERTRAND_3); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_BERTRAND_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_BERTRAND_2); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_BERTRAND_3); break; case ABOUT_ANDRE: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ANDRE_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ANDRE_2); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ANDRE_3); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_ANDRE_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ANDRE_2); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ANDRE_3); break; case ABOUT_MIKE: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MIKE_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MIKE_2); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_MIKE_3); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_MIKE_4); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_MIKE_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MIKE_2); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_MIKE_3); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_MIKE_4); break; case ABOUT_KJELL: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_KJELL_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_KJELL_2); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_KJELL_3); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_KJELL_4); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_KJELL_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_KJELL_2); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_KJELL_3); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_KJELL_4); break; case ABOUT_MARTIN: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_MARTIN_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MARTIN_2); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_MARTIN_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_MARTIN_2); break; case ABOUT_ROMOLO: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROMOLO_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROMOLO_2); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ROMOLO_3); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_ROMOLO_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROMOLO_2); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_ROMOLO_3); break; case ABOUT_ROB: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_ROB_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROB_2); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_ROB_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_ROB_2); break; case ABOUT_HARDWARE: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, "FrSky", INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_HARDWARE_2); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_HARDWARE_3); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, "FrSky", INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_HARDWARE_2); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_HARDWARE_3); break; case ABOUT_PARENTS: - lcdDrawText(ABOUT_X, MENU_HEADER_HEIGHT+1, STR_ABOUT_PARENTS_1, INVERS); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_PARENTS_2); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_PARENTS_3); - lcd_puts(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_PARENTS_4); + lcdDrawText(ABOUT_X, MENU_CONTENT_TOP, STR_ABOUT_PARENTS_1, INVERS); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + FH, STR_ABOUT_PARENTS_2); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 2*FH, STR_ABOUT_PARENTS_3); + lcdDrawText(ABOUT_X+ABOUT_INDENT, MENU_CONTENT_TOP + 3*FH, STR_ABOUT_PARENTS_4); screenDuration = 255; break; } @@ -156,4 +156,6 @@ void menuAboutView(evt_t event) chainMenu(menuMainView); } } + + return true; } diff --git a/radio/src/gui/Horus/view_channels.cpp b/radio/src/gui/Horus/view_channels.cpp index a3df50232..6188085df 100644 --- a/radio/src/gui/Horus/view_channels.cpp +++ b/radio/src/gui/Horus/view_channels.cpp @@ -39,7 +39,7 @@ bool isChannelUsed(int channel); int getChannelsUsed(); -void menuChannelsView(evt_t event) +bool menuChannelsView(evt_t event) { const int CHANNEL_MARGIN = 16; const int CHANNEL_HEIGHT = 25; @@ -110,13 +110,15 @@ void menuChannelsView(evt_t event) // Value #if defined(PPM_UNIT_US) uint8_t wbar = (longNames ? 54 : 64); - lcd_outdezAtt(x+LCD_W/2-3-wbar-ofs, y+1, PPM_CH_CENTER(ch)+val/2, TINSIZE); + lcdDrawNumber(x+LCD_W/2-3-wbar-ofs, y+1, PPM_CH_CENTER(ch)+val/2, TINSIZE); #elif defined(PPM_UNIT_PERCENT_PREC1) uint8_t wbar = (longNames ? 48 : 58); - lcd_outdezAtt(x+LCD_W/2-3-wbar-ofs, y+1, calcRESXto1000(val), PREC1|TINSIZE); + lcdDrawNumber(x+LCD_W/2-3-wbar-ofs, y+1, calcRESXto1000(val), PREC1|TINSIZE); #else uint8_t wbar = (longNames ? 54 : 64); - lcd_outdezAtt(x+LCD_W/2-3-wbar-ofs, y+1, calcRESXto1000(val)/10, TINSIZE); // G: Don't like the decimal part* + lcdDrawNumber(x+LCD_W/2-3-wbar-ofs, y+1, calcRESXto1000(val)/10, TINSIZE); // G: Don't like the decimal part* #endif #endif + + return true; } diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index a97b21862..1ca93a9cc 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -36,13 +36,23 @@ #include "../../opentx.h" -#define TRIM_LH_X 90 -#define TRIM_LV_X 24 -#define TRIM_RV_X (LCD_W-35) -#define TRIM_RH_X (LCD_W-95) -#define TRIM_V_Y 135 -#define TRIM_H_Y 235 -#define TRIM_LEN 80 +#define TRIM_LH_X 90 +#define TRIM_LV_X 24 +#define TRIM_RV_X (LCD_W-35) +#define TRIM_RH_X (LCD_W-95) +#define TRIM_V_Y 135 +#define TRIM_H_Y 235 +#define TRIM_LEN 80 + +#define MODELPANEL_LEFT 240 +#define MODELPANEL_TOP 68 +#define MODELPANEL_WIDTH MODEL_BITMAP_WIDTH +#define MODELPANEL_HEIGHT 135 + +#define TIMER1PANEL_LEFT 46 +#define TIMER1PANEL_TOP 55 +#define TIMER2PANEL_LEFT TIMER1PANEL_LEFT +#define TIMER2PANEL_TOP 134 void drawTrimSquare(coord_t x, coord_t y) { @@ -140,7 +150,7 @@ void drawTrims(uint8_t flightMode) drawVerticalTrimPosition(xm-1, ym-6, val); if (g_model.displayTrims != DISPLAY_TRIMS_NEVER && trim != 0) { if (g_model.displayTrims == DISPLAY_TRIMS_ALWAYS || (trimsDisplayTimer > 0 && (trimsDisplayMask & (1<0 ? 100 : 200, xm-2, trim, TINSIZE|VERTICAL); + // TODO lcdDrawNumber(trim>0 ? 100 : 200, xm-2, trim, TINSIZE|VERTICAL); } } } @@ -151,7 +161,7 @@ void drawTrims(uint8_t flightMode) drawHorizontalTrimPosition(xm-3, ym-2, val); if (g_model.displayTrims != DISPLAY_TRIMS_NEVER && trim != 0) { if (g_model.displayTrims == DISPLAY_TRIMS_ALWAYS || (trimsDisplayTimer > 0 && (trimsDisplayMask & (1<0 ? -20 : 50), ym+2, trim, TINSIZE); + lcdDrawNumber((stickIndex==0 ? TRIM_LH_X : TRIM_RH_X)+(trim>0 ? -20 : 50), ym+2, trim, TINSIZE); } } } @@ -166,11 +176,11 @@ void drawTimer(coord_t x, coord_t y, int index) if (timerData.start) { lcdDrawBitmapPatternPie(x+2, y+3, LBM_RSCALE, TITLE_BGCOLOR, 0, timerState.val <= 0 ? 360 : 360*(timerData.start-timerState.val)/timerData.start); } - putsTimer(x+74, y+31, abs(timerState.val), TEXT_COLOR|DBLSIZE|LEFT); + putsTimer(x+76, y+31, abs(timerState.val), TEXT_COLOR|DBLSIZE|LEFT); if (ZLEN(timerData.name) > 0) { - lcdDrawTextWithLen(x+74, y+20, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE|TEXT_COLOR); + lcdDrawTextWithLen(x+78, y+20, timerData.name, LEN_TIMER_NAME, ZCHAR|SMLSIZE|TEXT_COLOR); } - putsStrIdx(x+137, y+17, "TMR", 1, SMLSIZE|TEXT_COLOR); + putsStrIdx(x+137, y+17, "TMR", index+1, SMLSIZE|TEXT_COLOR); } @@ -280,16 +290,7 @@ void onMainViewMenu(const char *result) } } -const uint16_t LBM_MAINVIEW_FLAT[] = { -#include "../../bitmaps/Horus/mainview_flat.lbm" -}; - -const uint16_t LBM_CORSAIR[] = { -#include "../../bitmaps/Horus/corsair.lbm" -}; - - -void menuMainView(evt_t event) +bool menuMainView(evt_t event) { switch (event) { case EVT_ENTRY: @@ -298,6 +299,10 @@ void menuMainView(evt_t event) killEvents(KEY_DOWN); break; + case EVT_ENTRY_UP: + LOAD_MODEL_BITMAP(); + break; + case EVT_KEY_LONG(KEY_ENTER): killEvents(event); MENU_ADD_ITEM(STR_MODEL_SELECT); @@ -341,14 +346,13 @@ void menuMainView(evt_t event) } lcdDrawBitmap(0, 0, LBM_MAINVIEW_BACKGROUND); - // lcdDrawBitmap(0, 0, LBM_MAINVIEW_FLAT); // Header lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); drawTopmenuDatetime(); - if (1 || usbPlugged()) { + if (usbPlugged()) { lcdDrawBitmapPattern(378, 8, LBM_TOPMENU_USB, MENU_TITLE_COLOR); } const uint8_t rssiBarsValue[] = { 30, 40, 50, 60, 80 }; @@ -358,7 +362,6 @@ void menuMainView(evt_t event) lcdDrawSolidFilledRect(390+i*6, 38-height, 4, height, TELEMETRY_RSSI() >= rssiBarsValue[i] ? MENU_TITLE_COLOR : MENU_TITLE_DISABLE_COLOR); } - // Flight Mode Name int mode = mixerCurrentFlightMode; for (int i=0; itime % SECS_PER_DAY; putsTimer(4*10, y, tme, TIMEHOUR|LEFT); //event - lcd_outdezNAtt(14*10, y, te->event, LEADING0|LEFT, 3); + lcdDrawNumber(14*10, y, te->event, LEADING0|LEFT, 3); //data lcd_putsn (20*10, y, "0x", 2); lcd_outhex4(22*10-2, y, (uint16_t)(te->data >> 16)); @@ -207,6 +213,6 @@ void menuTraceBuffer(evt_t event) } - + return true; } -#endif //#if defined(DEBUG_TRACE_BUFFER) +#endif // defined(DEBUG_TRACE_BUFFER) diff --git a/radio/src/gui/Horus/view_text.cpp b/radio/src/gui/Horus/view_text.cpp index f9a036441..db12a6d69 100644 --- a/radio/src/gui/Horus/view_text.cpp +++ b/radio/src/gui/Horus/view_text.cpp @@ -39,7 +39,7 @@ #define TEXT_FILE_MAXSIZE 2048 char s_text_file[TEXT_FILENAME_MAXLEN]; -char s_text_screen[LCD_LINES-1][LCD_COLS+1]; +char s_text_screen[NUM_BODY_LINES][LCD_COLS+1]; void readTextFile(int & lines_count) { @@ -56,13 +56,13 @@ void readTextFile(int & lines_count) result = f_open(&file, s_text_file, FA_OPEN_EXISTING | FA_READ); if (result == FR_OK) { - for (int i=0; i=s_pgOfs && current_line-s_pgOfs=s_pgOfs && current_line-s_pgOfs NUM_BODY_LINES) { - drawScrollbar(LCD_W-5, 30, MENU_FOOTER_TOP-34, s_pgOfs, lines_count, NUM_BODY_LINES); - } + drawScrollbar(LCD_W-5, 30, MENU_FOOTER_TOP-34, s_pgOfs, lines_count, NUM_BODY_LINES); + + return true; } void pushMenuTextView(const char *filename) diff --git a/radio/src/gui/Horus/widgets.cpp b/radio/src/gui/Horus/widgets.cpp index 867675ad8..dfe78a565 100644 --- a/radio/src/gui/Horus/widgets.cpp +++ b/radio/src/gui/Horus/widgets.cpp @@ -106,12 +106,14 @@ void drawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) void drawScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible) { - lcdDrawSolidVerticalLine(x, y, h, LINE_COLOR); - coord_t yofs = (h*offset + count/2) / count; - coord_t yhgt = (h*visible + count/2) / count; - if (yhgt + yofs > h) - yhgt = h - yofs; - lcdDrawSolidFilledRect(x-1, y + yofs, 3, yhgt, SCROLLBOX_COLOR); + if (visible < count) { + lcdDrawSolidVerticalLine(x, y, h, LINE_COLOR); + coord_t yofs = (h*offset + count/2) / count; + coord_t yhgt = (h*visible + count/2) / count; + if (yhgt + yofs > h) + yhgt = h - yofs; + lcdDrawSolidFilledRect(x-1, y + yofs, 3, yhgt, SCROLLBOX_COLOR); + } } void drawProgressBar(const char *label) @@ -130,9 +132,38 @@ void updateProgressBar(int num, int den) } } +void drawShadow(coord_t x, coord_t y, coord_t w, coord_t h) +{ + lcdDrawSolidVerticalLine(x+w, y+1, h, TEXT_COLOR); + lcdDrawSolidHorizontalLine(x+1, y+h, w, TEXT_COLOR); + lcdDrawSolidVerticalLine(x+w+1, y+2, h, LINE_COLOR); + lcdDrawSolidHorizontalLine(x+2, y+h+1, w, LINE_COLOR); +} + +void drawScreenTemplate(const char * title) +{ + // Header + lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); + lcdDrawBitmapPattern(0, 0, LBM_TOPMENU_POLYGON, TITLE_BGCOLOR); + drawTopmenuDatetime(); + + lcdDrawBitmapPattern(4, 10, LBM_TOPMENU_OPENTX, MENU_TITLE_COLOR); + + if (title) { + // must be done at the end so that we can write something at the right of the menu title + lcdDrawText(50, 3, title, MENU_TITLE_COLOR|DBLSIZE); + } + + // Body + lcdDrawSolidFilledRect(0, MENU_HEADER_HEIGHT, LCD_W, LCD_H-MENU_HEADER_HEIGHT-MENU_FOOTER_HEIGHT, TEXT_BGCOLOR); + + // Footer + lcdDrawSolidFilledRect(0, MENU_FOOTER_TOP, LCD_W, MENU_FOOTER_HEIGHT, HEADER_BGCOLOR); +} + #define MENU_ICONS_SPACING 31 -void drawMenuTemplate(const char * name, evt_t event, uint16_t scrollbar_X) +void drawMenuTemplate(const char * name, uint16_t scrollbar_X) { // clear the screen lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); @@ -257,7 +288,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m } } else { - lcd_outdezAtt(x, y, value, attr, "%"); + lcdDrawNumber(x, y, value, attr, 0, NULL, "%"); if (invers) value = checkIncDec(event, value, min, max, EE_MODEL | editflags); } return value; @@ -266,7 +297,7 @@ int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t m int16_t gvarMenuItem(coord_t x, coord_t y, int16_t value, int16_t min, int16_t max, LcdFlags attr, evt_t event) { if (attr & INVERS) value = checkIncDec(event, value, min, max, EE_MODEL); - lcd_outdezAtt(x, y, value, attr, "%"); + lcdDrawNumber(x, y, value, attr, 0, NULL, "%"); return value; } #endif diff --git a/radio/src/gui/Taranis/lcd.cpp b/radio/src/gui/Taranis/lcd.cpp index 5d73bc284..4c7b9eb6a 100644 --- a/radio/src/gui/Taranis/lcd.cpp +++ b/radio/src/gui/Taranis/lcd.cpp @@ -325,12 +325,12 @@ void lcd_outdez8(coord_t x, coord_t y, int8_t val) lcd_outdezAtt(x, y, val); } -void lcd_outdezAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags) +void lcd_outdezAtt(coord_t x, coord_t y, int32_t val, LcdFlags flags) { lcd_outdezNAtt(x, y, val, flags); } -void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t len) +void lcd_outdezNAtt(coord_t x, coord_t y, int32_t val, LcdFlags flags, uint8_t len) { uint8_t fw = FWNUM; int8_t mode = MODE(flags); @@ -356,7 +356,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t if (mode != MODE(LEADING0)) { len = 1; - lcduint_t tmp = ((lcduint_t)val) / 10; + int32_t tmp = val / 10; while (tmp) { len++; tmp /= 10; @@ -400,7 +400,7 @@ void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags flags, uint8_t if (dblsize) x++; for (uint8_t i=1; i<=len; i++) { - div_t qr = div((lcduint_t)val, 10); + div_t qr = div(val, 10); char c = qr.rem + '0'; LcdFlags f = flags; lcdDrawChar(x, y, c, f); @@ -875,7 +875,7 @@ const pm_uint8_t bchunit_ar[] PROGMEM = { UNIT_DIST, // GPS Alt }; -void putsValueWithUnit(coord_t x, coord_t y, lcdint_t val, uint8_t unit, LcdFlags att) +void putsValueWithUnit(coord_t x, coord_t y, int32_t val, uint8_t unit, LcdFlags att) { // convertUnit(val, unit); lcd_outdezAtt(x, y, val, att & (~NO_UNIT)); @@ -956,7 +956,7 @@ void displayGpsCoords(coord_t x, coord_t y, TelemetryItem & telemetryItem, LcdFl } } -void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, lcdint_t value, LcdFlags att) +void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, int32_t value, LcdFlags att) { TelemetryItem & telemetryItem = telemetryItems[channel]; TelemetrySensor & telemetrySensor = g_model.telemetrySensors[channel]; @@ -976,7 +976,7 @@ void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, lcdint_t v } } -void putsChannelValue(coord_t x, coord_t y, source_t channel, lcdint_t value, LcdFlags att) +void putsChannelValue(coord_t x, coord_t y, source_t channel, int32_t value, LcdFlags att) { if (channel >= MIXSRC_FIRST_TELEM) { channel = (channel-MIXSRC_FIRST_TELEM) / 3; diff --git a/radio/src/gui/Taranis/lcd.h b/radio/src/gui/Taranis/lcd.h index 3138ea5b9..ede2df64a 100644 --- a/radio/src/gui/Taranis/lcd.h +++ b/radio/src/gui/Taranis/lcd.h @@ -48,7 +48,6 @@ #define CONTRAST_MAX 45 #define lcdint_t int32_t -#define lcduint_t uint32_t #define FW 6 #define FWNUM 5 @@ -156,8 +155,8 @@ void lcd_putsLeft(coord_t y, const pm_char * s); void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags mode=0); -void lcd_outdezNAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags mode=0, uint8_t len=0); -void lcd_outdezAtt(coord_t x, coord_t y, lcdint_t val, LcdFlags mode=0); +void lcd_outdezNAtt(coord_t x, coord_t y, int32_t val, LcdFlags mode=0, uint8_t len=0); +void lcd_outdezAtt(coord_t x, coord_t y, int32_t val, LcdFlags mode=0); void lcd_outdez8(coord_t x, coord_t y, int8_t val); void putsStrIdx(coord_t x, coord_t y, const pm_char *str, uint8_t idx, LcdFlags att=0); @@ -180,9 +179,9 @@ void putsVolts(coord_t x, coord_t y, uint16_t volts, LcdFlags att); void putsVBat(coord_t x, coord_t y, LcdFlags att); #if !defined(BOOT) -void putsChannelValue(coord_t x, coord_t y, source_t channel, lcdint_t val, LcdFlags att=0); +void putsChannelValue(coord_t x, coord_t y, source_t channel, int32_t val, LcdFlags att=0); void putsChannel(coord_t x, coord_t y, source_t channel, LcdFlags att=0); -void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, lcdint_t val, LcdFlags att=0); +void putsTelemetryChannelValue(coord_t x, coord_t y, uint8_t channel, int32_t val, LcdFlags att=0); #endif #define putstime_t int32_t @@ -232,8 +231,8 @@ void lcdSetContrast(); void lcdRefresh(); #endif -const char *bmpLoad(uint8_t *dest, const char *filename, const unsigned int width, const unsigned int height); -const char *writeScreenshot(); +const char * bmpLoad(uint8_t * dest, const char * filename, uint16_t width, uint16_t height); +const char * writeScreenshot(); #if defined(BOOT) #define BLINK_ON_PHASE (0) @@ -241,14 +240,4 @@ const char *writeScreenshot(); #define BLINK_ON_PHASE (g_blinkTmr10ms & (1<<6)) #endif -#if defined(SIMU) - extern bool lcd_refresh; - extern display_t lcd_buf[DISPLAY_BUFFER_SIZE]; -#endif - -char *strAppend(char * dest, const char * source, int len=0); -char *strSetCursor(char *dest, int position); -char *strAppendDate(char * str, bool time=false); -char *strAppendFilename(char * dest, const char * filename, const int size); - #endif // _LCD_H_ diff --git a/radio/src/gui/Taranis/menu_general.cpp b/radio/src/gui/Taranis/menu_general.cpp index a3534ca57..8f64fc4e1 100644 --- a/radio/src/gui/Taranis/menu_general.cpp +++ b/radio/src/gui/Taranis/menu_general.cpp @@ -88,7 +88,7 @@ enum EnumTabDiag { e_Calib }; -const MenuFuncP_PROGMEM menuTabGeneral[] PROGMEM = { +const MenuFuncP menuTabGeneral[] = { menuGeneralSetup, menuGeneralSdManager, menuGeneralCustomFunctions, diff --git a/radio/src/gui/Taranis/menu_general_sdmanager.cpp b/radio/src/gui/Taranis/menu_general_sdmanager.cpp index d5727fa52..c0bceee47 100644 --- a/radio/src/gui/Taranis/menu_general_sdmanager.cpp +++ b/radio/src/gui/Taranis/menu_general_sdmanager.cpp @@ -41,7 +41,7 @@ #define IS_DIRECTORY(fname) ((bool)(!NODE_TYPE(fname))) #define IS_FILE(fname) ((bool)(NODE_TYPE(fname))) -void menuGeneralSdManagerInfo(uint8_t event) +void menuGeneralSdManagerInfo(evt_t event) { SIMPLE_SUBMENU(STR_SD_INFO_TITLE, 1); @@ -181,13 +181,13 @@ void onSdManagerMenu(const char *result) strcat(lfn, line); } if (strcmp(clipboard.data.sd.directory, lfn)) { // prevent copying to the same directory - POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, lfn)); + POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, clipboard.data.sd.filename, lfn)); REFRESH_FILES(); } } else if (result == STR_RENAME_FILE) { memcpy(reusableBuffer.sdmanager.originalName, line, sizeof(reusableBuffer.sdmanager.originalName)); - char *ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); + char * ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); if (ext) { // write spaces to allow a longer filename memset(ext, ' ', SD_SCREEN_FILE_LENGTH-LEN_FILE_EXTENSION-(ext-line)); @@ -243,7 +243,7 @@ void onSdManagerMenu(const char *result) #endif } -void menuGeneralSdManager(uint8_t _event) +void menuGeneralSdManager(evt_t _event) { if (s_warning_result) { s_warning_result = 0; @@ -261,7 +261,7 @@ void menuGeneralSdManager(uint8_t _event) int lastPos = m_posVert; - uint8_t event = (EVT_KEY_MASK(_event) == KEY_ENTER ? 0 : _event); + evt_t event = (EVT_KEY_MASK(_event) == KEY_ENTER ? 0 : _event); SIMPLE_MENU(SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD, menuTabGeneral, e_Sd, reusableBuffer.sdmanager.count); int index = m_posVert-s_pgOfs; @@ -306,8 +306,8 @@ void menuGeneralSdManager(uint8_t _event) case EVT_KEY_LONG(KEY_ENTER): if (s_editMode == 0) { killEvents(_event); - char *line = reusableBuffer.sdmanager.lines[index]; - char *ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); + char * line = reusableBuffer.sdmanager.lines[index]; + char * ext = getFileExtension(line, SD_SCREEN_FILE_LENGTH+1); if (!strcmp(line, "..")) { break; // no menu for parent dir } @@ -341,7 +341,7 @@ void menuGeneralSdManager(uint8_t _event) } } if (!READ_ONLY()) { - if (IS_FILE(line)) // it's a file + if (IS_FILE(line)) MENU_ADD_ITEM(STR_COPY_FILE); if (clipboard.type == CLIPBOARD_TYPE_SD_FILE) MENU_ADD_ITEM(STR_PASTE); @@ -366,17 +366,17 @@ void menuGeneralSdManager(uint8_t _event) reusableBuffer.sdmanager.offset = 0; memset(reusableBuffer.sdmanager.lines, 0, sizeof(reusableBuffer.sdmanager.lines)); } - else if (s_pgOfs == reusableBuffer.sdmanager.count-7) { + else if (s_pgOfs == reusableBuffer.sdmanager.count-NUM_BODY_LINES) { reusableBuffer.sdmanager.offset = s_pgOfs; memset(reusableBuffer.sdmanager.lines, 0, sizeof(reusableBuffer.sdmanager.lines)); } else if (s_pgOfs > reusableBuffer.sdmanager.offset) { - memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], 6*sizeof(reusableBuffer.sdmanager.lines[0])); - memset(reusableBuffer.sdmanager.lines[6], 0xff, SD_SCREEN_FILE_LENGTH); - NODE_TYPE(reusableBuffer.sdmanager.lines[6]) = 1; + memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0])); + memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0xff, SD_SCREEN_FILE_LENGTH); + NODE_TYPE(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1]) = 1; } else { - memmove(reusableBuffer.sdmanager.lines[1], reusableBuffer.sdmanager.lines[0], 6*sizeof(reusableBuffer.sdmanager.lines[0])); + memmove(reusableBuffer.sdmanager.lines[1], reusableBuffer.sdmanager.lines[0], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0])); memset(reusableBuffer.sdmanager.lines[0], 0, sizeof(reusableBuffer.sdmanager.lines[0])); } @@ -403,7 +403,7 @@ void menuGeneralSdManager(uint8_t _event) for (int i=0; i=0; i--) { + for (int8_t i=NUM_BODY_LINES-1; i>=0; i--) { char *line = reusableBuffer.sdmanager.lines[i]; if (line[0] == '\0' || isFilenameGreater(isfile, fn, line)) { if (i > 0) memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], sizeof(reusableBuffer.sdmanager.lines[0]) * i); @@ -424,10 +424,10 @@ void menuGeneralSdManager(uint8_t _event) } } else if (s_pgOfs > reusableBuffer.sdmanager.offset) { - if (isFilenameGreater(isfile, fn, reusableBuffer.sdmanager.lines[5]) && isFilenameLower(isfile, fn, reusableBuffer.sdmanager.lines[6])) { - memset(reusableBuffer.sdmanager.lines[6], 0, sizeof(reusableBuffer.sdmanager.lines[0])); - strcpy(reusableBuffer.sdmanager.lines[6], fn); - NODE_TYPE(reusableBuffer.sdmanager.lines[6]) = isfile; + if (isFilenameGreater(isfile, fn, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-2]) && isFilenameLower(isfile, fn, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1])) { + memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0, sizeof(reusableBuffer.sdmanager.lines[0])); + strcpy(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], fn); + NODE_TYPE(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1]) = isfile; } } else { @@ -471,7 +471,7 @@ void menuGeneralSdManager(uint8_t _event) } } - char *ext = getFileExtension(reusableBuffer.sdmanager.lines[index], SD_SCREEN_FILE_LENGTH+1); + char * ext = getFileExtension(reusableBuffer.sdmanager.lines[index], SD_SCREEN_FILE_LENGTH+1); if (ext && !strcasecmp(ext, BITMAPS_EXT)) { if (lastPos != m_posVert) { if (bmpLoad(modelBitmap, reusableBuffer.sdmanager.lines[index], MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT)) { diff --git a/radio/src/gui/Taranis/menu_general_version.cpp b/radio/src/gui/Taranis/menu_general_version.cpp index b321b9962..0e9c467ff 100644 --- a/radio/src/gui/Taranis/menu_general_version.cpp +++ b/radio/src/gui/Taranis/menu_general_version.cpp @@ -85,7 +85,7 @@ void menuGeneralVersion(uint8_t event) { if (s_warning_result) { s_warning_result = 0; - displayPopup(STR_EEPROMFORMATTING); + displayPopup(STR_STORAGE_FORMAT); storageEraseAll(false); #if !defined(SIMU) NVIC_SystemReset(); diff --git a/radio/src/gui/Taranis/menu_model.cpp b/radio/src/gui/Taranis/menu_model.cpp index 7955c4913..53516d1dc 100644 --- a/radio/src/gui/Taranis/menu_model.cpp +++ b/radio/src/gui/Taranis/menu_model.cpp @@ -108,7 +108,7 @@ uint8_t editDelay(const coord_t y, const uint8_t event, const uint8_t attr, cons #define EDIT_DELAY(x, y, event, attr, str, delay) editDelay(y, event, attr, str, delay) #endif -const MenuFuncP_PROGMEM menuTabModel[] PROGMEM = { +const MenuFuncP menuTabModel[] = { menuModelSelect, menuModelSetup, CASE_HELI(menuModelHeli) diff --git a/radio/src/gui/Taranis/menus.h b/radio/src/gui/Taranis/menus.h index eda4925fd..3931fbab8 100644 --- a/radio/src/gui/Taranis/menus.h +++ b/radio/src/gui/Taranis/menus.h @@ -66,9 +66,8 @@ extern uint8_t calibrationState; void menu_lcd_onoff(coord_t x, coord_t y, uint8_t value, LcdFlags attr); typedef void (*MenuFuncP)(uint8_t event); -typedef void (*MenuFuncP_PROGMEM)(uint8_t event); -extern const MenuFuncP_PROGMEM menuTabModel[]; -extern const MenuFuncP_PROGMEM menuTabGeneral[]; +extern const MenuFuncP menuTabModel[]; +extern const MenuFuncP menuTabGeneral[]; extern MenuFuncP g_menuStack[5]; extern uint8_t g_menuPos[4]; diff --git a/radio/src/gui/gui.h b/radio/src/gui/gui.h index a82ddff6c..cee4cdb63 100644 --- a/radio/src/gui/gui.h +++ b/radio/src/gui/gui.h @@ -51,4 +51,9 @@ #include "9X/gui.h" #endif +#if defined(SIMU) +extern bool simuLcdRefresh; +extern display_t simuLcdBuf[DISPLAY_BUFFER_SIZE]; +#endif + #endif // _GUI_H_ diff --git a/radio/src/keys.h b/radio/src/keys.h index 2c678bd9c..559f06003 100644 --- a/radio/src/keys.h +++ b/radio/src/keys.h @@ -190,7 +190,6 @@ enum EnumKeys { #define _MSK_KEY_FLAGS 0x0e00 #define EVT_ENTRY 0x1000 #define EVT_ENTRY_UP 0x2000 -#define EVT_MENU_UP 0x4000 #else #define _MSK_KEY_BREAK 0x20 #define _MSK_KEY_REPT 0x40 @@ -199,7 +198,6 @@ enum EnumKeys { #define _MSK_KEY_FLAGS 0xe0 #define EVT_ENTRY 0xbf #define EVT_ENTRY_UP 0xbe -#define EVT_MENU_UP 0xbd #endif #define EVT_KEY_BREAK(key) ((key)|_MSK_KEY_BREAK) @@ -227,6 +225,10 @@ enum EnumKeys { #define EVT_ROTARY_RIGHT 0xde #endif +#if defined(COLORLCD) + #define EVT_REFRESH 0xDD00 +#endif + class Key { #define FILTERBITS 4 diff --git a/radio/src/main_arm.cpp b/radio/src/main_arm.cpp index a0736f7d6..b75527095 100644 --- a/radio/src/main_arm.cpp +++ b/radio/src/main_arm.cpp @@ -42,7 +42,7 @@ uint8_t requestScreenshot = false; void handleUsbConnection() { -#if defined(PCBTARANIS) && !defined(SIMU) +#if defined(CPUSTM32) && !defined(SIMU) static bool usbStarted = false; if (!usbStarted && usbPlugged()) { @@ -78,7 +78,7 @@ void handleUsbConnection() } #endif -#endif //#if defined(PCBTARANIS) && !defined(SIMU) +#endif // defined(CPUSTM32) && !defined(SIMU) } void checkSpeakerVolume() @@ -107,7 +107,104 @@ void checkEeprom() } #endif -#if defined(GUI) +#if defined(GUI) && defined(COLORLCD) +void guiMain(evt_t evt) +{ + bool refreshNeeded = false; + +#if defined(LUA) + uint32_t t0 = get_tmr10ms(); + static uint32_t lastLuaTime = 0; + uint16_t interval = (lastLuaTime == 0 ? 0 : (t0 - lastLuaTime)); + lastLuaTime = t0; + if (interval > maxLuaInterval) { + maxLuaInterval = interval; + } + + // run Lua scripts that don't use LCD (to use CPU time while LCD DMA is running) + luaTask(0, RUN_MIX_SCRIPT | RUN_FUNC_SCRIPT | RUN_TELEM_BG_SCRIPT, false); + + // wait for LCD DMA to finish before continuing, because code from this point + // is allowed to change the contents of LCD buffer + // + // WARNING: make sure no code above this line does any change to the LCD display buffer! + // + lcdRefreshWait(); + + // draw LCD from menus or from Lua script + // run Lua scripts that use LCD + + bool standaloneScriptWasRun = luaTask(evt, RUN_STNDAL_SCRIPT, true); + if (!standaloneScriptWasRun) { + luaTask(evt, RUN_TELEM_FG_SCRIPT, true); + refreshNeeded = true; + } + + t0 = get_tmr10ms() - t0; + if (t0 > maxLuaDuration) { + maxLuaDuration = t0; + } +#else + lcdRefreshWait(); // WARNING: make sure no code above this line does any change to the LCD display buffer! + const bool standaloneScriptWasRun = false; +#endif + + if (!standaloneScriptWasRun) { + while (1) { + // normal GUI from menus + const char * warn = s_warning; + uint8_t menu = s_menu_count; + + static bool popupDisplayed = false; + if (warn || menu) { + if (popupDisplayed == false) { + g_menuStack[g_menuStackPtr](EVT_REFRESH); + lcdDrawBlackOverlay(); + lcdStoreBackupBuffer(); + } + if (popupDisplayed == false || evt) { + lcdRestoreBackupBuffer(); + if (warn) DISPLAY_WARNING(evt); + if (menu) { + const char * result = displayMenu(evt); + if (result) { + menuHandler(result); + evt = EVT_REFRESH; + continue; + } + } + refreshNeeded = true; + popupDisplayed = true; + } + } + else { + popupDisplayed = false; + refreshNeeded = g_menuStack[g_menuStackPtr](evt); + } + + if (menuEvent == EVT_ENTRY) { + m_posVert = -1; + m_posHorz = 0; + evt = menuEvent; + menuEvent = 0; + } + else if (menuEvent == EVT_ENTRY_UP) { + m_posVert = g_menuPos[g_menuStackPtr]; + m_posHorz = 0; + evt = menuEvent; + menuEvent = 0; + } + else { + break; + } + } + } + + if (refreshNeeded) { + lcdRefresh(); + } +} +#elif defined(GUI) void guiMain(evt_t evt) { #if defined(LUA) @@ -147,39 +244,29 @@ void guiMain(evt_t evt) #endif if (!standaloneScriptWasRun) { - while (1) { -#if !defined(COLORLCD) - lcdClear(); -#endif - // normal GUI from menus - const char * warn = s_warning; - uint8_t menu = s_menu_count; - g_menuStack[g_menuStackPtr]((warn || menu) ? 0 : evt); - if (warn) DISPLAY_WARNING(evt); - if (menu) { - const char * result = displayMenu(evt); - if (result) { - menuHandler(result); - putEvent(EVT_MENU_UP); - } - } - drawStatusLine(); - if (menuEvent == EVT_ENTRY) { - m_posVert = -1; - m_posHorz = 0; - evt = menuEvent; - menuEvent = 0; - } - else if (menuEvent == EVT_ENTRY_UP) { - m_posVert = g_menuPos[g_menuStackPtr]; - m_posHorz = 0; - evt = menuEvent; - menuEvent = 0; - } - else { - break; + lcdClear(); + + // normal GUI from menus + const char * warn = s_warning; + uint8_t menu = s_menu_count; + if (menuEvent) { + m_posVert = menuEvent == EVT_ENTRY_UP ? g_menuPos[g_menuStackPtr] : 0; + m_posHorz = 0; + evt = menuEvent; + menuEvent = 0; + AUDIO_MENUS(); + } + + g_menuStack[g_menuStackPtr]((warn || menu) ? 0 : evt); + if (warn) DISPLAY_WARNING(evt); + if (menu) { + const char * result = displayMenu(evt); + if (result) { + menuHandler(result); } } + + drawStatusLine(); } lcdRefresh(); diff --git a/radio/src/main_avr.cpp b/radio/src/main_avr.cpp index cb1738b40..7aa266dee 100644 --- a/radio/src/main_avr.cpp +++ b/radio/src/main_avr.cpp @@ -146,7 +146,6 @@ void perMain() const char * result = displayMenu(evt); if (result) { menuHandler(result); - putEvent(EVT_MENU_UP); } } #endif diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 3ad41c06d..ed69b86af 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -439,6 +439,7 @@ enum UartModes { #if defined(PCBHORUS) #define LEN_SWITCH_NAME 3 #define LEN_ANA_NAME 3 + #define LEN_MODEL_FILENAME 16 #define EXTRA_GENERAL_FIELDS \ EXTRA_GENERAL_FIELDS_ARM \ uint8_t serial2Mode:6; \ @@ -448,7 +449,7 @@ enum UartModes { uint8_t potsType; /*two bits for every pot*/\ char switchNames[NUM_SWITCHES][LEN_SWITCH_NAME]; \ char anaNames[NUM_STICKS+NUM_POTS][LEN_ANA_NAME]; \ - char currModelFilename[10]; + char currModelFilename[LEN_MODEL_FILENAME+1]; #elif defined(PCBFLAMENCO) #define LEN_SWITCH_NAME 3 #define LEN_ANA_NAME 3 diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index b76cf64d4..10e71b2b7 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -55,9 +55,9 @@ ModelData g_model; Clipboard clipboard; #endif -#if defined(PCBTARANIS) && defined(SDCARD) +#if (defined(PCBTARANIS) || defined(PCBHORUS)) && defined(SDCARD) uint8_t modelBitmap[MODEL_BITMAP_SIZE]; -void loadModelBitmap(char *name, uint8_t *bitmap) +void loadModelBitmap(char * name, uint8_t * bitmap) { uint8_t len = zlen(name, LEN_BITMAP_NAME); if (len > 0) { @@ -325,6 +325,10 @@ void generalDefault() memcpy(g_eeGeneral.bluetoothName, defaultName, sizeof(defaultName)); #endif +#if !defined(EEPROM) + strcpy(g_eeGeneral.currModelFilename, DEFAULT_MODEL_FILENAME); +#endif + g_eeGeneral.chkSum = 0xFFFF; } @@ -417,17 +421,6 @@ void checkModelIdUnique(uint8_t index, uint8_t module) } #endif -#if defined(SDCARD) -bool isFileAvailable(const char * filename) -{ - FILINFO info; - TCHAR lfn[_MAX_LFN + 1]; - info.lfname = lfn; - info.lfsize = sizeof(lfn); - return f_stat(filename, &info) == FR_OK; -} -#endif - void modelDefault(uint8_t id) { memset(&g_model, 0, sizeof(g_model)); @@ -466,6 +459,10 @@ void modelDefault(uint8_t id) g_model.mavlink.rc_rssi_scale = 15; g_model.mavlink.pc_rssi_en = 1; #endif + +#if !defined(EEPROM) + strcpy(g_model.header.name, "\015\361\374\373\364\033\034"); +#endif } #if defined(VIRTUALINPUTS) @@ -1172,7 +1169,7 @@ void checkLowEEPROM() { if (g_eeGeneral.disableMemoryWarning) return; if (EeFsGetFree() < 100) { - ALERT(STR_EEPROMWARN, STR_EEPROMLOWMEM, AU_ERROR); + ALERT(STR_STORAGE_WARNING, STR_EEPROMLOWMEM, AU_ERROR); } } #endif diff --git a/radio/src/opentx.h b/radio/src/opentx.h index 31ac860ec..c8bd19a54 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -257,14 +257,12 @@ #endif #define CONVERT_PTR_UINT(x) ((uint32_t)(uint64_t)(x)) #define CONVERT_UINT_PTR(x) ((uint32_t*)(uint64_t)(x)) - char *convertSimuPath(const char *path); #else #define FORCEINLINE inline __attribute__ ((always_inline)) #define NOINLINE __attribute__ ((noinline)) #define SIMU_SLEEP(x) #define CONVERT_PTR_UINT(x) ((uint32_t)(x)) #define CONVERT_UINT_PTR(x) ((uint32_t *)(x)) - #define convertSimuPath(x) (x) #endif #if !defined(CPUM64) && !defined(ACCURAT_THROTTLE_TIMER) @@ -458,13 +456,21 @@ #include "storage/storage.h" #include "pulses/pulses.h" -#if defined(PCBTARANIS) - #define BITMAP_BUFFER_SIZE(width, height) (2 + width * ((height+7)/8)*4) +#if defined(PCBHORUS) + #define BITMAP_BUFFER_SIZE(width, height) (4 + (width)*(height)*3) + #define MODEL_BITMAP_WIDTH (3*64) // 171 + #define MODEL_BITMAP_HEIGHT (3*32) // 101 + #define MODEL_BITMAP_SIZE BITMAP_BUFFER_SIZE(MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT) + extern uint8_t modelBitmap[MODEL_BITMAP_SIZE]; + void loadModelBitmap(char * name, uint8_t * bitmap); + #define LOAD_MODEL_BITMAP() loadModelBitmap(g_model.header.bitmap, modelBitmap) +#elif defined(PCBTARANIS) + #define BITMAP_BUFFER_SIZE(width, height) (2 + (width) * (((height)+7)/8)*4) #define MODEL_BITMAP_WIDTH 64 #define MODEL_BITMAP_HEIGHT 32 #define MODEL_BITMAP_SIZE BITMAP_BUFFER_SIZE(MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT) extern uint8_t modelBitmap[MODEL_BITMAP_SIZE]; - void loadModelBitmap(char *name, uint8_t *bitmap); + void loadModelBitmap(char * name, uint8_t * bitmap); #define LOAD_MODEL_BITMAP() loadModelBitmap(g_model.header.bitmap, modelBitmap) #else #define LOAD_MODEL_BITMAP() @@ -556,6 +562,7 @@ typedef struct { } } CustomFunctionsContext; +#include "strhelpers.h" #include "gui/gui.h" #if defined(TEMPLATES) @@ -597,7 +604,7 @@ enum BaseCurves { #define THRCHK_DEADBAND 16 -#if defined(PCBFLAMENCO) +#if defined(COLORLCD) #define SPLASH_NEEDED() (true) #elif defined(PCBTARANIS) #define SPLASH_NEEDED() (g_eeGeneral.splashMode != 3) @@ -1095,7 +1102,6 @@ void generalDefault(); void modelDefault(uint8_t id); #if defined(CPUARM) -bool isFileAvailable(const char * filename); void checkModelIdUnique(uint8_t index, uint8_t module); #endif @@ -1410,8 +1416,8 @@ enum AUDIO_SOUNDS { #if defined(VOICE) AU_THROTTLE_ALERT, AU_SWITCH_ALERT, - AU_BAD_EEPROM, - AU_EEPROM_FORMATTING, + AU_BAD_RADIODATA, + AU_STORAGE_FORMAT, #endif AU_TX_BATTERY_LOW, AU_INACTIVITY, @@ -1544,13 +1550,13 @@ extern void opentxClose(); extern void opentxInit(); // Re-useable byte array to save having multiple buffers -#define SD_SCREEN_FILE_LENGTH (32) +#define SD_SCREEN_FILE_LENGTH (64) union ReusableBuffer { // 275 bytes struct { - char listnames[LCD_LINES-1][LEN_MODEL_NAME]; + char listnames[NUM_BODY_LINES][LEN_MODEL_NAME]; #if !defined(CPUARM) uint16_t eepromfree; #endif @@ -1584,7 +1590,7 @@ union ReusableBuffer // 274 bytes struct { - char lines[LCD_LINES-1][SD_SCREEN_FILE_LENGTH+1+1]; // the last char is used to store the flags (directory) of the line + char lines[NUM_BODY_LINES][SD_SCREEN_FILE_LENGTH+1+1]; // the last char is used to store the flags (directory) of the line uint32_t available; uint16_t offset; uint16_t count; @@ -1598,7 +1604,7 @@ extern union ReusableBuffer reusableBuffer; void checkFlashOnBeep(); #if defined(CPUARM) -void putsValueWithUnit(coord_t x, coord_t y, lcdint_t val, uint8_t unit, LcdFlags att); +void putsValueWithUnit(coord_t x, coord_t y, int32_t val, uint8_t unit, LcdFlags att); #elif defined(FRSKY) void convertUnit(getvalue_t & val, uint8_t & unit); // TODO check FORCEINLINE on stock #else diff --git a/radio/src/sdcard.cpp b/radio/src/sdcard.cpp index 6628415f7..9ba7d7f02 100644 --- a/radio/src/sdcard.cpp +++ b/radio/src/sdcard.cpp @@ -58,6 +58,74 @@ const char * sdCheckAndCreateDirectory(const char * path) return NULL; } +bool isFileAvailable(const char * path) +{ + FILINFO info; + TCHAR lfn[_MAX_LFN + 1]; + info.lfname = lfn; + info.lfsize = sizeof(lfn); + return f_stat(path, &info) == FR_OK; +} + +bool isFileAvailable(const char * filename, const char * directory) +{ + char path[256]; + char * pos = strAppend(path, directory); + *pos = '/'; + strAppend(pos+1, filename); + return isFileAvailable(path); +} + +#define LEN_FILE_EXTENSION 4 +char * getFileExtension(char * filename, int size=0) +{ + int len = strlen(filename); + if (size != 0 && size < len) { + len = size; + } + for (int i=len; i>=len-LEN_FILE_EXTENSION; --i) { + if (filename[i] == '.') { + return &filename[i]; + } + } + return NULL; +} + +char * getFileIndex(char * filename, unsigned int & value) +{ + value = 0; + char * pos = getFileExtension(filename); + if (!pos || pos == filename) + return NULL; + while (pos > filename) { + pos--; + char c = *pos; + if (c >= '0' && c <= '9') { + value = (value * 10) + (c - '\0'); + } + else { + return pos+1; + } + } + return filename; +} + +bool findNextFileIndex(char * filename, const char * directory) +{ + unsigned int index; + char * pos = getFileIndex(filename, index); + char extension[LEN_FILE_EXTENSION+1]; + strncpy(extension, getFileExtension(filename), sizeof(extension)); + do { + pos = strAppendNumber(pos, ++index); + strAppend(pos, extension); + if (!isFileAvailable(filename, directory)) { + return true; + } + } while (index < 99); + return false; +} + bool sdListFiles(const char *path, const char *extension, const uint8_t maxlen, const char *selection, uint8_t flags=0) { FILINFO fno; @@ -207,29 +275,20 @@ bool sdListFiles(const char *path, const char *extension, const uint8_t maxlen, } #if defined(CPUARM) && defined(SDCARD) -const char * sdCopyFile(const char * filename, const char * srcDir, const char * destDir) +const char * sdCopyFile(const char * srcPath, const char * destPath) { FIL srcFile; - FIL dstFile; + FIL destFile; char buf[256]; UINT read = sizeof(buf); UINT written = sizeof(buf); - char path[2*CLIPBOARD_PATH_LEN+1]; - char *tmp = strAppend(path, srcDir, CLIPBOARD_PATH_LEN); - *tmp++ = '/'; - strAppend(tmp, filename, CLIPBOARD_PATH_LEN); - - FRESULT result = f_open(&srcFile, path, FA_OPEN_EXISTING | FA_READ); + FRESULT result = f_open(&srcFile, srcPath, FA_OPEN_EXISTING | FA_READ); if (result != FR_OK) { return SDCARD_ERROR(result); } - tmp = strAppend(path, destDir, CLIPBOARD_PATH_LEN); - *tmp++ = '/'; - strAppend(tmp, filename, CLIPBOARD_PATH_LEN); - - result = f_open(&dstFile, path, FA_CREATE_ALWAYS | FA_WRITE); + result = f_open(&destFile, destPath, FA_CREATE_ALWAYS | FA_WRITE); if (result != FR_OK) { f_close(&srcFile); return SDCARD_ERROR(result); @@ -238,11 +297,11 @@ const char * sdCopyFile(const char * filename, const char * srcDir, const char * while (result==FR_OK && read==sizeof(buf) && written==sizeof(buf)) { result = f_read(&srcFile, buf, sizeof(buf), &read); if (result == FR_OK) { - result = f_write(&dstFile, buf, read, &written); + result = f_write(&destFile, buf, read, &written); } } - f_close(&dstFile); + f_close(&destFile); f_close(&srcFile); if (result != FR_OK) { @@ -251,7 +310,22 @@ const char * sdCopyFile(const char * filename, const char * srcDir, const char * return NULL; } -#endif // #if defined(PCBTARANIS) + +const char * sdCopyFile(const char * srcFilename, const char * srcDir, const char * destFilename, const char * destDir) +{ + char srcPath[2*CLIPBOARD_PATH_LEN+1]; + char * tmp = strAppend(srcPath, srcDir, CLIPBOARD_PATH_LEN); + *tmp++ = '/'; + strAppend(tmp, srcFilename, CLIPBOARD_PATH_LEN); + + char destPath[2*CLIPBOARD_PATH_LEN+1]; + tmp = strAppend(destPath, destDir, CLIPBOARD_PATH_LEN); + *tmp++ = '/'; + strAppend(tmp, destFilename, CLIPBOARD_PATH_LEN); + + return sdCopyFile(srcPath, destPath); +} +#endif // defined(CPUARM) && defined(SDCARD) #if !defined(SIMU) || defined(SIMU_DISKIO) diff --git a/radio/src/sdcard.h b/radio/src/sdcard.h index 05f959e90..cc21ebf40 100644 --- a/radio/src/sdcard.h +++ b/radio/src/sdcard.h @@ -100,7 +100,11 @@ inline const pm_char * SDCARD_ERROR(FRESULT result) #define O9X_FOURCC 0x3178396F // o9x for gruvin9x/MEGA2560 #endif -const char *sdCopyFile(const char *filename, const char *srcDir, const char *destDir); +bool isFileAvailable(const char * filename); +bool findNextFileIndex(char * filename, const char * directory); + +const char * sdCopyFile(const char * src, const char * dest); +const char * sdCopyFile(const char * filename, const char * srcDir, const char * destDir); #endif diff --git a/radio/src/serial.cpp b/radio/src/serial.cpp index 3e868c20f..1baf5bb75 100644 --- a/radio/src/serial.cpp +++ b/radio/src/serial.cpp @@ -53,10 +53,11 @@ void serialPutc(char c) void serialPrintf(const char * format, ...) { va_list arglist; - char tmp[PRINTF_BUFFER_SIZE]; + char tmp[PRINTF_BUFFER_SIZE+1]; va_start(arglist, format); vsnprintf(tmp, PRINTF_BUFFER_SIZE, format, arglist); + tmp[PRINTF_BUFFER_SIZE] = '\0'; va_end(arglist); const char *t = tmp; diff --git a/radio/src/simu.cpp b/radio/src/simu.cpp index d8616ed19..04e4847c8 100644 --- a/radio/src/simu.cpp +++ b/radio/src/simu.cpp @@ -454,8 +454,8 @@ void Open9xSim::setPixel(int x, int y, FXColor color) void Open9xSim::refreshDisplay() { - if (lcd_refresh) { - lcd_refresh = false; + if (simuLcdRefresh) { + simuLcdRefresh = false; FXColor offColor = isBacklightEnable() ? BL_COLOR : FXRGB(200, 200, 200); #if LCD_W == 128 FXColor onColor = FXRGB(0, 0, 0); @@ -463,19 +463,19 @@ void Open9xSim::refreshDisplay() for (int x=0; x>11)/0x1f, 255*((z&0x07E0)>>5)/0x3F, 255*(z&0x001F)/0x01F); setPixel(x, y, color); } #elif defined(PCBFLAMENCO) - display_t z = lcd_buf[y * LCD_W + x]; + display_t z = simuLcdBuf[y * LCD_W + x]; if (1) { FXColor color = FXRGB(255*((z&0xF00)>>8)/0x0f, 255*((z&0x0F0)>>4)/0x0f, 255*(z&0x00F)/0x0f); setPixel(x, y, color); } #elif defined(PCBTARANIS) - display_t * p = &lcd_buf[y / 2 * LCD_W + x]; + display_t * p = &simuLcdBuf[y / 2 * LCD_W + x]; uint8_t z = (y & 1) ? (*p >> 4) : (*p & 0x0F); if (z) { FXColor color; @@ -486,7 +486,7 @@ void Open9xSim::refreshDisplay() setPixel(x, y, color); } #else - if (lcd_buf[x+(y/8)*LCD_W] & (1<<(y%8))) { + if (simuLcdBuf[x+(y/8)*LCD_W] & (1<<(y%8))) { setPixel(x, y, onColor); } #endif diff --git a/radio/src/storage/eeprom_conversions.cpp b/radio/src/storage/eeprom_conversions.cpp index 1506d2fed..5ad37e5c1 100644 --- a/radio/src/storage/eeprom_conversions.cpp +++ b/radio/src/storage/eeprom_conversions.cpp @@ -1401,10 +1401,10 @@ bool eeConvert() g_eeGeneral.backlightMode = e_backlight_mode_on; g_eeGeneral.backlightBright = 0; g_eeGeneral.contrast = 25; - ALERT(STR_EEPROMWARN, msg, AU_BAD_EEPROM); + ALERT(STR_STORAGE_WARNING, msg, AU_BAD_RADIODATA); // Message - MESSAGE(STR_EEPROMWARN, STR_EEPROM_CONVERTING, NULL, AU_EEPROM_FORMATTING); // TODO translations + MESSAGE(STR_STORAGE_WARNING, STR_EEPROM_CONVERTING, NULL, AU_STORAGE_FORMAT); // TODO translations // General Settings conversion eeLoadGeneralSettingsData(); diff --git a/radio/src/storage/sdcard_raw.cpp b/radio/src/storage/sdcard_raw.cpp index f34e3be0b..9d79ffc45 100644 --- a/radio/src/storage/sdcard_raw.cpp +++ b/radio/src/storage/sdcard_raw.cpp @@ -116,16 +116,25 @@ const char * loadFile(const char * filename, uint8_t * data, uint16_t maxsize) return NULL; } -const char * loadModel(const char * filename) +void getModelPath(char * path, const char * filename) { - char path[256]; strcpy(path, STR_MODELS_PATH); path[sizeof(MODELS_PATH)-1] = '/'; strcpy(&path[sizeof(MODELS_PATH)], filename); +} +const char * readModel(const char * filename, uint8_t * buffer, uint32_t size) +{ + char path[256]; + getModelPath(path, filename); + return loadFile(path, buffer, size); +} + +const char * loadModel(const char * filename) +{ preModelLoad(); - const char * error = loadFile(path, (uint8_t *)&g_model, sizeof(g_model)); + const char * error = readModel(filename, (uint8_t *)&g_model, sizeof(g_model)); if (error) { TRACE("loadModel error=%s", error); } @@ -194,7 +203,6 @@ void storageReadAll() if (loadGeneralSettings() != NULL) { storageEraseAll(true); - strcpy(g_eeGeneral.currModelFilename, "model.bin"); } stickMode = g_eeGeneral.stickMode; @@ -211,10 +219,22 @@ void storageReadAll() loadModel(g_eeGeneral.currModelFilename); } +void storageCreateModelsList() +{ + FIL file; + + FRESULT result = f_open(&file, RADIO_MODELSLIST_PATH, FA_CREATE_ALWAYS | FA_WRITE); + if (result == FR_OK) { + f_puts("[" DEFAULT_CATEGORY "]\n" DEFAULT_MODEL_FILENAME "\n", &file); + f_close(&file); + } +} + void storageFormat() { sdCheckAndCreateDirectory(RADIO_PATH); sdCheckAndCreateDirectory(MODELS_PATH); + storageCreateModelsList(); } struct StorageModelsList { @@ -357,6 +377,10 @@ const char * storageModifyModel(unsigned int operation, int category, int positi f_putc('\n', &file); continue; } + else if (operation == STORAGE_REMOVE) { + TRACE("skip %s", line); + continue; + } } } } @@ -370,6 +394,8 @@ const char * storageModifyModel(unsigned int operation, int category, int positi } f_close(&file); + f_unlink(RADIO_PATH "/models.old"); + f_rename(RADIO_MODELSLIST_PATH, RADIO_PATH "/models.old"); f_rename(RADIO_PATH "/models.tmp", RADIO_MODELSLIST_PATH); return NULL; } diff --git a/radio/src/storage/sdcard_raw.h b/radio/src/storage/sdcard_raw.h index 448e77d36..f56163159 100644 --- a/radio/src/storage/sdcard_raw.h +++ b/radio/src/storage/sdcard_raw.h @@ -34,6 +34,10 @@ * */ +#define DEFAULT_CATEGORY "Models" +#define DEFAULT_MODEL_FILENAME "model01.bin" + +const char * readModel(const char * filename, uint8_t * buffer, uint32_t size); const char * loadModel(const char * filename); diff --git a/radio/src/storage/storage_common.cpp b/radio/src/storage/storage_common.cpp index 9dc2ab2c6..05bdfb692 100644 --- a/radio/src/storage/storage_common.cpp +++ b/radio/src/storage/storage_common.cpp @@ -117,10 +117,10 @@ void storageEraseAll(bool warn) modelDefault(0); if (warn) { - ALERT(STR_EEPROMWARN, STR_BADEEPROMDATA, AU_BAD_EEPROM); + ALERT(STR_STORAGE_WARNING, STR_BAD_RADIO_DATA, AU_BAD_RADIODATA); } - MESSAGE(STR_EEPROMWARN, STR_EEPROMFORMATTING, NULL, AU_EEPROM_FORMATTING); + MESSAGE(STR_STORAGE_WARNING, STR_STORAGE_FORMAT, NULL, AU_STORAGE_FORMAT); storageFormat(); storageDirty(EE_GENERAL); diff --git a/radio/src/strhelpers.cpp b/radio/src/strhelpers.cpp index bd11c1e02..74b7fdfe4 100644 --- a/radio/src/strhelpers.cpp +++ b/radio/src/strhelpers.cpp @@ -161,8 +161,7 @@ char *strcat_zchar(char * dest, const char * name, uint8_t size, const char *def #endif #endif -#if defined(COLORLCD) -char *strAppendDigits(char *dest, int value) +char * strAppendNumber(char * dest, unsigned int value) { div_t qr = div(value, 10); *dest++ = '0' + qr.quot; @@ -170,10 +169,9 @@ char *strAppendDigits(char *dest, int value) *dest = '\0'; return dest; } -#endif #if defined(CPUARM) || defined(SDCARD) -char *strAppend(char *dest, const char *source, int len) +char * strAppend(char * dest, const char * source, int len) { while ((*dest++ = *source++)) { if (--len == 0) { @@ -184,7 +182,7 @@ char *strAppend(char *dest, const char *source, int len) return dest - 1; } -char *strSetCursor(char *dest, int position) +char * strSetCursor(char * dest, int position) { *dest++ = 0x1F; *dest++ = position; @@ -192,7 +190,7 @@ char *strSetCursor(char *dest, int position) return dest; } -char *strAppendFilename(char *dest, const char *filename, const int size) +char * strAppendFilename(char *dest, const char *filename, const int size) { memset(dest, 0, size); for (int i=0; i(size, strlen(filename)); - for (int i=len; i>=len-LEN_FILE_EXTENSION; --i) { - if (filename[i] == '.') { - return &filename[i]; - } - } - return NULL; -} - #if defined(RTCLOCK) #include "rtc.h" diff --git a/radio/src/strhelpers.h b/radio/src/strhelpers.h new file mode 100644 index 000000000..d53033736 --- /dev/null +++ b/radio/src/strhelpers.h @@ -0,0 +1,41 @@ +/* + * Authors (alphabetical order) + * - Andre Bernet + * - Andreas Weitl + * - Bertrand Songis + * - Bryan J. Rentoul (Gruvin) + * - Cameron Weeks + * - Erez Raviv + * - Gabriel Birkus + * - Jean-Pierre Parisy + * - Karl Szmutny + * - Michael Blandford + * - Michal Hlavinka + * - Pat Mackenzie + * - Philip Moss + * - Rob Thomson + * - Romolo Manfredini + * - Thomas Husterer + * + * opentx is based on code named + * gruvin9x by Bryan J. Rentoul: http://code.google.com/p/gruvin9x/, + * er9x by Erez Raviv: http://code.google.com/p/er9x/, + * and the original (and ongoing) project by + * Thomas Husterer, th9x: http://code.google.com/p/th9x/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + * + */ + +char * strAppend(char * dest, const char * source, int len=0); +char * strAppendNumber(char * dest, unsigned int number); +char * strSetCursor(char * dest, int position); +char * strAppendDate(char * str, bool time=false); +char * strAppendFilename(char * dest, const char * filename, const int size); diff --git a/radio/src/targets/Horus/adc_driver.cpp b/radio/src/targets/Horus/adc_driver.cpp index e6f46ff9e..f84eda4e3 100644 --- a/radio/src/targets/Horus/adc_driver.cpp +++ b/radio/src/targets/Horus/adc_driver.cpp @@ -63,6 +63,8 @@ const int8_t ana_direction[NUMBER_ANALOG] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 void adcInit() { + return ; + // Enable clocks RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; RCC->AHB1ENR |= ADC_RCC_AHB1Periph_GPIO | RCC_AHB1ENR_DMA2EN; @@ -93,6 +95,8 @@ void adcInit() void adcRead() { + return ; + DMA2_Stream0->CR &= ~DMA_SxCR_EN ; // Disable DMA ADC1->SR &= ~(uint32_t) ( ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR ) ; DMA2->LIFCR = DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 |DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0 ; // Write ones to clear bits diff --git a/radio/src/targets/Horus/board_horus.cpp b/radio/src/targets/Horus/board_horus.cpp index 5a7e0ef19..ed0d9742e 100644 --- a/radio/src/targets/Horus/board_horus.cpp +++ b/radio/src/targets/Horus/board_horus.cpp @@ -72,7 +72,7 @@ void init5msTimer() { // Timer14 RCC->APB1ENR |= RCC_APB1ENR_TIM14EN ; // Enable clock - TIM14->ARR = 4999 ; // 5mS + TIM14->ARR = 999 ; // 5mS TIM14->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 1000000 - 1 ; // 1uS from 30MHz TIM14->CCER = 0 ; TIM14->CCMR1 = 0 ; @@ -95,13 +95,18 @@ void interrupt5ms() { static uint32_t pre_scale ; // Used to get 10 Hz counter - AUDIO_HEARTBEAT(); + ++pre_scale; + + if (pre_scale == 5 || pre_scale == 10) { + AUDIO_HEARTBEAT(); #if defined(HAPTIC) - HAPTIC_HEARTBEAT(); + HAPTIC_HEARTBEAT(); #endif - if ( ++pre_scale >= 2 ) { + } + + if ( pre_scale >= 10 ) { pre_scale = 0 ; per10ms(); } @@ -244,13 +249,3 @@ void checkTrainerSettings() } } } - -// TODO -bool eeCopyModel(uint8_t dst, uint8_t src) { } -void eeSwapModels(uint8_t id1, uint8_t id2) { } -void eeDeleteModel(uint8_t idx) { } -uint8_t eeFindEmptyModel(uint8_t id, bool down) { return 0; }; -bool eeModelExists(uint8_t id) { return true; } -const pm_char * eeBackupModel(uint8_t i_fileSrc) { return NULL; } -const pm_char * eeRestoreModel(uint8_t i_fileDst, char *model_name) { return NULL; } -void selectModel(uint8_t sub) { } diff --git a/radio/src/targets/Horus/board_horus.h b/radio/src/targets/Horus/board_horus.h index 4aa3cc449..90298f462 100644 --- a/radio/src/targets/Horus/board_horus.h +++ b/radio/src/targets/Horus/board_horus.h @@ -104,15 +104,9 @@ extern "C" { #define PIN_PORTA 0x0000 #define PIN_PORTB 0x0100 #define PIN_PORTC 0x0200 -#define PIN_PORTD 0x0300 -#define PIN_PORTE 0x0400 -#define PIN_PORTF 0x0500 #define PIN_PER_1 0x0010 #define PIN_PER_2 0x0020 #define PIN_PER_3 0x0030 -#define PIN_PER_5 0x0050 -#define PIN_PER_6 0x0060 -#define PIN_PER_8 0x0080 #define PIN_OS25 0x2000 #define PIN_OS50 0x4000 #define PIN_OS100 0x6000 @@ -241,7 +235,7 @@ void pwrOn(void); void pwrOff(void); uint32_t pwrPressed(void); uint32_t pwrPressedDuration(void); -#define UNEXPECTED_SHUTDOWN() (g_eeGeneral.unexpectedShutdown) +#define UNEXPECTED_SHUTDOWN() (false) // (g_eeGeneral.unexpectedShutdown) // Led driver void ledInit(void); @@ -253,6 +247,8 @@ void ledBlue(void); void lcdInit(void); void lcdRefresh(void); void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); +void lcdStoreBackupBuffer(void); +void lcdRestoreBackupBuffer(void); // Backlight driver #define setBacklight(xx) diff --git a/radio/src/targets/Horus/diskio.cpp b/radio/src/targets/Horus/diskio.cpp index 49b948c71..046e31430 100644 --- a/radio/src/targets/Horus/diskio.cpp +++ b/radio/src/targets/Horus/diskio.cpp @@ -17,8 +17,7 @@ /*-----------------------------------------------------------------------*/ #if !defined(BOOT) static OS_MutexID ioMutex; -volatile int mutexCheck = 0; - +unsigned int ioMutexReq = 0, ioMutexRel = 0; int ff_cre_syncobj (BYTE vol, _SYNC_t *mutex) { *mutex = ioMutex; @@ -27,11 +26,13 @@ int ff_cre_syncobj (BYTE vol, _SYNC_t *mutex) int ff_req_grant (_SYNC_t mutex) { + ioMutexReq += 1; return CoEnterMutexSection(mutex) == E_OK; } void ff_rel_grant (_SYNC_t mutex) { + ioMutexRel += 1; CoLeaveMutexSection(mutex); } @@ -87,7 +88,7 @@ DSTATUS disk_status ( return(stat); } - +unsigned int sdReadRetries = 0; /*-----------------------------------------------------------------------*/ /* Read Sector(s) */ @@ -99,56 +100,78 @@ DRESULT disk_read ( UINT count /* Number of sectors to read (1..255) */ ) { - SD_Error Status; + DRESULT res = RES_OK; + SD_Error Status; - // TRACE("disk_read %d %p %10d %d\n",drv,buff,sector,count); + // TRACE("disk_read %d %p %10d %d", drv, buff, sector, count); - if (SD_Detect() != SD_PRESENT) - return(RES_NOTRDY); + if (SD_Detect() != SD_PRESENT) { + TRACE("SD_Detect() != SD_PRESENT"); + return RES_NOTRDY; + } - if ((DWORD)buff & 3) // DMA Alignment failure, do single up to aligned buffer - { - DRESULT res = RES_OK; - DWORD scratch[BLOCK_SIZE / 4]; // Alignment assured, you'll need a sufficiently big stack + if ((DWORD)buff & 3) // DMA Alignment failure, do single up to aligned buffer + { + TRACE("DMA Alignment failure, align buffer"); - while(count--) - { - res = disk_read(drv, (BYTE *)scratch, sector++, 1); + DWORD scratch[BLOCK_SIZE / 4]; // Alignment assured, you'll need a sufficiently big stack - if (res != RES_OK) { - TRACE("disk_read() status=%d", res); - break; - } + while(count--) { + res = disk_read(drv, (BYTE *)scratch, sector++, 1); - memcpy(buff, scratch, BLOCK_SIZE); + if (res != RES_OK) { + TRACE("disk_read() status=%d", res); + break; + } - buff += BLOCK_SIZE; - } + memcpy(buff, scratch, BLOCK_SIZE); - return(res); - } + buff += BLOCK_SIZE; + } - Status = SD_ReadMultiBlocksFIXED(buff, sector, BLOCK_SIZE, count); // 4GB Compliant + return res; + } - if (Status == SD_OK) - { - SDTransferState State; + for (int retry=0; retry<3; retry++) { + res = RES_OK; - Status = SD_WaitReadOperation(); // Check if the Transfer is finished + if (count == 1) { + Status = SD_ReadBlock(buff, sector, BLOCK_SIZE); // 4GB Compliant + } + else { + Status = SD_ReadMultiBlocks(buff, sector, BLOCK_SIZE, count); // 4GB Compliant + } - while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) +#if defined(SD_DMA_MODE) + if (Status == SD_OK) { + SDTransferState State; - if ((State == SD_TRANSFER_ERROR) || (Status != SD_OK)) { - TRACE("SD_ReadMultiBlocksFIXED() wait error (State=%d Status=%d)", State, Status); - return(RES_ERROR); - } - else - return(RES_OK); - } - else { - TRACE("SD_ReadMultiBlocksFIXED() error (Status=%d)", Status); - return(RES_ERROR); - } + Status = SD_WaitReadOperation(); // Check if the Transfer is finished + + while ((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) + + if (State == SD_TRANSFER_ERROR) { + TRACE("State=SD_TRANSFER_ERROR"); + res = RES_ERROR; + } + else if (Status != SD_OK) { + TRACE("Status(WaitRead)=%d", Status); + res = RES_ERROR; + } + } + else { + TRACE("Status(ReadBlock)=%d", Status); + res = RES_ERROR; + } +#endif + + if (res == RES_OK) + break; + + sdReadRetries += 1; + } + + return res; } /*-----------------------------------------------------------------------*/ @@ -163,15 +186,15 @@ DRESULT disk_write ( ) { SD_Error Status; + DRESULT res = RES_OK; - // TRACE("disk_write %d %p %10d %d\n",drv,buff,sector,count); + // TRACE("disk_write %d %p %10d %d", drv, buff, sector, count); if (SD_Detect() != SD_PRESENT) return(RES_NOTRDY); if ((DWORD)buff & 3) // DMA Alignment failure, do single up to aligned buffer { - DRESULT res = RES_OK; DWORD scratch[BLOCK_SIZE / 4]; // Alignment assured, you'll need a sufficiently big stack while(count--) @@ -181,7 +204,7 @@ DRESULT disk_write ( res = disk_write(drv, (BYTE *)scratch, sector++, 1); if (res != RES_OK) - break; + break; buff += BLOCK_SIZE; } @@ -189,7 +212,12 @@ DRESULT disk_write ( return(res); } - Status = SD_WriteMultiBlocksFIXED((uint8_t *)buff, sector, BLOCK_SIZE, count); // 4GB Compliant + if (count == 1) { + Status = SD_WriteBlock((uint8_t *)buff, sector*BLOCK_SIZE, BLOCK_SIZE); // 4GB Compliant + } + else { + Status = SD_WriteMultiBlocks((uint8_t *)buff, sector, BLOCK_SIZE, count); // 4GB Compliant + } if (Status == SD_OK) { @@ -200,12 +228,14 @@ DRESULT disk_write ( while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) if ((State == SD_TRANSFER_ERROR) || (Status != SD_OK)) - return(RES_ERROR); - else - return(RES_OK); + res = RES_ERROR; } - else - return(RES_ERROR); + else { + res = RES_ERROR; + } + + // TRACE("result=%d", res); + return res; } #endif /* _READONLY */ @@ -221,7 +251,25 @@ DRESULT disk_ioctl ( void *buff /* Buffer to send/receive control data */ ) { - return RES_OK; + DRESULT res; + + if (drv) return RES_PARERR; + + res = RES_ERROR; + + switch (ctrl) { + case GET_SECTOR_COUNT : /* Get number of sectors on the disk (DWORD) */ + *(DWORD*)buff = SDCardInfo.CardCapacity / BLOCK_SIZE; + res = RES_OK; + break; + + case GET_SECTOR_SIZE : /* Get R/W sector size (WORD) */ + *(WORD*)buff = BLOCK_SIZE; + res = RES_OK; + break; + } + + return res; } // TODO everything here should not be in the driver layer ... @@ -231,15 +279,6 @@ FATFS g_FATFS_Obj; FIL g_telemetryFile = {0}; #endif -#if defined(BOOT) -void sdInit(void) -{ - if (f_mount(&g_FATFS_Obj, "", 1) == FR_OK) { - f_chdir("/"); - } -} -#else -// TODO shouldn't be there! void sdInit(void) { ioMutex = CoCreateMutex(); @@ -276,7 +315,6 @@ void sdDone() f_mount(NULL, "", 0); // unmount SD } } -#endif uint32_t sdMounted() { diff --git a/radio/src/targets/Horus/hal.h b/radio/src/targets/Horus/hal.h index 30b5a8f7a..096a4fec7 100644 --- a/radio/src/targets/Horus/hal.h +++ b/radio/src/targets/Horus/hal.h @@ -54,22 +54,22 @@ #define SWITCHES_GPIO_PIN_H GPIO_Pin_7 // PG.07 // Trims -#define TRIMS_GPIO_REG_LHL GPIOC->IDR -#define TRIMS_GPIO_PIN_LHL GPIO_Pin_0 // PC.00 -#define TRIMS_GPIO_REG_LHR GPIOI->IDR -#define TRIMS_GPIO_PIN_LHR GPIO_Pin_4 // PI.04 -#define TRIMS_GPIO_REG_LVD GPIOG->IDR -#define TRIMS_GPIO_PIN_LVD GPIO_Pin_12 // PG.12 -#define TRIMS_GPIO_REG_LVU GPIOJ->IDR -#define TRIMS_GPIO_PIN_LVU GPIO_Pin_14 // PJ.14 -#define TRIMS_GPIO_REG_RVD GPIOJ->IDR -#define TRIMS_GPIO_PIN_RVD GPIO_Pin_13 // PJ.13 -#define TRIMS_GPIO_REG_RHL GPIOD->IDR -#define TRIMS_GPIO_PIN_RHL GPIO_Pin_3 // PD.03 +#define TRIMS_GPIO_REG_RHL GPIOC->IDR +#define TRIMS_GPIO_PIN_RHL GPIO_Pin_0 // PC.00 +#define TRIMS_GPIO_REG_RHR GPIOI->IDR +#define TRIMS_GPIO_PIN_RHR GPIO_Pin_4 // PI.04 +#define TRIMS_GPIO_REG_RVD GPIOG->IDR +#define TRIMS_GPIO_PIN_RVD GPIO_Pin_12 // PG.12 #define TRIMS_GPIO_REG_RVU GPIOJ->IDR -#define TRIMS_GPIO_PIN_RVU GPIO_Pin_12 // PJ.12 -#define TRIMS_GPIO_REG_RHR GPIOD->IDR -#define TRIMS_GPIO_PIN_RHR GPIO_Pin_7 // PD.07 +#define TRIMS_GPIO_PIN_RVU GPIO_Pin_14 // PJ.14 +#define TRIMS_GPIO_REG_LVD GPIOJ->IDR +#define TRIMS_GPIO_PIN_LVD GPIO_Pin_13 // PJ.13 +#define TRIMS_GPIO_REG_LHL GPIOD->IDR +#define TRIMS_GPIO_PIN_LHL GPIO_Pin_3 // PD.03 +#define TRIMS_GPIO_REG_LVU GPIOJ->IDR +#define TRIMS_GPIO_PIN_LVU GPIO_Pin_12 // PJ.12 +#define TRIMS_GPIO_REG_LHR GPIOD->IDR +#define TRIMS_GPIO_PIN_LHR GPIO_Pin_7 // PD.07 #define TRIMS_GPIO_REG_RSD GPIOJ->IDR #define TRIMS_GPIO_PIN_RSD GPIO_Pin_8 // PJ.08 #define TRIMS_GPIO_REG_RSU GPIOD->IDR @@ -81,13 +81,13 @@ // Index of all keys #define KEYS_GPIOB_PINS (SWITCHES_GPIO_PIN_B_L | SWITCHES_GPIO_PIN_C_H | TRIMS_GPIO_PIN_LSD | TRIMS_GPIO_PIN_LSU) -#define KEYS_GPIOC_PINS (KEYS_GPIO_PIN_MENU | KEYS_GPIO_PIN_ENTER | KEYS_GPIO_PIN_RIGHT | TRIMS_GPIO_PIN_LHL) -#define KEYS_GPIOD_PINS (SWITCHES_GPIO_PIN_C_L | TRIMS_GPIO_PIN_RHL | TRIMS_GPIO_PIN_RHR | TRIMS_GPIO_PIN_RSU) +#define KEYS_GPIOC_PINS (KEYS_GPIO_PIN_MENU | KEYS_GPIO_PIN_ENTER | KEYS_GPIO_PIN_RIGHT | TRIMS_GPIO_PIN_RHL) +#define KEYS_GPIOD_PINS (SWITCHES_GPIO_PIN_C_L | TRIMS_GPIO_PIN_LHL | TRIMS_GPIO_PIN_LHR | TRIMS_GPIO_PIN_RSU) #define KEYS_GPIOE_PINS (SWITCHES_GPIO_PIN_E_L) -#define KEYS_GPIOG_PINS (KEYS_GPIO_PIN_UP | SWITCHES_GPIO_PIN_D_L | SWITCHES_GPIO_PIN_G_H | SWITCHES_GPIO_PIN_G_L | SWITCHES_GPIO_PIN_H | TRIMS_GPIO_PIN_LVD) +#define KEYS_GPIOG_PINS (KEYS_GPIO_PIN_UP | SWITCHES_GPIO_PIN_D_L | SWITCHES_GPIO_PIN_G_H | SWITCHES_GPIO_PIN_G_L | SWITCHES_GPIO_PIN_H | TRIMS_GPIO_PIN_RVD) #define KEYS_GPIOH_PINS (SWITCHES_GPIO_PIN_A_L | SWITCHES_GPIO_PIN_B_H | SWITCHES_GPIO_PIN_E_H | SWITCHES_GPIO_PIN_F | ENC_GPIO_PIN_A | ENC_GPIO_PIN_B) -#define KEYS_GPIOI_PINS (KEYS_GPIO_PIN_EXIT | KEYS_GPIO_PIN_LEFT | KEYS_GPIO_PIN_DOWN | SWITCHES_GPIO_PIN_A_H | TRIMS_GPIO_PIN_LHR) -#define KEYS_GPIOJ_PINS (SWITCHES_GPIO_PIN_D_H | TRIMS_GPIO_PIN_LVU | TRIMS_GPIO_PIN_RVD | TRIMS_GPIO_PIN_RVU | TRIMS_GPIO_PIN_RSD) +#define KEYS_GPIOI_PINS (KEYS_GPIO_PIN_EXIT | KEYS_GPIO_PIN_LEFT | KEYS_GPIO_PIN_DOWN | SWITCHES_GPIO_PIN_A_H | TRIMS_GPIO_PIN_RHR) +#define KEYS_GPIOJ_PINS (SWITCHES_GPIO_PIN_D_H | TRIMS_GPIO_PIN_RVU | TRIMS_GPIO_PIN_LVD | TRIMS_GPIO_PIN_LVU | TRIMS_GPIO_PIN_RSD) // ADC #define ADC_RCC_AHB1Periph_GPIO (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC) diff --git a/radio/src/targets/Horus/lcd_driver.cpp b/radio/src/targets/Horus/lcd_driver.cpp index 0303e2f34..945c51b07 100644 --- a/radio/src/targets/Horus/lcd_driver.cpp +++ b/radio/src/targets/Horus/lcd_driver.cpp @@ -9,22 +9,20 @@ #define HFP 3 #define VFP 2 -#define LCD_PIXEL_WIDTH ((uint16_t)480) -#define LCD_PIXEL_HEIGHT ((uint16_t)272) +#define LCD_DIR_HORIZONTAL 0x0000 +#define LCD_DIR_VERTICAL 0x0001 -#define LCD_DIR_HORIZONTAL 0x0000 -#define LCD_DIR_VERTICAL 0x0001 +#define LCD_FIRST_LAYER 0 +#define LCD_SECOND_LAYER 1 -#define LCD_BACKGROUND_LAYER 0x0000 -#define LCD_FOREGROUND_LAYER 0x0001 +#define LCD_BKLIGHT_PWM_FREQ 300 -#define LCD_BKLIGHT_PWM_FREQ 300 +#define LCD_FIRST_FRAME_BUFFER SDRAM_BANK_ADDR +#define LCD_SECOND_FRAME_BUFFER (SDRAM_BANK_ADDR + DISPLAY_BUFFER_SIZE) +#define LCD_BACKUP_FRAME_BUFFER (SDRAM_BANK_ADDR + 2*DISPLAY_BUFFER_SIZE) -#define LCD_DEFAULT_FRAME_BUFFER SDRAM_BANK_ADDR -#define FRAME_BUFFER_OFFSET ((uint32_t)0x100000) - -uint32_t CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER; -uint32_t CurrentLayer = LCD_BACKGROUND_LAYER; +uint32_t CurrentFrameBuffer = LCD_FIRST_FRAME_BUFFER; +uint32_t CurrentLayer = LCD_FIRST_LAYER; #define NRST_LOW() do { LCD_GPIO_NRST->BSRRH = LCD_GPIO_PIN_NRST; } while(0) #define NRST_HIGH() do { LCD_GPIO_NRST->BSRRL = LCD_GPIO_PIN_NRST; } while(0) @@ -193,13 +191,13 @@ void LCD_Init_LTDC(void) /* Configure accumulated vertical back porch */ LTDC_InitStruct.LTDC_AccumulatedVBP = VBP; /* Configure accumulated active width */ - LTDC_InitStruct.LTDC_AccumulatedActiveW = LCD_PIXEL_WIDTH + HBP; + LTDC_InitStruct.LTDC_AccumulatedActiveW = LCD_W + HBP; /* Configure accumulated active height */ - LTDC_InitStruct.LTDC_AccumulatedActiveH = LCD_PIXEL_HEIGHT + VBP; + LTDC_InitStruct.LTDC_AccumulatedActiveH = LCD_H + VBP; /* Configure total width */ - LTDC_InitStruct.LTDC_TotalWidth = LCD_PIXEL_WIDTH + HBP + HFP; + LTDC_InitStruct.LTDC_TotalWidth = LCD_W + HBP + HFP; /* Configure total height */ - LTDC_InitStruct.LTDC_TotalHeigh = LCD_PIXEL_HEIGHT + VBP + VFP; + LTDC_InitStruct.LTDC_TotalHeigh = LCD_H + VBP + VFP; // init ltdc LTDC_Init(<DC_InitStruct); @@ -240,9 +238,9 @@ void LCD_LayerInit() Vertical start = vertical synchronization + vertical back porch = 4 Vertical stop = Vertical start + window height -1 = 4 + 320 -1 */ LTDC_Layer_InitStruct.LTDC_HorizontalStart = HBP + 1; - LTDC_Layer_InitStruct.LTDC_HorizontalStop = (LCD_PIXEL_WIDTH + HBP); + LTDC_Layer_InitStruct.LTDC_HorizontalStop = (LCD_W + HBP); LTDC_Layer_InitStruct.LTDC_VerticalStart = VBP + 1;; - LTDC_Layer_InitStruct.LTDC_VerticalStop = (LCD_PIXEL_HEIGHT + VBP); + LTDC_Layer_InitStruct.LTDC_VerticalStop = (LCD_H + VBP); /* Pixel Format configuration*/ LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_RGB565; @@ -260,20 +258,20 @@ void LCD_LayerInit() /* the length of one line of pixels in bytes + 3 then : Line Lenth = Active high width x number of bytes per pixel + 3 - Active high width = LCD_PIXEL_WIDTH + Active high width = LCD_W number of bytes per pixel = 2 (pixel_format : RGB565) */ - LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((LCD_PIXEL_WIDTH * 2) + 3); + LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((LCD_W * 2) + 3); /* the pitch is the increment from the start of one line of pixels to the start of the next line in bytes, then : Pitch = Active high width x number of bytes per pixel */ - LTDC_Layer_InitStruct.LTDC_CFBPitch = (LCD_PIXEL_WIDTH * 2); + LTDC_Layer_InitStruct.LTDC_CFBPitch = (LCD_W * 2); /* Configure the number of lines */ - LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_PIXEL_HEIGHT; + LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_H; /* Start Address configuration : the LCD Frame buffer is defined on SDRAM w/ Offset */ - LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_DEFAULT_FRAME_BUFFER; + LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_FIRST_FRAME_BUFFER; /* Initialize LTDC layer 1 */ LTDC_LayerInit(LTDC_Layer1, <DC_Layer_InitStruct); @@ -283,7 +281,7 @@ void LCD_LayerInit() LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_PAxCA; /* Start Address configuration : the LCD Frame buffer is defined on SDRAM w/ Offset */ - LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_DEFAULT_FRAME_BUFFER + FRAME_BUFFER_OFFSET; + LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_FIRST_FRAME_BUFFER + DISPLAY_BUFFER_SIZE; /* Initialize LTDC layer 2 */ LTDC_LayerInit(LTDC_Layer2, <DC_Layer_InitStruct); @@ -377,25 +375,21 @@ void LCD_Init(void) */ void LCD_SetLayer(uint32_t Layerx) { - if (Layerx == LCD_BACKGROUND_LAYER) + if (Layerx == LCD_FIRST_LAYER) { - CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER; - CurrentLayer = LCD_BACKGROUND_LAYER; + CurrentFrameBuffer = LCD_FIRST_FRAME_BUFFER; + CurrentLayer = LCD_FIRST_LAYER; } else { - CurrentFrameBuffer = LCD_DEFAULT_FRAME_BUFFER + FRAME_BUFFER_OFFSET; - CurrentLayer = LCD_FOREGROUND_LAYER; + CurrentFrameBuffer = LCD_FIRST_FRAME_BUFFER + DISPLAY_BUFFER_SIZE; + CurrentLayer = LCD_SECOND_LAYER; } } void LCD_Clear(uint16_t Color) { - uint16_t *index ; - - /* erase memory */ - for (index = (uint16_t *)CurrentFrameBuffer; index < (uint16_t *)(CurrentFrameBuffer+(FRAME_BUFFER_OFFSET)); index++) - { + for (uint16_t * index = (uint16_t *)CurrentFrameBuffer; index < (uint16_t *)(CurrentFrameBuffer+DISPLAY_BUFFER_SIZE); index++) { *index = Color; } } @@ -408,12 +402,10 @@ void LCD_Clear(uint16_t Color) */ void LCD_SetTransparency(uint8_t transparency) { - if (CurrentLayer == LCD_BACKGROUND_LAYER) - { + if (CurrentLayer == LCD_FIRST_LAYER) { LTDC_LayerAlpha(LTDC_Layer1, transparency); } - else - { + else { LTDC_LayerAlpha(LTDC_Layer2, transparency); } LTDC_ReloadConfig(LTDC_IMReload); @@ -429,19 +421,19 @@ void lcdInit(void) LTDC_Cmd(ENABLE); /* Set Background layer */ - LCD_SetLayer(LCD_BACKGROUND_LAYER); + LCD_SetLayer(LCD_FIRST_LAYER); LCD_Clear(0); LCD_SetTransparency(0); /* Set Foreground layer */ - LCD_SetLayer(LCD_FOREGROUND_LAYER); + LCD_SetLayer(LCD_SECOND_LAYER); LCD_Clear(0); LCD_SetTransparency(255); } void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { - uint32_t addr = CurrentFrameBuffer + 2*(LCD_PIXEL_WIDTH*y + x); + uint32_t addr = CurrentFrameBuffer + 2*(LCD_W*y + x); uint16_t red = (0xF800 & color) >> 11; uint16_t blue = 0x001F & color; uint16_t green = (0x07E0 & color) >> 5; @@ -456,7 +448,7 @@ void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, u DMA2D_InitStruct.DMA2D_OutputRed = red; DMA2D_InitStruct.DMA2D_OutputAlpha = 0x0F; DMA2D_InitStruct.DMA2D_OutputMemoryAdd = addr; - DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_PIXEL_WIDTH - w); + DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_W - w); DMA2D_InitStruct.DMA2D_NumberOfLine = h; DMA2D_InitStruct.DMA2D_PixelPerLine = w; DMA2D_Init(&DMA2D_InitStruct); @@ -468,12 +460,26 @@ void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, u while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); } +void lcdStoreBackupBuffer() +{ + for (uint16_t * src = (uint16_t *)CurrentFrameBuffer, * dest = (uint16_t *)LCD_BACKUP_FRAME_BUFFER; src < (uint16_t *)(CurrentFrameBuffer+DISPLAY_BUFFER_SIZE); src++, dest++) { + *dest = *src; + } +} + +void lcdRestoreBackupBuffer() +{ + for (uint16_t * dest = (uint16_t *)CurrentFrameBuffer, * src = (uint16_t *)LCD_BACKUP_FRAME_BUFFER; dest < (uint16_t *)(CurrentFrameBuffer+DISPLAY_BUFFER_SIZE); src++, dest++) { + *dest = *src; + } +} + void lcdRefresh() { LCD_SetTransparency(255); - if (CurrentLayer == LCD_BACKGROUND_LAYER) - LCD_SetLayer(LCD_FOREGROUND_LAYER); + if (CurrentLayer == LCD_FIRST_LAYER) + LCD_SetLayer(LCD_SECOND_LAYER); else - LCD_SetLayer(LCD_BACKGROUND_LAYER); + LCD_SetLayer(LCD_FIRST_LAYER); LCD_SetTransparency(0); } diff --git a/radio/src/targets/Horus/sdio_sd.c b/radio/src/targets/Horus/sdio_sd.c index 8749bc62f..9691c7e1c 100644 --- a/radio/src/targets/Horus/sdio_sd.c +++ b/radio/src/targets/Horus/sdio_sd.c @@ -331,21 +331,6 @@ */ #define SDIO_SEND_IF_COND ((uint32_t)0x00000008) -/** - * @} - */ - -/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Macros - * @{ - */ -/** - * @} - */ - -/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Variables - * @{ - */ - static uint32_t CardType = SDIO_STD_CAPACITY_SD_CARD_V1_1; static uint32_t CSD_Tab[4], CID_Tab[4], RCA = 0; static uint8_t SDSTATUS_Tab[16]; @@ -357,14 +342,7 @@ SD_CardInfo SDCardInfo; SDIO_InitTypeDef SDIO_InitStructure; SDIO_CmdInitTypeDef SDIO_CmdInitStructure; SDIO_DataInitTypeDef SDIO_DataInitStructure; -/** - * @} - */ - -/** @defgroup STM32F4_DISCOVERY_SDIO_SD_Private_Function_Prototypes - * @{ - */ static SD_Error CmdError(void); static SD_Error CmdResp1Error(uint8_t cmd); static SD_Error CmdResp7Error(void); @@ -440,7 +418,7 @@ void SD_LowLevel_Init(void) /* Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; @@ -498,7 +476,7 @@ void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize) SDDMA_InitStructure.DMA_PeripheralBaseAddr = SD_SDIO_FIFO_ADDRESS; SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferSRC; SDDMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; - SDDMA_InitStructure.DMA_BufferSize = 0; + SDDMA_InitStructure.DMA_BufferSize = 1; SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; @@ -540,7 +518,7 @@ void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize) SDDMA_InitStructure.DMA_PeripheralBaseAddr = SD_SDIO_FIFO_ADDRESS; SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferDST; SDDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; - SDDMA_InitStructure.DMA_BufferSize = 0; + SDDMA_InitStructure.DMA_BufferSize = 1; SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; @@ -712,13 +690,17 @@ uint8_t SD_Detect(void) return status; } +//--------------------------------------------------------------------------------------- +//disable optimize +#define OPTIMIZE(level) __attribute__((optimize(level))) + /** * @brief Enquires cards about their operating voltage and configures * clock controls. * @param None * @retval SD_Error: SD Card Error code. */ -SD_Error SD_PowerON(void) +OPTIMIZE("O0") SD_Error SD_PowerON(void) { __IO SD_Error errorstatus = SD_OK; uint32_t response = 0, count = 0, validvoltage = 0; @@ -865,7 +847,7 @@ SD_Error SD_PowerON(void) * @param None * @retval SD_Error: SD Card Error code. */ -SD_Error SD_PowerOFF(void) +OPTIMIZE("O0") SD_Error SD_PowerOFF(void) { SD_Error errorstatus = SD_OK; @@ -881,7 +863,7 @@ SD_Error SD_PowerOFF(void) * @param None * @retval SD_Error: SD Card Error code. */ -SD_Error SD_InitializeCards(void) +OPTIMIZE("O0") SD_Error SD_InitializeCards(void) { SD_Error errorstatus = SD_OK; uint16_t rca = 0x01; @@ -970,7 +952,7 @@ SD_Error SD_InitializeCards(void) * information. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo) +OPTIMIZE("O0") SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo) { SD_Error errorstatus = SD_OK; uint8_t tmp = 0; @@ -1061,7 +1043,7 @@ SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo) /*!< Byte 10 */ tmp = (uint8_t)((CSD_Tab[2] & 0x0000FF00) >> 8); - cardinfo->CardCapacity = (uint64_t)(cardinfo->SD_csd.DeviceSize + 1) * (uint64_t)(512 * 1024); + cardinfo->CardCapacity = ((uint64_t)cardinfo->SD_csd.DeviceSize + 1) * 512 * 1024; cardinfo->CardBlockSize = 512; } @@ -1182,7 +1164,7 @@ SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo) * @arg SDIO_BusWide_1b: 1-bit data transfer * @retval SD_Error: SD Card Error code. */ -SD_Error SD_GetCardStatus(SD_CardStatus *cardstatus) +OPTIMIZE("O0") SD_Error SD_GetCardStatus(SD_CardStatus *cardstatus) { SD_Error errorstatus = SD_OK; uint8_t tmp = 0; @@ -1267,7 +1249,7 @@ SD_Error SD_GetCardStatus(SD_CardStatus *cardstatus) * @arg SDIO_BusWide_1b: 1-bit data transfer * @retval SD_Error: SD Card Error code. */ -SD_Error SD_EnableWideBusOperation(uint32_t WideMode) +OPTIMIZE("O0") SD_Error SD_EnableWideBusOperation(uint32_t WideMode) { SD_Error errorstatus = SD_OK; @@ -1326,7 +1308,7 @@ SD_Error SD_EnableWideBusOperation(uint32_t WideMode) * @param addr: Address of the Card to be selected. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_SelectDeselect(uint32_t addr) +OPTIMIZE("O0") SD_Error SD_SelectDeselect(uint32_t addr) { SD_Error errorstatus = SD_OK; @@ -1357,7 +1339,7 @@ SD_Error SD_SelectDeselect(uint32_t addr) * @param BlockSize: the SD card Data block size. The Block size should be 512. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize) +OPTIMIZE("O0") SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize) { SD_Error errorstatus = SD_OK; #if defined (SD_POLLING_MODE) @@ -1367,13 +1349,21 @@ SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize) TransferError = SD_OK; TransferEnd = 0; StopCondition = 0; + DMAEndOfTransfer = 0; SDIO->DCTRL = 0x0; - if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) - { +#if defined (SD_DMA_MODE) + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SDIO_DMACmd(ENABLE); + SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize); +#endif + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { BlockSize = 512; - ReadAddr /= 512; + } + else { + ReadAddr *= 512; } /* Set Block Size for Card */ @@ -1464,10 +1454,6 @@ SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize) /*!< Clear all the static flags */ SDIO_ClearFlag(SDIO_STATIC_FLAGS); -#elif defined (SD_DMA_MODE) - SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); - SDIO_DMACmd(ENABLE); - SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize); #endif return(errorstatus); @@ -1488,19 +1474,25 @@ SD_Error SD_ReadBlock(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize) * @param NumberOfBlocks: number of blocks to be read. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks) +OPTIMIZE("O0") SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint32_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks) { SD_Error errorstatus = SD_OK; TransferError = SD_OK; TransferEnd = 0; StopCondition = 1; + DMAEndOfTransfer = 0; SDIO->DCTRL = 0x0; - if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) - { + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, (NumberOfBlocks * BlockSize)); + SDIO_DMACmd(ENABLE); + + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { BlockSize = 512; - ReadAddr /= 512; + } + else { + ReadAddr *= 512; } /*!< Set Block Size for Card */ @@ -1541,71 +1533,6 @@ SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint32_t ReadAddr, uint16_t Block return(errorstatus); } - SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); - SDIO_DMACmd(ENABLE); - SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, (NumberOfBlocks * BlockSize)); - - return(errorstatus); -} - -// FIXED Version where ReadAddr is in BLOCKS NOT BYTES, permits SDHC media >4GB - -SD_Error SD_ReadMultiBlocksFIXED(uint8_t *readbuff, uint32_t ReadAddr, uint32_t BlockSize, uint32_t NumberOfBlocks) -{ - SD_Error errorstatus = SD_OK; - TransferError = SD_OK; - TransferEnd = 0; - StopCondition = 1; - - SDIO->DCTRL = 0x0; - - if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) - BlockSize = 512; - else - ReadAddr *= BlockSize; // Convert to Bytes for NON SDHC - - /*!< Set Block Size for Card */ - SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; - SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; - SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; - SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; - SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; - SDIO_SendCommand(&SDIO_CmdInitStructure); - - errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); - - if (SD_OK != errorstatus) - { - return(errorstatus); - } - - SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; - SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize; - SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; - SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO; - SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; - SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; - SDIO_DataConfig(&SDIO_DataInitStructure); - - /*!< Send CMD18 READ_MULT_BLOCK with argument data address */ - SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr; - SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_MULT_BLOCK; - SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; - SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; - SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; - SDIO_SendCommand(&SDIO_CmdInitStructure); - - errorstatus = CmdResp1Error(SD_CMD_READ_MULT_BLOCK); - - if (errorstatus != SD_OK) - { - return(errorstatus); - } - - SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); - SDIO_DMACmd(ENABLE); - SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, (NumberOfBlocks * BlockSize)); - return(errorstatus); } @@ -1617,7 +1544,7 @@ SD_Error SD_ReadMultiBlocksFIXED(uint8_t *readbuff, uint32_t ReadAddr, uint32_t * @param None. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_WaitReadOperation(void) +OPTIMIZE("O0") SD_Error SD_WaitReadOperation(void) { SD_Error errorstatus = SD_OK; uint32_t timeout; @@ -1641,6 +1568,7 @@ SD_Error SD_WaitReadOperation(void) if (StopCondition == 1) { errorstatus = SD_StopTransfer(); + StopCondition = 0; } if ((timeout == 0) && (errorstatus == SD_OK)) @@ -1675,7 +1603,7 @@ SD_Error SD_WaitReadOperation(void) * @param BlockSize: the SD card Data block size. The Block size should be 512. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize) +OPTIMIZE("O0") SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize) { SD_Error errorstatus = SD_OK; @@ -1690,6 +1618,11 @@ SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSiz SDIO->DCTRL = 0x0; +#if defined (SD_DMA_MODE) + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); + SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, BlockSize); + SDIO_DMACmd(ENABLE); +#endif if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { @@ -1784,10 +1717,6 @@ SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSiz errorstatus = SD_START_BIT_ERR; return(errorstatus); } -#elif defined (SD_DMA_MODE) - SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); - SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, BlockSize); - SDIO_DMACmd(ENABLE); #endif return(errorstatus); @@ -1808,7 +1737,7 @@ SD_Error SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSiz * @param NumberOfBlocks: number of blocks to be written. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks) +OPTIMIZE("O0") SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks) { SD_Error errorstatus = SD_OK; @@ -1818,6 +1747,10 @@ SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t Bl SDIO->DCTRL = 0x0; + SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_TXUNDERR | SDIO_IT_STBITERR, ENABLE); + SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize)); + SDIO_DMACmd(ENABLE); + if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { BlockSize = 512; @@ -1893,105 +1826,9 @@ SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t Bl SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; SDIO_DataConfig(&SDIO_DataInitStructure); - SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); - SDIO_DMACmd(ENABLE); - SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize)); - return(errorstatus); } -// FIXED Version where WriteAddr is in BLOCKS NOT BYTES, permits SDHC media >4GB - -SD_Error SD_WriteMultiBlocksFIXED(uint8_t *writebuff, uint32_t WriteAddr, uint32_t BlockSize, uint32_t NumberOfBlocks) -{ - SD_Error errorstatus = SD_OK; - - TransferError = SD_OK; - TransferEnd = 0; - StopCondition = 1; - - SDIO->DCTRL = 0x0; - - if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) - BlockSize = 512; - else - WriteAddr *= BlockSize; // Convert to Bytes for NON SDHC - - /* Set Block Size for Card */ - SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; - SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; - SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; - SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; - SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; - SDIO_SendCommand(&SDIO_CmdInitStructure); - - errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); - - if (SD_OK != errorstatus) - { - return(errorstatus); - } - - /*!< To improve performance */ - SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) (RCA << 16); - SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; - SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; - SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; - SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; - SDIO_SendCommand(&SDIO_CmdInitStructure); - - - errorstatus = CmdResp1Error(SD_CMD_APP_CMD); - - if (errorstatus != SD_OK) - { - return(errorstatus); - } - /*!< To improve performance */ - SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)NumberOfBlocks; - SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCK_COUNT; - SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; - SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; - SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; - SDIO_SendCommand(&SDIO_CmdInitStructure); - - errorstatus = CmdResp1Error(SD_CMD_SET_BLOCK_COUNT); - - if (errorstatus != SD_OK) - { - return(errorstatus); - } - - - /*!< Send CMD25 WRITE_MULT_BLOCK with argument data address */ - SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr; - SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_MULT_BLOCK; - SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; - SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; - SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; - SDIO_SendCommand(&SDIO_CmdInitStructure); - - errorstatus = CmdResp1Error(SD_CMD_WRITE_MULT_BLOCK); - - if (SD_OK != errorstatus) - { - return(errorstatus); - } - - SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; - SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize; - SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; - SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard; - SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block; - SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; - SDIO_DataConfig(&SDIO_DataInitStructure); - - SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE); - SDIO_DMACmd(ENABLE); - SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize)); - - return(errorstatus); -} /** * @brief This function waits until the SDIO DMA data transfer is finished. @@ -2001,7 +1838,7 @@ SD_Error SD_WriteMultiBlocksFIXED(uint8_t *writebuff, uint32_t WriteAddr, uint32 * @param None. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_WaitWriteOperation(void) +OPTIMIZE("O0") SD_Error SD_WaitWriteOperation(void) { SD_Error errorstatus = SD_OK; uint32_t timeout; @@ -2025,6 +1862,7 @@ SD_Error SD_WaitWriteOperation(void) if (StopCondition == 1) { errorstatus = SD_StopTransfer(); + StopCondition = 0; } if ((timeout == 0) && (errorstatus == SD_OK)) @@ -2053,7 +1891,7 @@ SD_Error SD_WaitWriteOperation(void) * - SD_TRANSFER_OK: No data transfer is acting * - SD_TRANSFER_BUSY: Data transfer is acting */ -SDTransferState SD_GetTransferState(void) +OPTIMIZE("O0") SDTransferState SD_GetTransferState(void) { if (SDIO->STA & (SDIO_FLAG_TXACT | SDIO_FLAG_RXACT)) { @@ -2070,7 +1908,7 @@ SDTransferState SD_GetTransferState(void) * @param None * @retval SD_Error: SD Card Error code. */ -SD_Error SD_StopTransfer(void) +OPTIMIZE("O0") SD_Error SD_StopTransfer(void) { SD_Error errorstatus = SD_OK; @@ -2093,7 +1931,7 @@ SD_Error SD_StopTransfer(void) * @param endaddr: the end address. * @retval SD_Error: SD Card Error code. */ -SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr) +OPTIMIZE("O0") SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr) { SD_Error errorstatus = SD_OK; uint32_t delay = 0; @@ -2189,7 +2027,7 @@ SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr) * status (Card Status register). * @retval SD_Error: SD Card Error code. */ -SD_Error SD_SendStatus(uint32_t *pcardstatus) +OPTIMIZE("O0") SD_Error SD_SendStatus(uint32_t *pcardstatus) { SD_Error errorstatus = SD_OK; @@ -2224,7 +2062,7 @@ SD_Error SD_SendStatus(uint32_t *pcardstatus) * (SD Status register). * @retval SD_Error: SD Card Error code. */ -SD_Error SD_SendSDStatus(uint32_t *psdstatus) +OPTIMIZE("O0") SD_Error SD_SendSDStatus(uint32_t *psdstatus) { SD_Error errorstatus = SD_OK; uint32_t count = 0; @@ -2341,7 +2179,7 @@ SD_Error SD_SendSDStatus(uint32_t *psdstatus) * @param None * @retval SD_Error: SD Card Error code. */ -SD_Error SD_ProcessIRQ(void) +OPTIMIZE("O0") SD_Error SD_ProcessIRQ(void) { if (SDIO_GetITStatus(SDIO_IT_DATAEND) != RESET) { @@ -2386,7 +2224,7 @@ SD_Error SD_ProcessIRQ(void) * @param None. * @retval None. */ -void SD_ProcessDMAIRQ(void) +OPTIMIZE("O0") void SD_ProcessDMAIRQ(void) { if(DMA2->LISR & SD_SDIO_DMA_FLAG_TCIF) { @@ -2400,7 +2238,7 @@ void SD_ProcessDMAIRQ(void) * @param None * @retval SD_Error: SD Card Error code. */ -static SD_Error CmdError(void) +OPTIMIZE("O0") static SD_Error CmdError(void) { SD_Error errorstatus = SD_OK; uint32_t timeout; @@ -2429,7 +2267,7 @@ static SD_Error CmdError(void) * @param None * @retval SD_Error: SD Card Error code. */ -static SD_Error CmdResp7Error(void) +OPTIMIZE("O0") static SD_Error CmdResp7Error(void) { SD_Error errorstatus = SD_OK; uint32_t status; @@ -2466,7 +2304,7 @@ static SD_Error CmdResp7Error(void) * @param cmd: The sent command index. * @retval SD_Error: SD Card Error code. */ -static SD_Error CmdResp1Error(uint8_t cmd) +OPTIMIZE("O0") static SD_Error CmdResp1Error(uint8_t cmd) { SD_Error errorstatus = SD_OK; uint32_t status; @@ -2612,7 +2450,7 @@ static SD_Error CmdResp1Error(uint8_t cmd) * @param None * @retval SD_Error: SD Card Error code. */ -static SD_Error CmdResp3Error(void) +OPTIMIZE("O0") static SD_Error CmdResp3Error(void) { SD_Error errorstatus = SD_OK; uint32_t status; @@ -2640,7 +2478,7 @@ static SD_Error CmdResp3Error(void) * @param None * @retval SD_Error: SD Card Error code. */ -static SD_Error CmdResp2Error(void) +OPTIMIZE("O0") static SD_Error CmdResp2Error(void) { SD_Error errorstatus = SD_OK; uint32_t status; @@ -2678,7 +2516,7 @@ static SD_Error CmdResp2Error(void) * address RCA. * @retval SD_Error: SD Card Error code. */ -static SD_Error CmdResp6Error(uint8_t cmd, uint16_t *prca) +OPTIMIZE("O0") static SD_Error CmdResp6Error(uint8_t cmd, uint16_t *prca) { SD_Error errorstatus = SD_OK; uint32_t status; @@ -2747,7 +2585,7 @@ static SD_Error CmdResp6Error(uint8_t cmd, uint16_t *prca) * This parameter can be: ENABLE or DISABLE. * @retval SD_Error: SD Card Error code. */ -static SD_Error SDEnWideBus(FunctionalState NewState) +OPTIMIZE("O0") static SD_Error SDEnWideBus(FunctionalState NewState) { SD_Error errorstatus = SD_OK; @@ -2861,7 +2699,7 @@ static SD_Error SDEnWideBus(FunctionalState NewState) * @param pstatus: pointer to the variable that will contain the SD card state. * @retval SD_Error: SD Card Error code. */ -static SD_Error IsCardProgramming(uint8_t *pstatus) +OPTIMIZE("O0") static SD_Error IsCardProgramming(uint8_t *pstatus) { SD_Error errorstatus = SD_OK; __IO uint32_t respR1 = 0, status = 0; @@ -3020,7 +2858,7 @@ static SD_Error IsCardProgramming(uint8_t *pstatus) * @param pscr: pointer to the buffer that will contain the SCR value. * @retval SD_Error: SD Card Error code. */ -static SD_Error FindSCR(uint16_t rca, uint32_t *pscr) +OPTIMIZE("O0") static SD_Error FindSCR(uint16_t rca, uint32_t *pscr) { uint32_t index = 0; SD_Error errorstatus = SD_OK; @@ -3129,7 +2967,7 @@ static SD_Error FindSCR(uint16_t rca, uint32_t *pscr) * @param NumberOfBytes: number of bytes. * @retval None */ -uint8_t convert_from_bytes_to_power_of_two(uint16_t NumberOfBytes) +OPTIMIZE("O0") uint8_t convert_from_bytes_to_power_of_two(uint16_t NumberOfBytes) { uint8_t count = 0; diff --git a/radio/src/targets/Horus/sdio_sd.h b/radio/src/targets/Horus/sdio_sd.h index 5d3671b78..8e69d25d4 100644 --- a/radio/src/targets/Horus/sdio_sd.h +++ b/radio/src/targets/Horus/sdio_sd.h @@ -386,6 +386,8 @@ SD_Error SD_WaitWriteOperation(void); } #endif +extern SD_CardInfo SDCardInfo; + #endif /* __STM32F4_DISCOVERY_SDIO_SD_H */ /** * @} diff --git a/radio/src/targets/Horus/usb_conf.h b/radio/src/targets/Horus/usb_conf.h index c285e607d..7e02211c4 100644 --- a/radio/src/targets/Horus/usb_conf.h +++ b/radio/src/targets/Horus/usb_conf.h @@ -43,7 +43,7 @@ * when FS core is used. *******************************************************************************/ #ifndef USE_USB_OTG_FS - #define USE_USB_OTG_FS + #define USE_USB_OTG_FS // USB2.0 Full Speed == 12 Mbit #endif /* USE_USB_OTG_FS */ #ifdef USE_USB_OTG_FS diff --git a/radio/src/targets/Horus/usbd_desc.c b/radio/src/targets/Horus/usbd_desc.c index 541200a16..63187d67b 100644 --- a/radio/src/targets/Horus/usbd_desc.c +++ b/radio/src/targets/Horus/usbd_desc.c @@ -65,22 +65,22 @@ #if defined(BOOT) #define USBD_PID 0x5720 - #define USBD_PRODUCT_FS_STRING "FrSky Taranis Bootloader" + #define USBD_PRODUCT_FS_STRING "FrSky Horus Bootloader" #define USBD_CONFIGURATION_FS_STRING "MSC Config" #define USBD_INTERFACE_FS_STRING "MSC Interface" #elif defined(USB_JOYSTICK) #define USBD_PID 0x5710 - #define USBD_PRODUCT_FS_STRING "FrSky Taranis Joystick" + #define USBD_PRODUCT_FS_STRING "FrSky Horus Joystick" #define USBD_CONFIGURATION_FS_STRING "HID Config" #define USBD_INTERFACE_FS_STRING "HID Interface" #elif defined(USB_SERIAL) #define USBD_PID 0x5740 // do not change, this ID is used by the ST USB driver for Windows - #define USBD_PRODUCT_FS_STRING "FrSky Taranis Serial Port" + #define USBD_PRODUCT_FS_STRING "FrSky Horus Serial Port" #define USBD_CONFIGURATION_FS_STRING "VSP Config" #define USBD_INTERFACE_FS_STRING "VSP Interface" #elif defined(USB_MASS_STORAGE) #define USBD_PID 0x5720 - #define USBD_PRODUCT_FS_STRING "FrSky Taranis Mass Storage" + #define USBD_PRODUCT_FS_STRING "FrSky Horus Mass Storage" #define USBD_CONFIGURATION_FS_STRING "MSC Config" #define USBD_INTERFACE_FS_STRING "MSC Interface" #endif diff --git a/radio/src/targets/Horus/usbd_desc.h b/radio/src/targets/Horus/usbd_desc.h index 7a9d5b564..537f69b14 100644 --- a/radio/src/targets/Horus/usbd_desc.h +++ b/radio/src/targets/Horus/usbd_desc.h @@ -31,7 +31,8 @@ #define __USB_DESC_H /* Includes ------------------------------------------------------------------*/ -#include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Core/inc/usbd_def.h" +#include "usb_core.h" +#include "usbd_def.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/targets/Horus/usbd_storage_msd.cpp b/radio/src/targets/Horus/usbd_storage_msd.cpp index 5c7c2df1d..62cef6c19 100644 --- a/radio/src/targets/Horus/usbd_storage_msd.cpp +++ b/radio/src/targets/Horus/usbd_storage_msd.cpp @@ -191,20 +191,23 @@ int8_t STORAGE_Read (uint8_t lun, uint32_t blk_addr, uint16_t blk_len) { - if (SD_ReadMultiBlocksFIXED(buf, blk_addr, BLOCKSIZE, blk_len) != 0) { - return -1; + + for (int retry=0; retry<3; retry++) { + if (SD_ReadMultiBlocks(buf, blk_addr, BLOCKSIZE, blk_len) != 0) { + continue; + } + + SDTransferState State; + + SD_Error Status = SD_WaitReadOperation(); // Check if the Transfer is finished + + while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) + + if ((State == SD_TRANSFER_OK) && (Status == SD_OK)) { + return 0; + } } - - SDTransferState State; - - SD_Error Status = SD_WaitReadOperation(); // Check if the Transfer is finished - - while((State = SD_GetStatus()) == SD_TRANSFER_BUSY); // BUSY, OK (DONE), ERROR (FAIL) - - if ((State == SD_TRANSFER_ERROR) || (Status != SD_OK)) - return -1; - - return 0; + return -1; } /** * @brief Write data to the medium @@ -220,7 +223,7 @@ int8_t STORAGE_Write (uint8_t lun, uint32_t blk_addr, uint16_t blk_len) { - if (SD_WriteMultiBlocksFIXED(buf, blk_addr, BLOCKSIZE, blk_len) != 0) + if (SD_WriteMultiBlocks(buf, blk_addr, BLOCKSIZE, blk_len) != 0) return -1; SDTransferState State; diff --git a/radio/src/targets/Taranis/diskio.cpp b/radio/src/targets/Taranis/diskio.cpp index b91501cd5..d7e111b28 100644 --- a/radio/src/targets/Taranis/diskio.cpp +++ b/radio/src/targets/Taranis/diskio.cpp @@ -81,7 +81,6 @@ /*-----------------------------------------------------------------------*/ #if !defined(BOOT) static OS_MutexID ioMutex; -volatile int mutexCheck = 0; int ff_cre_syncobj (BYTE vol, _SYNC_t *mutex) { diff --git a/radio/src/targets/Taranis/usb_bsp.c b/radio/src/targets/Taranis/usb_bsp.c index 37bc90703..8d29546c1 100644 --- a/radio/src/targets/Taranis/usb_bsp.c +++ b/radio/src/targets/Taranis/usb_bsp.c @@ -30,8 +30,8 @@ #include "usb_bsp.h" -#include "../Taranis/board_taranis.h" -#include "../Taranis/usbd_conf.h" +#include "board_taranis.h" +#include "usbd_conf.h" extern uint32_t SystemCoreClock; diff --git a/radio/src/targets/Taranis/usb_driver.c b/radio/src/targets/Taranis/usb_driver.c index aff638720..9233962b1 100644 --- a/radio/src/targets/Taranis/usb_driver.c +++ b/radio/src/targets/Taranis/usb_driver.c @@ -34,7 +34,7 @@ * */ -#include "../Taranis/board_taranis.h" +#include "board_taranis.h" #include "STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_OTG_Driver/inc/usb_dcd_int.h" int usbPlugged(void) diff --git a/radio/src/targets/Taranis/usbd_cdc.cpp b/radio/src/targets/Taranis/usbd_cdc.cpp index fec1e02d7..7b1a44634 100644 --- a/radio/src/targets/Taranis/usbd_cdc.cpp +++ b/radio/src/targets/Taranis/usbd_cdc.cpp @@ -34,7 +34,7 @@ extern "C" { /* Includes ------------------------------------------------------------------*/ -#include "../Taranis/usb_conf.h" +#include "usb_conf.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ diff --git a/radio/src/targets/Taranis/usbd_conf.h b/radio/src/targets/Taranis/usbd_conf.h index f797f4ce1..557973a26 100644 --- a/radio/src/targets/Taranis/usbd_conf.h +++ b/radio/src/targets/Taranis/usbd_conf.h @@ -30,7 +30,7 @@ #define __USBD_CONF__H__ /* Includes ------------------------------------------------------------------*/ -#include "../Taranis/usb_conf.h" +#include "usb_conf.h" #define USBD_CFG_MAX_NUM 1 #define USBD_ITF_MAX_NUM 1 diff --git a/radio/src/targets/Taranis/usbd_desc.c b/radio/src/targets/Taranis/usbd_desc.c index 303c04779..2c2011255 100644 --- a/radio/src/targets/Taranis/usbd_desc.c +++ b/radio/src/targets/Taranis/usbd_desc.c @@ -27,12 +27,12 @@ /* Includes ------------------------------------------------------------------*/ #include -#include "usbd_desc.h" -#include "board_taranis.h" -#include "usbd_conf.h" #include "usbd_core.h" +#include "usbd_desc.h" #include "usbd_req.h" +#include "usbd_conf.h" #include "usb_regs.h" +#include "board_taranis.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/targets/Taranis/usbd_hid_joystick.c b/radio/src/targets/Taranis/usbd_hid_joystick.c index ed6706e5e..33a6c3d85 100644 --- a/radio/src/targets/Taranis/usbd_hid_joystick.c +++ b/radio/src/targets/Taranis/usbd_hid_joystick.c @@ -47,9 +47,9 @@ */ /* Includes ------------------------------------------------------------------*/ -#include "../Taranis/usbd_desc.h" #include "usbd_hid_core.h" #include "usbd_req.h" +#include "usbd_desc.h" /** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY * @{ diff --git a/radio/src/targets/Taranis/usbd_storage_msd.cpp b/radio/src/targets/Taranis/usbd_storage_msd.cpp index 1a4904ca0..51cccc788 100644 --- a/radio/src/targets/Taranis/usbd_storage_msd.cpp +++ b/radio/src/targets/Taranis/usbd_storage_msd.cpp @@ -28,14 +28,14 @@ /* Includes ------------------------------------------------------------------*/ #include "../../opentx.h" #include "../../thirdparty/FatFs/diskio.h" -#include "../Taranis/board_taranis.h" +#include "board_taranis.h" #if defined(__cplusplus) && !defined(SIMU) extern "C" { #endif #include "usbd_msc_mem.h" -#include "../Taranis/usb_conf.h" +#include "usb_conf.h" #if defined(BOOT) #define STORAGE_LUN_NBR 2 diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 548dfc9ed..276da05ac 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -716,7 +716,7 @@ namespace simu { FATFS g_FATFS_Obj; #endif -char *convertSimuPath(const char *path) +char * convertSimuPath(const char *path) { static char result[1024]; if (path[0] == '/' && strcmp(simuSdDirectory, "/") != 0) @@ -975,11 +975,15 @@ FRESULT f_unlink (const TCHAR * name) FRESULT f_rename(const TCHAR *oldname, const TCHAR *newname) { - if (rename(oldname, newname) < 0) { - TRACE("f_rename(%s, %s) = error %d (%s)", oldname, newname, errno, strerror(errno)); + char old[1024]; + strcpy(old, convertSimuPath(oldname)); + char * path = convertSimuPath(newname); + + if (rename(old, path) < 0) { + TRACE("f_rename(%s, %s) = error %d (%s)", old, path, errno, strerror(errno)); return FR_INVALID_NAME; } - TRACE("f_rename(%s, %s) = OK", oldname, newname); + TRACE("f_rename(%s, %s) = OK", old, path); return FR_OK; } @@ -1226,8 +1230,8 @@ uint32_t sdGetSpeed() #endif // #if defined(SIMU_DISKIO) -bool lcd_refresh = true; -display_t lcd_buf[DISPLAY_BUFFER_SIZE]; +bool simuLcdRefresh = true; +display_t simuLcdBuf[DISPLAY_BUFFER_SIZE]; #if !defined(PCBFLAMENCO) && !defined(PCBHORUS) void lcdSetRefVolt(uint8_t val) @@ -1251,8 +1255,19 @@ void lcdRefresh() TW8823_SendScreen(); #endif - memcpy(lcd_buf, displayBuf, sizeof(lcd_buf)); - lcd_refresh = true; + memcpy(simuLcdBuf, displayBuf, sizeof(simuLcdBuf)); + simuLcdRefresh = true; +} + +display_t simuLcdBackupBuf[DISPLAY_BUFFER_SIZE]; +void lcdStoreBackupBuffer() +{ + memcpy(simuLcdBackupBuf, displayBuf, sizeof(simuLcdBackupBuf)); +} + +void lcdRestoreBackupBuffer() +{ + memcpy(displayBuf, simuLcdBackupBuf, sizeof(displayBuf)); } #if defined(PCBTARANIS) || defined(PCBFLAMENCO) || defined(PCBHORUS) diff --git a/radio/src/tasks_arm.cpp b/radio/src/tasks_arm.cpp index 502336dc4..640dca234 100644 --- a/radio/src/tasks_arm.cpp +++ b/radio/src/tasks_arm.cpp @@ -52,7 +52,7 @@ OS_TID menusTaskId; TaskStack _ALIGNED(8) menusStack; OS_TID mixerTaskId; -TaskStack mixerStack; +TaskStack mixerStack; OS_TID audioTaskId; TaskStack audioStack; @@ -235,4 +235,4 @@ void tasksStart() CoStartOS(); } -#endif // #if !defined(SIMU) +#endif // !defined(SIMU) diff --git a/radio/src/tests/lcd.cpp b/radio/src/tests/lcd.cpp index 05561b83a..2042de3a6 100644 --- a/radio/src/tests/lcd.cpp +++ b/radio/src/tests/lcd.cpp @@ -72,11 +72,11 @@ void doPaint(QPainter & p) #endif for (int x=0; x> 4) : (lcd_buf[idx] & 0x0F); + unsigned int z = (y & 1) ? (simuLcdBuf[idx] >> 4) : (simuLcdBuf[idx] & 0x0F); if (z) { if (z != previousDepth) { previousDepth = z; diff --git a/radio/src/translations.cpp b/radio/src/translations.cpp index e87f6b2cc..e5e3325d3 100644 --- a/radio/src/translations.cpp +++ b/radio/src/translations.cpp @@ -298,18 +298,27 @@ const pm_char STR_CH[] PROGMEM = TR_CH; const pm_char STR_MODEL[] PROGMEM = TR_MODEL; const pm_char STR_FP[] PROGMEM = TR_FP; const pm_char STR_MIX[] PROGMEM = TR_MIX; -const pm_char STR_EEPROMLOWMEM[] PROGMEM = TR_EEPROMLOWMEM; const pm_char STR_ALERT[] PROGMEM = TR_ALERT; const pm_char STR_PRESSANYKEYTOSKIP[] PROGMEM = TR_PRESSANYKEYTOSKIP; const pm_char STR_THROTTLENOTIDLE[] PROGMEM = TR_THROTTLENOTIDLE; const pm_char STR_ALARMSDISABLED[] PROGMEM = TR_ALARMSDISABLED; const pm_char STR_PRESSANYKEY[] PROGMEM = TR_PRESSANYKEY; -const pm_char STR_BADEEPROMDATA[] PROGMEM = TR_BADEEPROMDATA; -const pm_char STR_EEPROMFORMATTING[] PROGMEM = TR_EEPROMFORMATTING; -const pm_char STR_EEPROMOVERFLOW[] PROGMEM = TR_EEPROMOVERFLOW; const pm_char STR_TRIMS2OFFSETS[] PROGMEM = TR_TRIMS2OFFSETS; const pm_char STR_MENURADIOSETUP[] PROGMEM = TR_MENURADIOSETUP; +#if defined(EEPROM) +const pm_char STR_BAD_RADIO_DATA[] PROGMEM = TR_BADEEPROMDATA; +const pm_char STR_STORAGE_FORMAT[] PROGMEM = TR_EEPROMFORMATTING; +const pm_char STR_STORAGE_WARNING[] PROGMEM = TR_EEPROMWARN; +const pm_char STR_EEPROM_CONVERTING[] PROGMEM = TR_EEPROM_CONVERTING; +const pm_char STR_EEPROMLOWMEM[] PROGMEM = TR_EEPROMLOWMEM; +const pm_char STR_EEPROMOVERFLOW[] PROGMEM = TR_EEPROMOVERFLOW; +#else +const pm_char STR_BAD_RADIO_DATA[] PROGMEM = TR_BAD_RADIO_DATA; +const pm_char STR_STORAGE_WARNING[] PROGMEM = TR_STORAGE_WARNING; +const pm_char STR_STORAGE_FORMAT[] PROGMEM = TR_STORAGE_FORMAT; +#endif + #if defined(FAS_OFFSET) || !defined(CPUM64) const pm_char STR_FAS_OFFSET[] PROGMEM = TR_FAS_OFFSET; #endif @@ -416,6 +425,7 @@ const pm_char STR_PERSISTENT_MAH[] PROGMEM = TR_PERSISTENT_MAH; #if defined(NAVIGATION_MENUS) const pm_char STR_SELECT_MODEL[] PROGMEM = TR_SELECT_MODEL; const pm_char STR_CREATE_MODEL[] PROGMEM = TR_CREATE_MODEL; +const pm_char STR_DUPLICATE_MODEL[] PROGMEM = TR_DUPLICATE_MODEL; const pm_char STR_COPY_MODEL[] PROGMEM = TR_COPY_MODEL; const pm_char STR_MOVE_MODEL[] PROGMEM = TR_MOVE_MODEL; const pm_char STR_DELETE_MODEL[] PROGMEM = TR_DELETE_MODEL; @@ -452,8 +462,6 @@ const pm_char STR_MODELS_EXT[] PROGMEM = MODELS_EXT; #endif const pm_char STR_WARNING[] PROGMEM = TR_WARNING; -const pm_char STR_EEPROMWARN[] PROGMEM = TR_EEPROMWARN; -const pm_char STR_EEPROM_CONVERTING[] PROGMEM = TR_EEPROM_CONVERTING; const pm_char STR_THROTTLEWARN[] PROGMEM = TR_THROTTLEWARN; const pm_char STR_ALARMSWARN[] PROGMEM = TR_ALARMSWARN; const pm_char STR_SWITCHWARN[] PROGMEM = TR_SWITCHWARN; diff --git a/radio/src/translations.h b/radio/src/translations.h index dd1978b89..8f26c28f5 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -500,8 +500,8 @@ extern const pm_char STR_THROTTLENOTIDLE[]; extern const pm_char STR_ALARMSDISABLED[]; extern const pm_char STR_PRESSANYKEY[]; #define LEN_PRESSANYKEY PSIZE(TR_PRESSANYKEY) -extern const pm_char STR_BADEEPROMDATA[]; -extern const pm_char STR_EEPROMFORMATTING[]; +extern const pm_char STR_BAD_RADIO_DATA[]; +extern const pm_char STR_STORAGE_FORMAT[]; extern const pm_char STR_EEPROMOVERFLOW[]; extern const pm_char STR_TRIMS2OFFSETS[]; extern const pm_char STR_MENURADIOSETUP[]; @@ -598,6 +598,7 @@ extern const pm_char STR_CURRENT[]; #if defined(NAVIGATION_MENUS) extern const pm_char STR_SELECT_MODEL[]; extern const pm_char STR_CREATE_MODEL[]; +extern const pm_char STR_DUPLICATE_MODEL[]; extern const pm_char STR_COPY_MODEL[]; extern const pm_char STR_MOVE_MODEL[]; extern const pm_char STR_DELETE_MODEL[]; @@ -635,7 +636,7 @@ extern const pm_char STR_RESET_BTN[]; #endif extern const pm_char STR_WARNING[]; -extern const pm_char STR_EEPROMWARN[]; +extern const pm_char STR_STORAGE_WARNING[]; extern const pm_char STR_EEPROM_CONVERTING[]; extern const pm_char STR_THROTTLEWARN[]; extern const pm_char STR_ALARMSWARN[]; diff --git a/radio/src/translations/cz.h.txt b/radio/src/translations/cz.h.txt index 99bcdabc5..bb1a20ff2 100644 --- a/radio/src/translations/cz.h.txt +++ b/radio/src/translations/cz.h.txt @@ -806,6 +806,7 @@ #define TR_CURRENT INDENT"Proud" #define TR_SELECT_MODEL "Vyber model" #define TR_CREATE_MODEL "Nový model" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Kopírovat" #define TR_MOVE_MODEL "Přesunout" #define TR_BACKUP_MODEL "Zálohovat na SD" diff --git a/radio/src/translations/de.h.txt b/radio/src/translations/de.h.txt index 57a540434..7e85e1bfe 100644 --- a/radio/src/translations/de.h.txt +++ b/radio/src/translations/de.h.txt @@ -810,6 +810,7 @@ #define TR_CURRENT TR(INDENT "Strom", INDENT "Stromquelle") #define TR_SELECT_MODEL "Modell Wählen" #define TR_CREATE_MODEL "Neues Modell" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Kopiere Modell" #define TR_MOVE_MODEL "Verschiebe Modell" #define TR_BACKUP_MODEL "Modell auf SD-Karte" //9XR-Pro diff --git a/radio/src/translations/en.h.txt b/radio/src/translations/en.h.txt index 796994624..ef07841c0 100644 --- a/radio/src/translations/en.h.txt +++ b/radio/src/translations/en.h.txt @@ -760,7 +760,9 @@ #define TR_ALARMSDISABLED "Alarms Disabled" #define TR_PRESSANYKEY TR("\010Press any Key", "Press any Key") #define TR_BADEEPROMDATA "Bad EEprom Data" +#define TR_BAD_RADIO_DATA "Bad Radio Data" #define TR_EEPROMFORMATTING "Formatting EEPROM" +#define TR_STORAGE_FORMAT "Storage Preparation" #define TR_EEPROMOVERFLOW "EEPROM overflow" #define TR_MENURADIOSETUP "RADIO SETUP" #define TR_MENUDATEANDTIME "DATE AND TIME" @@ -824,6 +826,7 @@ #define TR_CURRENT TR(INDENT "Current",INDENT "Current Source") #define TR_SELECT_MODEL "Select Model" #define TR_CREATE_MODEL "Create Model" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Copy Model" #define TR_MOVE_MODEL "Move Model" #define TR_BACKUP_MODEL "Backup Model" @@ -835,6 +838,7 @@ #define TR_INCOMPATIBLE "Incompatible" #define TR_WARNING "WARNING" #define TR_EEPROMWARN "EEPROM" +#define TR_STORAGE_WARNING "STORAGE" #define TR_EEPROM_CONVERTING "Converting EEPROM" #define TR_THROTTLEWARN "THROTTLE" #define TR_ALARMSWARN "ALARMS" diff --git a/radio/src/translations/es.h.txt b/radio/src/translations/es.h.txt index 6defc6b88..e187fc233 100644 --- a/radio/src/translations/es.h.txt +++ b/radio/src/translations/es.h.txt @@ -760,6 +760,7 @@ #define TR_CURRENT INDENT"Actual" #define TR_SELECT_MODEL "Selec Modelo" #define TR_CREATE_MODEL "Crear Modelo" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Copiar Modelo" #define TR_MOVE_MODEL "Mover Modelo" #define TR_BACKUP_MODEL "Copia Sgdad Mod." diff --git a/radio/src/translations/fi.h.txt b/radio/src/translations/fi.h.txt index 553a14321..b4029f9a8 100644 --- a/radio/src/translations/fi.h.txt +++ b/radio/src/translations/fi.h.txt @@ -760,6 +760,7 @@ #define TR_CURRENT INDENT"Current" #define TR_SELECT_MODEL "Select Model" #define TR_CREATE_MODEL "Create Model" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Copy Model" #define TR_MOVE_MODEL "Move Model" #define TR_BACKUP_MODEL "Backup Model" diff --git a/radio/src/translations/fr.h.txt b/radio/src/translations/fr.h.txt index e738566d1..b6216fe9a 100644 --- a/radio/src/translations/fr.h.txt +++ b/radio/src/translations/fr.h.txt @@ -797,6 +797,7 @@ #define TR_CURRENT TR(INDENT "Courant",INDENT "Source Courant") #define TR_SELECT_MODEL "Sélect. Modèle" #define TR_CREATE_MODEL "Créer Modèle" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Copier Modèle" #define TR_MOVE_MODEL "Déplacer Modèle" #define TR_BACKUP_MODEL "Archiver Modèle" diff --git a/radio/src/translations/it.h.txt b/radio/src/translations/it.h.txt index 8f02b67a9..3a2c9585a 100644 --- a/radio/src/translations/it.h.txt +++ b/radio/src/translations/it.h.txt @@ -808,6 +808,7 @@ #define TR_CURRENT TR(INDENT "Corrente",INDENT "Corrente") #define TR_SELECT_MODEL "Scegli Memo." #define TR_CREATE_MODEL "Crea Modello" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Copia Modello" #define TR_MOVE_MODEL "Sposta Modello" #define TR_BACKUP_MODEL "Salva Modello" diff --git a/radio/src/translations/nl.h.txt b/radio/src/translations/nl.h.txt index d02f8a7bf..ab4fc3eeb 100644 --- a/radio/src/translations/nl.h.txt +++ b/radio/src/translations/nl.h.txt @@ -827,6 +827,7 @@ #define TR_CURRENT TR(INDENT "Stroom", INDENT "Stroombron") #define TR_SELECT_MODEL "Kies Model" #define TR_CREATE_MODEL "Nieuw Model" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Kopieer Model" #define TR_MOVE_MODEL "Verplaats Model" #define TR_BACKUP_MODEL "Backup Model" diff --git a/radio/src/translations/pl.h.txt b/radio/src/translations/pl.h.txt index 86225bc75..1e62a463b 100644 --- a/radio/src/translations/pl.h.txt +++ b/radio/src/translations/pl.h.txt @@ -809,6 +809,7 @@ #define TR_CURRENT TR (INDENT"Prąd",INDENT"Źródło Prądu") #define TR_SELECT_MODEL "Wybór modelu" #define TR_CREATE_MODEL "Nowy model" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Skopiuj model" #define TR_MOVE_MODEL "Przenieś model" #define TR_BACKUP_MODEL "Zbackupuj model" diff --git a/radio/src/translations/pt.h.txt b/radio/src/translations/pt.h.txt index 45efa69b6..580c1a7cf 100644 --- a/radio/src/translations/pt.h.txt +++ b/radio/src/translations/pt.h.txt @@ -760,6 +760,7 @@ #define TR_CURRENT INDENT"Amperes" #define TR_SELECT_MODEL "Selec. Modelo" #define TR_CREATE_MODEL "Criar Modelo" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Copy Model" #define TR_MOVE_MODEL "Move Model" #define TR_BACKUP_MODEL "Salvar Modelo" diff --git a/radio/src/translations/se.h.txt b/radio/src/translations/se.h.txt index bc5dbff7b..a9f81138b 100644 --- a/radio/src/translations/se.h.txt +++ b/radio/src/translations/se.h.txt @@ -760,6 +760,7 @@ #define TR_CURRENT INDENT"Ampere" #define TR_SELECT_MODEL "Välj Modell" #define TR_CREATE_MODEL "Skapa Modell" +#define TR_DUPLICATE_MODEL "Duplicate Model" #define TR_COPY_MODEL "Kopiera Modell" #define TR_MOVE_MODEL "Flytta Modell" #define TR_BACKUP_MODEL "Modell-backup" diff --git a/radio/util/font2png.py b/radio/util/font2png.py index 47ddec9fd..6faf9f43a 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -86,8 +86,8 @@ def createFontBitmap(filename, fontname, fontsize, fontbold, foreground, backgro f.write("{ ") f.write(",".join(str(tmp) for tmp in coords)) if extraWidth: - for w in (7, 20, 30, 41, 52, 57, 70, 80, 92, 104, 113, 126, 136): - f.write(", %d" % (int(coords[-1])+w)) + for i in range(1, 14): + f.write(", %d" % (int(coords[-1])+i*(extraWidth/12))) # f.write(file("fonts/extra_%dpx.specs" % fontsize).read()) f.write(" }") f.close() diff --git a/radio/util/img2lbm.py b/radio/util/img2lbm.py index f6b92ca98..7d8a152c2 100755 --- a/radio/util/img2lbm.py +++ b/radio/util/img2lbm.py @@ -44,13 +44,14 @@ elif what == "4/4/4/4": pixel = image.pixel(x, y) f.write("0x%1x%1x%1x%1x," % (Qt.qAlpha(pixel)/16, Qt.qRed(pixel)/16, Qt.qGreen(pixel)/16, Qt.qBlue(pixel)/16)) f.write("\n") -elif what == "5/6/5": +elif what == "5/6/5/8": colors = [] - f.write("%d,%d,\n" % (width, height)) + writeSize(f, width, height) for y in range(height): for x in range(width): pixel = image.pixel(x, y) - f.write("0x%04x," % (((Qt.qRed(pixel) >> 3) << 11) + ((Qt.qGreen(pixel) >> 2) << 5) + ((Qt.qBlue(pixel) >> 3) << 0))) + val = ((Qt.qRed(pixel) >> 3) << 11) + ((Qt.qGreen(pixel) >> 2) << 5) + ((Qt.qBlue(pixel) >> 3) << 0) + f.write("%d,%d,%d," % (val%256, val/256, Qt.qAlpha(pixel) >> 4)) f.write("\n") elif what == "4bits": colors = [] From 62e6e30481dff5e2b7420f7fdb55f5890cb8357a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 3 Nov 2015 21:02:28 +0100 Subject: [PATCH 50/74] [Horus] Massstorage working now Still a problem on Windows which doesn't want to format the SD card. Also it seems I am not able to rename the card. It reminds me some issue we already had on Taranis. --- radio/src/targets/Horus/diskio.cpp | 4 ++-- radio/src/targets/Horus/usbd_storage_msd.cpp | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/radio/src/targets/Horus/diskio.cpp b/radio/src/targets/Horus/diskio.cpp index 046e31430..0c5d122e8 100644 --- a/radio/src/targets/Horus/diskio.cpp +++ b/radio/src/targets/Horus/diskio.cpp @@ -259,12 +259,12 @@ DRESULT disk_ioctl ( switch (ctrl) { case GET_SECTOR_COUNT : /* Get number of sectors on the disk (DWORD) */ - *(DWORD*)buff = SDCardInfo.CardCapacity / BLOCK_SIZE; + *(DWORD*)buff = SDCardInfo.CardCapacity / SDCardInfo.CardBlockSize; res = RES_OK; break; case GET_SECTOR_SIZE : /* Get R/W sector size (WORD) */ - *(WORD*)buff = BLOCK_SIZE; + *(WORD*)buff = SDCardInfo.CardBlockSize; res = RES_OK; break; } diff --git a/radio/src/targets/Horus/usbd_storage_msd.cpp b/radio/src/targets/Horus/usbd_storage_msd.cpp index 62cef6c19..fd3fbcd98 100644 --- a/radio/src/targets/Horus/usbd_storage_msd.cpp +++ b/radio/src/targets/Horus/usbd_storage_msd.cpp @@ -131,17 +131,8 @@ int8_t STORAGE_GetCapacity (uint8_t lun, uint32_t *block_num, uint32_t *block_si if (!SD_CARD_PRESENT()) return -1; - *block_size = BLOCKSIZE; - - static DWORD sector_count = 0; - if (sector_count == 0) { - if (disk_ioctl(0, GET_SECTOR_COUNT, §or_count) != RES_OK) { - sector_count = 0; - return -1; - } - } - - *block_num = sector_count; // sector_count; + *block_size = SDCardInfo.CardBlockSize; + *block_num = (SDCardInfo.CardCapacity / SDCardInfo.CardBlockSize) - 1; return 0; } From bca18f5938b88f36f8c12b0f9b6c7fba2551f358 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 3 Nov 2015 21:36:22 +0100 Subject: [PATCH 51/74] Compilation fix --- radio/src/targets/Taranis/bootloader/boot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radio/src/targets/Taranis/bootloader/boot.cpp b/radio/src/targets/Taranis/bootloader/boot.cpp index d0df4469e..f4d57be1f 100644 --- a/radio/src/targets/Taranis/bootloader/boot.cpp +++ b/radio/src/targets/Taranis/bootloader/boot.cpp @@ -53,7 +53,7 @@ #include #include -#include "../../Taranis/board_taranis.h" +#include "../board_taranis.h" #include "storage/eeprom_rlc.h" #include "pwr.h" #include "gui/Taranis/lcd.h" @@ -63,6 +63,7 @@ #include "FatFs/diskio.h" #include "translations/en.h" #include "stamp-opentx.h" +#include "strhelpers.h" #if defined(PCBTARANIS) #define BOOTLOADER_TITLE " Taranis BootLoader - " VERS_STR From b1f5ea115c4b43ef4f916b84ea44fed8bcf8bd58 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 6 Nov 2015 17:03:55 +0100 Subject: [PATCH 52/74] [Horus] ADC driver by Mike + new CLI command repeat --- radio/src/cli.cpp | 28 ++- radio/src/opentx.h | 15 +- radio/src/targets/Horus/adc_driver.cpp | 222 +++++++++++++++++------- radio/src/targets/Horus/board_horus.cpp | 2 +- radio/src/targets/Horus/board_horus.h | 63 +++---- radio/src/targets/Horus/hal.h | 40 ++--- 6 files changed, 252 insertions(+), 118 deletions(-) diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index f217cde9d..a555acf41 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -44,8 +44,12 @@ OS_TID cliTaskId; TaskStack cliStack; Fifo<256> cliRxFifo; uint8_t cliTracesEnabled = true; +// char cliLastLine[CLI_COMMAND_MAX_LEN+1]; typedef int (* CliFunction) (const char ** args); +int cliExecLine(char * line); +int cliExecCommand(const char ** argv); +int cliHelp(const char ** argv); struct CliCommand { @@ -326,7 +330,27 @@ int cliDebugVars(const char ** argv) return 0; } -int cliHelp(const char ** argv); +int cliRepeat(const char ** argv) +{ + int interval = 0; + int counter = 0; + if (toInt(argv, 1, &interval) > 0 && argv[2]) { + interval *= 50; + counter = interval; + uint8_t c; + while (!cliRxFifo.pop(c) || !(c == '\r' || c == '\n' || c == ' ')) { + CoTickDelay(10); // 20ms + if (++counter >= interval) { + cliExecCommand(&argv[2]); + counter = 0; + } + } + } + else { + serialPrint("%s: Invalid arguments", argv[0]); + } + return 0; +} const CliCommand cliCommands[] = { { "beep", cliBeep, "[] []" }, @@ -342,6 +366,7 @@ const CliCommand cliCommands[] = { #endif { "help", cliHelp, "[]" }, { "debugvars", cliDebugVars, "" }, + { "repeat", cliRepeat, " " }, { NULL, NULL, NULL } /* sentinel */ }; @@ -423,6 +448,7 @@ void cliTask(void * pdata) // enter serialCrlf(); line[pos] = '\0'; + // strcpy(cliLastLine, line); cliExecLine(line); pos = 0; cliPrompt(); diff --git a/radio/src/opentx.h b/radio/src/opentx.h index c8bd19a54..9618e564b 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -1005,7 +1005,16 @@ enum Analogs { STICK2, STICK3, STICK4, -#if defined(PCBFLAMENCO) +#if defined(PCBHORUS) + POT1, + POT2, + POT3, + POT_LAST=POT3, + SLIDER1, + SLIDER2, + SLIDER3, + SLIDER4, +#elif defined(PCBFLAMENCO) POT1, POT2, POT_LAST = POT2, @@ -1049,6 +1058,10 @@ enum Analogs { TX_VOLTAGE, #if defined(PCBSKY9X) && !defined(REVA) TX_CURRENT, +#endif +#if defined(PCBHORUS) + MOUSE1, + MOUSE2, #endif NUMBER_ANALOG }; diff --git a/radio/src/targets/Horus/adc_driver.cpp b/radio/src/targets/Horus/adc_driver.cpp index f84eda4e3..fd88831a6 100644 --- a/radio/src/targets/Horus/adc_driver.cpp +++ b/radio/src/targets/Horus/adc_driver.cpp @@ -36,87 +36,189 @@ #include "../../opentx.h" -#define STICK_LV 3 -#define STICK_LH 2 -#define STICK_RV 0 -#define STICK_RH 1 -#define POT_L 13 -#define POT_R 5 -#define SLIDE_L 9 -#define SLIDE_R 8 -#define SWITCHES1 11 -#define SWITCHES2 7 -#define SWITCHES3 10 -#define SWITCHES4 6 -#define BATTERY 14 +#define ADC_CS_HIGH() (ADC_SPI_GPIO->BSRRL = ADC_SPI_PIN_CS) +#define ADC_CS_LOW() (ADC_SPI_GPIO->BSRRH = ADC_SPI_PIN_CS) -// Sample time should exceed 1uS -#define SAMPTIME 2 // sample time = 28 cycles +#define RESETCMD 0x4000 +#define MANUAL_MODE 0x1000 // manual mode channel 0 + +#define SAMPTIME 2 // sample time = 28 cycles uint16_t Analog_values[NUMBER_ANALOG] __DMA; -#define NUMBER_ANALOG_ADC1 13 +static u16 SPIx_ReadWriteByte(uint16_t value) +{ + while(SPI_I2S_GetFlagStatus(ADC_SPI,SPI_I2S_FLAG_TXE) == RESET); + SPI_I2S_SendData(ADC_SPI,value); -#if 0 -const int8_t ana_direction[NUMBER_ANALOG] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; -#endif + while(SPI_I2S_GetFlagStatus(ADC_SPI,SPI_I2S_FLAG_RXNE) ==RESET); + return SPI_I2S_ReceiveData(ADC_SPI); +} + +static void ADS7952_Init() +{ + GPIO_InitTypeDef GPIO_InitStructure; + SPI_InitTypeDef SPI_InitStructure; + + GPIO_InitStructure.GPIO_Pin = ADC_SPI_PIN_MISO|ADC_SPI_PIN_SCK|ADC_SPI_PIN_MOSI; + GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(ADC_SPI_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = ADC_SPI_PIN_CS; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(ADC_SPI_GPIO, &GPIO_InitStructure); + + GPIO_PinAFConfig(ADC_SPI_GPIO, ADC_SPI_PinSource_SCK, ADC_GPIO_AF); + GPIO_PinAFConfig(ADC_SPI_GPIO, ADC_SPI_PinSource_MISO, ADC_GPIO_AF); + GPIO_PinAFConfig(ADC_SPI_GPIO, ADC_SPI_PinSource_MOSI, ADC_GPIO_AF); + + SPI_I2S_DeInit(ADC_SPI); + + SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; + SPI_InitStructure.SPI_Mode = SPI_Mode_Master; + SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; + SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; + SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; + SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; + SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; + SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; + SPI_InitStructure.SPI_CRCPolynomial = 7; + SPI_Init(ADC_SPI,&SPI_InitStructure); + SPI_Cmd(ADC_SPI,ENABLE); + SPI_I2S_ITConfig(ADC_SPI,SPI_I2S_IT_TXE,DISABLE); + SPI_I2S_ITConfig(ADC_SPI,SPI_I2S_IT_RXNE,DISABLE); + + ADC_CS_HIGH(); + delay_01us(1); + ADC_CS_LOW(); + SPIx_ReadWriteByte(RESETCMD); + ADC_CS_HIGH(); + delay_01us(1); + ADC_CS_LOW(); + SPIx_ReadWriteByte(MANUAL_MODE); + ADC_CS_HIGH(); + +// SPIx_ReadWriteByte(ProgramReg_Auto2 ); +// ADC_CS_HIGH(); + +// asm("nop"); +// ADC_CS_LOW(); +// SPIx_ReadWriteByte(AutoMode_2); +// ADC_CS_HIGH(); + +// asm("nop"); +// ADC_CS_LOW(); +// SPIx_ReadWriteByte(ContinuedSelectMode); +// ADC_CS_HIGH(); +// asm("nop"); +} void adcInit() { - return ; + RCC_AHB1PeriphClockCmd(ADC_RCC_AHB1Periph, ENABLE); + RCC_APB2PeriphClockCmd(ADC_RCC_APB2Periph, ENABLE); - // Enable clocks - RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; - RCC->AHB1ENR |= ADC_RCC_AHB1Periph_GPIO | RCC_AHB1ENR_DMA2EN; + ADS7952_Init(); - configure_pins(ADC_GPIO_PIN_STICK_LV | ADC_GPIO_PIN_STICK_LH | ADC_GPIO_PIN_STICK_RV | ADC_GPIO_PIN_STICK_RH | - ADC_GPIO_PIN_POT_R1 | ADC_GPIO_SWITCHES_PIN_R3 | ADC_GPIO_SWITCHES_PIN_R4, PIN_ANALOG | PIN_PORTA); - - configure_pins(ADC_GPIO_PIN_POT_R2 | ADC_GPIO_PIN_POT_L2, PIN_ANALOG | PIN_PORTB); - - configure_pins(ADC_GPIO_PIN_POT_L1 | ADC_GPIO_SWITCHES_PIN_L3 | ADC_GPIO_SWITCHES_PIN_L4 | ADC_GPIO_PIN_BATT, PIN_ANALOG | PIN_PORTC); - - ADC1->CR1 = ADC_CR1_SCAN; - ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_DMA | ADC_CR2_DDS; - ADC1->SQR1 = (BATTERY<<0) + ((NUMBER_ANALOG_ADC1-1) << 20); // bits 23:20 = number of conversions - ADC1->SQR2 = (SLIDE_L<<0) + (SLIDE_R<<5) + (SWITCHES1<<10) + (SWITCHES2<<15) + (SWITCHES3<<20) + (SWITCHES4<<25); // conversions 7 to 12 - ADC1->SQR3 = (STICK_LH<<0) + (STICK_LV<<5) + (STICK_RV<<10) + (STICK_RH<<15) + (POT_L<<20) + (POT_R<<25); // conversions 1 to 6 - ADC1->SMPR1 = SAMPTIME + (SAMPTIME<<3) + (SAMPTIME<<6) + (SAMPTIME<<9) + (SAMPTIME<<12) + (SAMPTIME<<15) + (SAMPTIME<<18) + (SAMPTIME<<21) + (SAMPTIME<<24); - ADC1->SMPR2 = SAMPTIME + (SAMPTIME<<3) + (SAMPTIME<<6) + (SAMPTIME<<9) + (SAMPTIME<<12) + (SAMPTIME<<15) + (SAMPTIME<<18) + (SAMPTIME<<21) + (SAMPTIME<<24) + (SAMPTIME<<27) ; - - ADC->CCR = 0 ; //ADC_CCR_ADCPRE_0 ; // Clock div 2 - - DMA2_Stream0->CR = DMA_SxCR_PL | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC; - DMA2_Stream0->PAR = CONVERT_PTR_UINT(&ADC1->DR); - DMA2_Stream0->M0AR = CONVERT_PTR_UINT(Analog_values); - DMA2_Stream0->NDTR = NUMBER_ANALOG_ADC1; - DMA2_Stream0->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ; + configure_pins( ADC_GPIO_PIN_MOUSE1 | ADC_GPIO_PIN_MOUSE2, PIN_ANALOG | PIN_PORTF ); + + ADC3->CR1 = ADC_CR1_SCAN; + ADC3->CR2 = ADC_CR2_ADON | ADC_CR2_DMA | ADC_CR2_DDS; + ADC3->SQR1 = (2-1) << 20; // NUMBER_ANALOG Channels + ADC3->SQR3 = ADC_IN_MOUSE1 + (ADC_IN_MOUSE2<<5); + ADC3->SMPR1 = SAMPTIME + (SAMPTIME<<3) + (SAMPTIME<<6) + (SAMPTIME<<9) + (SAMPTIME<<12) + + (SAMPTIME<<15) + (SAMPTIME<<18) + (SAMPTIME<<21) + (SAMPTIME<<24); + ADC3->SMPR2 = SAMPTIME + (SAMPTIME<<3) + (SAMPTIME<<6) + (SAMPTIME<<9) + (SAMPTIME<<12) + + (SAMPTIME<<15) + (SAMPTIME<<18) + (SAMPTIME<<21) + (SAMPTIME<<24) + (SAMPTIME<<27); + ADC->CCR = 0; // ADC_CCR_ADCPRE_0; Clock div 2 + + // Enable the DMA channel here, DMA2 stream 1, channel 2 + DMA2_Stream1->CR = DMA_SxCR_PL | DMA_SxCR_CHSEL_1 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC; + DMA2_Stream1->PAR = (uint32_t) &ADC3->DR; + DMA2_Stream1->M0AR = (uint32_t) &Analog_values[MOUSE1]; + DMA2_Stream1->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0; } +#define SPI_STICK1 0 +#define SPI_STICK2 1 +#define SPI_STICK3 2 +#define SPI_STICK4 3 +#define SPI_POT1 4 +#define SPI_POT2 5 +#define SPI_POT3 6 +#define SPI_SLIDER1 7 +#define SPI_SLIDER2 8 +#define SPI_TX_VOLTAGE 9 +#define SPI_SLIDER3 10 +#define SPI_SLIDER4 11 + +const uint16_t adcCommands[MOUSE1+2] = +{ + MANUAL_MODE | ( SPI_STICK1 << 7 ), + MANUAL_MODE | ( SPI_STICK2 << 7 ), + MANUAL_MODE | ( SPI_STICK3 << 7 ), + MANUAL_MODE | ( SPI_STICK4 << 7 ), + MANUAL_MODE | ( SPI_POT1 << 7 ), + MANUAL_MODE | ( SPI_POT2 << 7 ), + MANUAL_MODE | ( SPI_POT3 << 7 ), + MANUAL_MODE | ( SPI_SLIDER1 << 7 ), + MANUAL_MODE | ( SPI_SLIDER2 << 7 ), + MANUAL_MODE | ( SPI_SLIDER3 << 7 ), + MANUAL_MODE | ( SPI_SLIDER4 << 7 ), + MANUAL_MODE | ( SPI_TX_VOLTAGE << 7 ), + MANUAL_MODE | ( 0 << 7 ), + MANUAL_MODE | ( 0 << 7 ) +}; + void adcRead() { - return ; + const uint16_t * command = adcCommands; - DMA2_Stream0->CR &= ~DMA_SxCR_EN ; // Disable DMA - ADC1->SR &= ~(uint32_t) ( ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR ) ; - DMA2->LIFCR = DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 |DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0 ; // Write ones to clear bits - DMA2_Stream0->CR |= DMA_SxCR_EN ; // Enable DMA - ADC1->CR2 |= (uint32_t)ADC_CR2_SWSTART ; - for (unsigned int i=0; i<10000; i++) { - if (DMA2->LISR & DMA_LISR_TCIF0) { + // Start on chip ADC read + DMA2_Stream1->CR &= ~DMA_SxCR_EN; // Disable DMA + ADC3->SR &= ~(uint32_t) ( ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR ); + DMA2->LIFCR = DMA_LIFCR_CTCIF1 | DMA_LIFCR_CHTIF1 |DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CFEIF1; // Write ones to clear bits + DMA2_Stream1->M0AR = (uint32_t) &Analog_values[MOUSE1]; + DMA2_Stream1->NDTR = 2; + DMA2_Stream1->CR |= DMA_SxCR_EN; // Enable DMA + ADC3->CR2 |= (uint32_t)ADC_CR2_SWSTART; + + ADC_CS_LOW(); + delay_01us(1); + SPIx_ReadWriteByte(*command++); // Discard + ADC_CS_HIGH(); + delay_01us(1); + + ADC_CS_LOW(); + delay_01us(1); + SPIx_ReadWriteByte(*command++); // Discard + ADC_CS_HIGH(); + delay_01us(1); + + for (uint32_t adcIndex=0; adcIndexLISR & DMA_LISR_TCIF1) { break; } } - DMA2_Stream0->CR &= ~DMA_SxCR_EN ; // Disable DMA -} -// TODO -void adcStop() -{ + // On chip ADC read should have finished } uint16_t getAnalogValue(uint32_t value) { - // return Analog_values[ana_mapping[value]]; - return 0; // Analog_values[value]; + return Analog_values[value]; } diff --git a/radio/src/targets/Horus/board_horus.cpp b/radio/src/targets/Horus/board_horus.cpp index ed0d9742e..a3ef084b1 100644 --- a/radio/src/targets/Horus/board_horus.cpp +++ b/radio/src/targets/Horus/board_horus.cpp @@ -167,7 +167,7 @@ void boardInit() TRACE("Horus started :)"); keysInit(); - // adcInit(); + adcInit(); lcdInit(); audioInit(); i2cInit(); diff --git a/radio/src/targets/Horus/board_horus.h b/radio/src/targets/Horus/board_horus.h index 90298f462..12e5ec6f8 100644 --- a/radio/src/targets/Horus/board_horus.h +++ b/radio/src/targets/Horus/board_horus.h @@ -77,39 +77,40 @@ extern "C" { } #endif -#define FLASHSIZE 0x80000 -#define BOOTLOADER_SIZE 0x8000 -#define FIRMWARE_ADDRESS 0x08000000 +#define FLASHSIZE 0x80000 +#define BOOTLOADER_SIZE 0x8000 +#define FIRMWARE_ADDRESS 0x08000000 -#define PERI1_FREQUENCY 45000000 -#define PERI2_FREQUENCY 90000000 -#define TIMER_MULT_APB1 4 -#define TIMER_MULT_APB2 2 +#define PERI1_FREQUENCY 45000000 +#define PERI2_FREQUENCY 90000000 +#define TIMER_MULT_APB1 4 +#define TIMER_MULT_APB2 2 -#define PIN_MODE_MASK 0x0003 -#define PIN_INPUT 0x0000 -#define PIN_OUTPUT 0x0001 -#define PIN_PERIPHERAL 0x0002 -#define PIN_ANALOG 0x0003 -#define PIN_PULL_MASK 0x000C -#define PIN_PULLUP 0x0004 -#define PIN_NO_PULLUP 0x0000 -#define PIN_PULLDOWN 0x0008 -#define PIN_NO_PULLDOWN 0x0000 -#define PIN_PERI_MASK 0x00F0 -#define PIN_PUSHPULL 0x0000 -#define PIN_ODRAIN 0x8000 -#define PIN_PORT_MASK 0x0700 -#define PIN_SPEED_MASK 0x6000 -#define PIN_PORTA 0x0000 -#define PIN_PORTB 0x0100 -#define PIN_PORTC 0x0200 -#define PIN_PER_1 0x0010 -#define PIN_PER_2 0x0020 -#define PIN_PER_3 0x0030 -#define PIN_OS25 0x2000 -#define PIN_OS50 0x4000 -#define PIN_OS100 0x6000 +#define PIN_MODE_MASK 0x0003 +#define PIN_INPUT 0x0000 +#define PIN_OUTPUT 0x0001 +#define PIN_PERIPHERAL 0x0002 +#define PIN_ANALOG 0x0003 +#define PIN_PULL_MASK 0x000C +#define PIN_PULLUP 0x0004 +#define PIN_NO_PULLUP 0x0000 +#define PIN_PULLDOWN 0x0008 +#define PIN_NO_PULLDOWN 0x0000 +#define PIN_PERI_MASK 0x00F0 +#define PIN_PUSHPULL 0x0000 +#define PIN_ODRAIN 0x8000 +#define PIN_PORT_MASK 0x0700 +#define PIN_SPEED_MASK 0x6000 +#define PIN_PORTA 0x0000 +#define PIN_PORTB 0x0100 +#define PIN_PORTC 0x0200 +#define PIN_PORTF 0x0500 +#define PIN_PER_1 0x0010 +#define PIN_PER_2 0x0020 +#define PIN_PER_3 0x0030 +#define PIN_OS25 0x2000 +#define PIN_OS50 0x4000 +#define PIN_OS100 0x6000 void configure_pins( uint32_t pins, uint16_t config ); diff --git a/radio/src/targets/Horus/hal.h b/radio/src/targets/Horus/hal.h index 096a4fec7..19c84f484 100644 --- a/radio/src/targets/Horus/hal.h +++ b/radio/src/targets/Horus/hal.h @@ -90,30 +90,22 @@ #define KEYS_GPIOJ_PINS (SWITCHES_GPIO_PIN_D_H | TRIMS_GPIO_PIN_RVU | TRIMS_GPIO_PIN_LVD | TRIMS_GPIO_PIN_LVU | TRIMS_GPIO_PIN_RSD) // ADC -#define ADC_RCC_AHB1Periph_GPIO (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOC) -#define ADC_GPIO_STICKS GPIOA -#define ADC_GPIO_PIN_STICK_LV GPIO_Pin_0 // PA.00 -#define ADC_GPIO_PIN_STICK_LH GPIO_Pin_1 // PA.01 -#define ADC_GPIO_PIN_STICK_RV GPIO_Pin_2 // PA.02 -#define ADC_GPIO_PIN_STICK_RH GPIO_Pin_3 // PA.03 -#define ADC_GPIO_POT_L1 GPIOC -#define ADC_GPIO_PIN_POT_L1 GPIO_Pin_3 // PC.03 -#define ADC_GPIO_POT_R1 GPIOA -#define ADC_GPIO_PIN_POT_R1 GPIO_Pin_5 // PA.05 -#define ADC_GPIO_POT_L2 GPIOB -#define ADC_GPIO_PIN_POT_L2 GPIO_Pin_1 // PB.01 -#define ADC_GPIO_POT_R2 GPIOB -#define ADC_GPIO_PIN_POT_R2 GPIO_Pin_0 // PB.00 -#define ADC_GPIO_SWITCHES_L3 GPIOC -#define ADC_GPIO_SWITCHES_PIN_L3 GPIO_Pin_1 // PC.01 -#define ADC_GPIO_SWITCHES_R3 GPIOA -#define ADC_GPIO_SWITCHES_PIN_R3 GPIO_Pin_7 // PA.07 -#define ADC_GPIO_SWITCHES_L4 GPIOC -#define ADC_GPIO_SWITCHES_PIN_L4 GPIO_Pin_0 // PC.00 -#define ADC_GPIO_SWITCHES_R4 GPIOA -#define ADC_GPIO_SWITCHES_PIN_R4 GPIO_Pin_6 // PA.06 -#define ADC_GPIO_BATT GPIOC -#define ADC_GPIO_PIN_BATT GPIO_Pin_4 // PC.04 +#define ADC_RCC_AHB1Periph (RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_DMA2) +#define ADC_RCC_APB2Periph (RCC_APB2Periph_SPI4 | RCC_APB2Periph_ADC3) +#define ADC_SPI SPI4 +#define ADC_GPIO_AF GPIO_AF_SPI4 +#define ADC_SPI_GPIO GPIOE +#define ADC_SPI_PIN_SCK GPIO_Pin_2 +#define ADC_SPI_PIN_CS GPIO_Pin_4 +#define ADC_SPI_PIN_MOSI GPIO_Pin_6 +#define ADC_SPI_PIN_MISO GPIO_Pin_5 +#define ADC_SPI_PinSource_SCK GPIO_PinSource2 +#define ADC_SPI_PinSource_MISO GPIO_PinSource5 +#define ADC_SPI_PinSource_MOSI GPIO_PinSource6 +#define ADC_GPIO_PIN_MOUSE1 GPIO_Pin_8 // PF.8 ADC3_IN6 J5 MOUSE_X +#define ADC_GPIO_PIN_MOUSE2 GPIO_Pin_9 // PF.9 ADC3_IN7 J6 MOUSE_Y +#define ADC_IN_MOUSE1 6 +#define ADC_IN_MOUSE2 7 // Power #define PWR_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOJ From 3a740907bb693fcab4f3a70296db241bf4b56026 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 6 Nov 2015 18:46:37 +0100 Subject: [PATCH 53/74] Analog_values renamed to adcValues --- radio/src/cli.cpp | 2 +- radio/src/targets/Horus/adc_driver.cpp | 10 +++++----- radio/src/targets/Sky9x/adc_driver.cpp | 24 ++++++++++++------------ radio/src/targets/Taranis/adc_driver.cpp | 12 ++++++------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index a555acf41..0e0d51e76 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -260,7 +260,7 @@ int cliDisplay(const char ** argv) } else if (!strcmp(argv[1], "adc")) { for (int i=0; iCR = DMA_SxCR_PL | DMA_SxCR_CHSEL_1 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC; DMA2_Stream1->PAR = (uint32_t) &ADC3->DR; - DMA2_Stream1->M0AR = (uint32_t) &Analog_values[MOUSE1]; + DMA2_Stream1->M0AR = (uint32_t) &adcValues[MOUSE1]; DMA2_Stream1->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0; } @@ -184,7 +184,7 @@ void adcRead() DMA2_Stream1->CR &= ~DMA_SxCR_EN; // Disable DMA ADC3->SR &= ~(uint32_t) ( ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR ); DMA2->LIFCR = DMA_LIFCR_CTCIF1 | DMA_LIFCR_CHTIF1 |DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CFEIF1; // Write ones to clear bits - DMA2_Stream1->M0AR = (uint32_t) &Analog_values[MOUSE1]; + DMA2_Stream1->M0AR = (uint32_t) &adcValues[MOUSE1]; DMA2_Stream1->NDTR = 2; DMA2_Stream1->CR |= DMA_SxCR_EN; // Enable DMA ADC3->CR2 |= (uint32_t)ADC_CR2_SWSTART; @@ -204,7 +204,7 @@ void adcRead() for (uint32_t adcIndex=0; adcIndexADC_LCDR; // Clear DRSY flag } // Next bit may be done using the PDC - Analog_values[0] = ADC->ADC_CDR1; - Analog_values[1] = ADC->ADC_CDR2; - Analog_values[2] = ADC->ADC_CDR3; - Analog_values[3] = ADC->ADC_CDR4; - Analog_values[4] = ADC->ADC_CDR5; - Analog_values[5] = ADC->ADC_CDR9; - Analog_values[6] = ADC->ADC_CDR13; - Analog_values[7] = ADC->ADC_CDR14; + adcValues[0] = ADC->ADC_CDR1; + adcValues[1] = ADC->ADC_CDR2; + adcValues[2] = ADC->ADC_CDR3; + adcValues[3] = ADC->ADC_CDR4; + adcValues[4] = ADC->ADC_CDR5; + adcValues[5] = ADC->ADC_CDR9; + adcValues[6] = ADC->ADC_CDR13; + adcValues[7] = ADC->ADC_CDR14; #if !defined(REVA) - Analog_values[8] = ADC->ADC_CDR8 ; + adcValues[8] = ADC->ADC_CDR8 ; #endif temperature = (((int32_t)temperature * 7) + ((((int32_t)ADC->ADC_CDR15 - 838) * 621) >> 11)) >> 3; // Filter it @@ -122,7 +122,7 @@ void adcRead() uint32_t i ; for (i=0; iCR = DMA_SxCR_PL | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC; DMA2_Stream0->PAR = CONVERT_PTR_UINT(&ADC1->DR); - DMA2_Stream0->M0AR = CONVERT_PTR_UINT(Analog_values); + DMA2_Stream0->M0AR = CONVERT_PTR_UINT(adcValues); DMA2_Stream0->NDTR = NUMBER_ANALOG_ADC1; DMA2_Stream0->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ; @@ -118,7 +118,7 @@ void adcInit() // Enable the DMA channel here, DMA2 stream 1, channel 2 DMA2_Stream1->CR = DMA_SxCR_PL | DMA_SxCR_CHSEL_1 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC; DMA2_Stream1->PAR = CONVERT_PTR_UINT(&ADC3->DR); - DMA2_Stream1->M0AR = CONVERT_PTR_UINT(Analog_values + NUMBER_ANALOG_ADC1); + DMA2_Stream1->M0AR = CONVERT_PTR_UINT(adcValues + NUMBER_ANALOG_ADC1); DMA2_Stream1->NDTR = NUMBER_ANALOG_ADC3; DMA2_Stream1->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0 ; #endif @@ -175,7 +175,7 @@ uint16_t getAnalogValue(uint32_t index) index = ana_mapping[index]; #endif if (ana_direction[index] < 0) - return 4096 - Analog_values[index]; + return 4096 - adcValues[index]; else - return Analog_values[index]; + return adcValues[index]; } From 5445e506a6af19336220fbb75712d8ef31abd260 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 8 Nov 2015 18:42:10 +0100 Subject: [PATCH 54/74] Simulator bitmaps moved to different directories --- companion/src/companion.qrc | 95 +++++---- .../src/images/{ => simulator/9X}/9xcurs.png | Bin .../images/{ => simulator/9X}/9xcursdown.png | Bin .../images/{ => simulator/9X}/9xcursmin.png | Bin .../images/{ => simulator/9X}/9xcursphoto.png | Bin .../images/{ => simulator/9X}/9xcursplus.png | Bin .../images/{ => simulator/9X}/9xcursup.png | Bin .../src/images/{ => simulator/9X}/9xdb-bl.png | Bin .../src/images/{ => simulator/9X}/9xdb-gr.png | Bin .../src/images/{ => simulator/9X}/9xdb-or.png | Bin .../src/images/{ => simulator/9X}/9xdb-rd.png | Bin .../src/images/{ => simulator/9X}/9xdb-yl.png | Bin .../src/images/{ => simulator/9X}/9xdb.png | Bin .../src/images/{ => simulator/9X}/9xdl-bl.png | Bin .../src/images/{ => simulator/9X}/9xdl-gr.png | Bin .../src/images/{ => simulator/9X}/9xdl-or.png | Bin .../src/images/{ => simulator/9X}/9xdl-rd.png | Bin .../src/images/{ => simulator/9X}/9xdl-yl.png | Bin .../src/images/{ => simulator/9X}/9xdl.png | Bin .../src/images/{ => simulator/9X}/9xdr-bl.png | Bin .../src/images/{ => simulator/9X}/9xdr-gr.png | Bin .../src/images/{ => simulator/9X}/9xdr-or.png | Bin .../src/images/{ => simulator/9X}/9xdr-rd.png | Bin .../src/images/{ => simulator/9X}/9xdr-yl.png | Bin .../src/images/{ => simulator/9X}/9xdr.png | Bin .../src/images/{ => simulator/9X}/9xdt-bl.png | Bin .../src/images/{ => simulator/9X}/9xdt-gr.png | Bin .../src/images/{ => simulator/9X}/9xdt-or.png | Bin .../src/images/{ => simulator/9X}/9xdt-rd.png | Bin .../src/images/{ => simulator/9X}/9xdt-yl.png | Bin .../src/images/{ => simulator/9X}/9xdt.png | Bin .../src/images/{ => simulator/9X}/9xmenu.png | Bin .../images/{ => simulator/9X}/9xmenuexit.png | Bin .../images/{ => simulator/9X}/9xmenumenu.png | Bin .../src/images/simulator/Horus/bottom.png | Bin 0 -> 133 bytes companion/src/images/simulator/Horus/left.png | Bin 0 -> 41117 bytes .../src/images/simulator/Horus/middle.png | Bin 0 -> 156118 bytes .../src/images/simulator/Horus/right.png | Bin 0 -> 45445 bytes companion/src/images/simulator/Horus/top.png | Bin 0 -> 133 bytes .../images/{ => simulator/Taranis}/x9b0.png | Bin .../images/{ => simulator/Taranis}/x9l0.png | Bin .../images/{ => simulator/Taranis}/x9l1.png | Bin .../images/{ => simulator/Taranis}/x9l2.png | Bin .../images/{ => simulator/Taranis}/x9l3.png | Bin .../images/{ => simulator/Taranis}/x9l4.png | Bin .../images/{ => simulator/Taranis}/x9r0.png | Bin .../images/{ => simulator/Taranis}/x9r1.png | Bin .../images/{ => simulator/Taranis}/x9r2.png | Bin .../images/{ => simulator/Taranis}/x9r3.png | Bin .../images/{ => simulator/Taranis}/x9s0.png | Bin .../images/{ => simulator/Taranis}/x9t0.png | Bin companion/src/simulation/buttonswidget.h | 4 +- .../src/simulation/simulatordialog-9x.ui | 12 +- .../src/simulation/simulatordialog-horus.ui | 192 +++++++++++------- .../src/simulation/simulatordialog-taranis.ui | 8 +- companion/src/simulation/simulatordialog.cpp | 4 +- .../src/simulation/simulatordialog9x.cpp | 22 +- .../src/simulation/simulatordialoghorus.cpp | 16 +- .../src/simulation/simulatordialogtaranis.cpp | 14 +- 59 files changed, 214 insertions(+), 153 deletions(-) rename companion/src/images/{ => simulator/9X}/9xcurs.png (100%) rename companion/src/images/{ => simulator/9X}/9xcursdown.png (100%) rename companion/src/images/{ => simulator/9X}/9xcursmin.png (100%) rename companion/src/images/{ => simulator/9X}/9xcursphoto.png (100%) rename companion/src/images/{ => simulator/9X}/9xcursplus.png (100%) rename companion/src/images/{ => simulator/9X}/9xcursup.png (100%) rename companion/src/images/{ => simulator/9X}/9xdb-bl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdb-gr.png (100%) rename companion/src/images/{ => simulator/9X}/9xdb-or.png (100%) rename companion/src/images/{ => simulator/9X}/9xdb-rd.png (100%) rename companion/src/images/{ => simulator/9X}/9xdb-yl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdb.png (100%) rename companion/src/images/{ => simulator/9X}/9xdl-bl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdl-gr.png (100%) rename companion/src/images/{ => simulator/9X}/9xdl-or.png (100%) rename companion/src/images/{ => simulator/9X}/9xdl-rd.png (100%) rename companion/src/images/{ => simulator/9X}/9xdl-yl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdr-bl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdr-gr.png (100%) rename companion/src/images/{ => simulator/9X}/9xdr-or.png (100%) rename companion/src/images/{ => simulator/9X}/9xdr-rd.png (100%) rename companion/src/images/{ => simulator/9X}/9xdr-yl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdr.png (100%) rename companion/src/images/{ => simulator/9X}/9xdt-bl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdt-gr.png (100%) rename companion/src/images/{ => simulator/9X}/9xdt-or.png (100%) rename companion/src/images/{ => simulator/9X}/9xdt-rd.png (100%) rename companion/src/images/{ => simulator/9X}/9xdt-yl.png (100%) rename companion/src/images/{ => simulator/9X}/9xdt.png (100%) rename companion/src/images/{ => simulator/9X}/9xmenu.png (100%) rename companion/src/images/{ => simulator/9X}/9xmenuexit.png (100%) rename companion/src/images/{ => simulator/9X}/9xmenumenu.png (100%) create mode 100644 companion/src/images/simulator/Horus/bottom.png create mode 100644 companion/src/images/simulator/Horus/left.png create mode 100644 companion/src/images/simulator/Horus/middle.png create mode 100644 companion/src/images/simulator/Horus/right.png create mode 100644 companion/src/images/simulator/Horus/top.png rename companion/src/images/{ => simulator/Taranis}/x9b0.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9l0.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9l1.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9l2.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9l3.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9l4.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9r0.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9r1.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9r2.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9r3.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9s0.png (100%) rename companion/src/images/{ => simulator/Taranis}/x9t0.png (100%) diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index ed4700fc7..018d2e9e2 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -5,39 +5,44 @@ ../../CREDITS.txt ../../DONATIONS.txt ../releasenotes.txt - images/9xdb.png - images/9xdl.png - images/9xdr.png - images/9xdt.png - images/9xdb-bl.png - images/9xdl-bl.png - images/9xdr-bl.png - images/9xdt-bl.png - images/9xdb-gr.png - images/9xdl-gr.png - images/9xdr-gr.png - images/9xdt-gr.png - images/9xdb-or.png - images/9xdl-or.png - images/9xdr-or.png - images/9xdt-or.png - images/9xdb-yl.png - images/9xdl-yl.png - images/9xdr-yl.png - images/9xdt-yl.png - images/9xdb-rd.png - images/9xdl-rd.png - images/9xdr-rd.png - images/9xdt-rd.png - images/9xmenu.png - images/9xmenumenu.png - images/9xmenuexit.png - images/9xcurs.png - images/9xcursup.png - images/9xcursplus.png - images/9xcursmin.png - images/9xcursdown.png - images/9xcursphoto.png + images/simulator/Horus/middle.png + images/simulator/Horus/left.png + images/simulator/Horus/right.png + images/simulator/Horus/top.png + images/simulator/Horus/bottom.png + images/simulator/9X/9xdb.png + images/simulator/9X/9xdl.png + images/simulator/9X/9xdr.png + images/simulator/9X/9xdt.png + images/simulator/9X/9xdb-bl.png + images/simulator/9X/9xdl-bl.png + images/simulator/9X/9xdr-bl.png + images/simulator/9X/9xdt-bl.png + images/simulator/9X/9xdb-gr.png + images/simulator/9X/9xdl-gr.png + images/simulator/9X/9xdr-gr.png + images/simulator/9X/9xdt-gr.png + images/simulator/9X/9xdb-or.png + images/simulator/9X/9xdl-or.png + images/simulator/9X/9xdr-or.png + images/simulator/9X/9xdt-or.png + images/simulator/9X/9xdb-yl.png + images/simulator/9X/9xdl-yl.png + images/simulator/9X/9xdr-yl.png + images/simulator/9X/9xdt-yl.png + images/simulator/9X/9xdb-rd.png + images/simulator/9X/9xdl-rd.png + images/simulator/9X/9xdr-rd.png + images/simulator/9X/9xdt-rd.png + images/simulator/9X/9xmenu.png + images/simulator/9X/9xmenumenu.png + images/simulator/9X/9xmenuexit.png + images/simulator/9X/9xcurs.png + images/simulator/9X/9xcursup.png + images/simulator/9X/9xcursplus.png + images/simulator/9X/9xcursmin.png + images/simulator/9X/9xcursdown.png + images/simulator/9X/9xcursphoto.png images/screenshot.png images/splash-9x.png images/splash-9xrpro.png @@ -47,18 +52,18 @@ images/maps.png images/track.png images/track0.png - images/x9b0.png - images/x9l0.png - images/x9l1.png - images/x9l2.png - images/x9l3.png - images/x9l4.png - images/x9r0.png - images/x9r1.png - images/x9r2.png - images/x9r3.png - images/x9s0.png - images/x9t0.png + images/simulator/Taranis/x9b0.png + images/simulator/Taranis/x9l0.png + images/simulator/Taranis/x9l1.png + images/simulator/Taranis/x9l2.png + images/simulator/Taranis/x9l3.png + images/simulator/Taranis/x9l4.png + images/simulator/Taranis/x9r0.png + images/simulator/Taranis/x9r1.png + images/simulator/Taranis/x9r2.png + images/simulator/Taranis/x9r3.png + images/simulator/Taranis/x9s0.png + images/simulator/Taranis/x9t0.png images/taranison.png images/wizard/ailerons.png images/wizard/airbrakes.png diff --git a/companion/src/images/9xcurs.png b/companion/src/images/simulator/9X/9xcurs.png similarity index 100% rename from companion/src/images/9xcurs.png rename to companion/src/images/simulator/9X/9xcurs.png diff --git a/companion/src/images/9xcursdown.png b/companion/src/images/simulator/9X/9xcursdown.png similarity index 100% rename from companion/src/images/9xcursdown.png rename to companion/src/images/simulator/9X/9xcursdown.png diff --git a/companion/src/images/9xcursmin.png b/companion/src/images/simulator/9X/9xcursmin.png similarity index 100% rename from companion/src/images/9xcursmin.png rename to companion/src/images/simulator/9X/9xcursmin.png diff --git a/companion/src/images/9xcursphoto.png b/companion/src/images/simulator/9X/9xcursphoto.png similarity index 100% rename from companion/src/images/9xcursphoto.png rename to companion/src/images/simulator/9X/9xcursphoto.png diff --git a/companion/src/images/9xcursplus.png b/companion/src/images/simulator/9X/9xcursplus.png similarity index 100% rename from companion/src/images/9xcursplus.png rename to companion/src/images/simulator/9X/9xcursplus.png diff --git a/companion/src/images/9xcursup.png b/companion/src/images/simulator/9X/9xcursup.png similarity index 100% rename from companion/src/images/9xcursup.png rename to companion/src/images/simulator/9X/9xcursup.png diff --git a/companion/src/images/9xdb-bl.png b/companion/src/images/simulator/9X/9xdb-bl.png similarity index 100% rename from companion/src/images/9xdb-bl.png rename to companion/src/images/simulator/9X/9xdb-bl.png diff --git a/companion/src/images/9xdb-gr.png b/companion/src/images/simulator/9X/9xdb-gr.png similarity index 100% rename from companion/src/images/9xdb-gr.png rename to companion/src/images/simulator/9X/9xdb-gr.png diff --git a/companion/src/images/9xdb-or.png b/companion/src/images/simulator/9X/9xdb-or.png similarity index 100% rename from companion/src/images/9xdb-or.png rename to companion/src/images/simulator/9X/9xdb-or.png diff --git a/companion/src/images/9xdb-rd.png b/companion/src/images/simulator/9X/9xdb-rd.png similarity index 100% rename from companion/src/images/9xdb-rd.png rename to companion/src/images/simulator/9X/9xdb-rd.png diff --git a/companion/src/images/9xdb-yl.png b/companion/src/images/simulator/9X/9xdb-yl.png similarity index 100% rename from companion/src/images/9xdb-yl.png rename to companion/src/images/simulator/9X/9xdb-yl.png diff --git a/companion/src/images/9xdb.png b/companion/src/images/simulator/9X/9xdb.png similarity index 100% rename from companion/src/images/9xdb.png rename to companion/src/images/simulator/9X/9xdb.png diff --git a/companion/src/images/9xdl-bl.png b/companion/src/images/simulator/9X/9xdl-bl.png similarity index 100% rename from companion/src/images/9xdl-bl.png rename to companion/src/images/simulator/9X/9xdl-bl.png diff --git a/companion/src/images/9xdl-gr.png b/companion/src/images/simulator/9X/9xdl-gr.png similarity index 100% rename from companion/src/images/9xdl-gr.png rename to companion/src/images/simulator/9X/9xdl-gr.png diff --git a/companion/src/images/9xdl-or.png b/companion/src/images/simulator/9X/9xdl-or.png similarity index 100% rename from companion/src/images/9xdl-or.png rename to companion/src/images/simulator/9X/9xdl-or.png diff --git a/companion/src/images/9xdl-rd.png b/companion/src/images/simulator/9X/9xdl-rd.png similarity index 100% rename from companion/src/images/9xdl-rd.png rename to companion/src/images/simulator/9X/9xdl-rd.png diff --git a/companion/src/images/9xdl-yl.png b/companion/src/images/simulator/9X/9xdl-yl.png similarity index 100% rename from companion/src/images/9xdl-yl.png rename to companion/src/images/simulator/9X/9xdl-yl.png diff --git a/companion/src/images/9xdl.png b/companion/src/images/simulator/9X/9xdl.png similarity index 100% rename from companion/src/images/9xdl.png rename to companion/src/images/simulator/9X/9xdl.png diff --git a/companion/src/images/9xdr-bl.png b/companion/src/images/simulator/9X/9xdr-bl.png similarity index 100% rename from companion/src/images/9xdr-bl.png rename to companion/src/images/simulator/9X/9xdr-bl.png diff --git a/companion/src/images/9xdr-gr.png b/companion/src/images/simulator/9X/9xdr-gr.png similarity index 100% rename from companion/src/images/9xdr-gr.png rename to companion/src/images/simulator/9X/9xdr-gr.png diff --git a/companion/src/images/9xdr-or.png b/companion/src/images/simulator/9X/9xdr-or.png similarity index 100% rename from companion/src/images/9xdr-or.png rename to companion/src/images/simulator/9X/9xdr-or.png diff --git a/companion/src/images/9xdr-rd.png b/companion/src/images/simulator/9X/9xdr-rd.png similarity index 100% rename from companion/src/images/9xdr-rd.png rename to companion/src/images/simulator/9X/9xdr-rd.png diff --git a/companion/src/images/9xdr-yl.png b/companion/src/images/simulator/9X/9xdr-yl.png similarity index 100% rename from companion/src/images/9xdr-yl.png rename to companion/src/images/simulator/9X/9xdr-yl.png diff --git a/companion/src/images/9xdr.png b/companion/src/images/simulator/9X/9xdr.png similarity index 100% rename from companion/src/images/9xdr.png rename to companion/src/images/simulator/9X/9xdr.png diff --git a/companion/src/images/9xdt-bl.png b/companion/src/images/simulator/9X/9xdt-bl.png similarity index 100% rename from companion/src/images/9xdt-bl.png rename to companion/src/images/simulator/9X/9xdt-bl.png diff --git a/companion/src/images/9xdt-gr.png b/companion/src/images/simulator/9X/9xdt-gr.png similarity index 100% rename from companion/src/images/9xdt-gr.png rename to companion/src/images/simulator/9X/9xdt-gr.png diff --git a/companion/src/images/9xdt-or.png b/companion/src/images/simulator/9X/9xdt-or.png similarity index 100% rename from companion/src/images/9xdt-or.png rename to companion/src/images/simulator/9X/9xdt-or.png diff --git a/companion/src/images/9xdt-rd.png b/companion/src/images/simulator/9X/9xdt-rd.png similarity index 100% rename from companion/src/images/9xdt-rd.png rename to companion/src/images/simulator/9X/9xdt-rd.png diff --git a/companion/src/images/9xdt-yl.png b/companion/src/images/simulator/9X/9xdt-yl.png similarity index 100% rename from companion/src/images/9xdt-yl.png rename to companion/src/images/simulator/9X/9xdt-yl.png diff --git a/companion/src/images/9xdt.png b/companion/src/images/simulator/9X/9xdt.png similarity index 100% rename from companion/src/images/9xdt.png rename to companion/src/images/simulator/9X/9xdt.png diff --git a/companion/src/images/9xmenu.png b/companion/src/images/simulator/9X/9xmenu.png similarity index 100% rename from companion/src/images/9xmenu.png rename to companion/src/images/simulator/9X/9xmenu.png diff --git a/companion/src/images/9xmenuexit.png b/companion/src/images/simulator/9X/9xmenuexit.png similarity index 100% rename from companion/src/images/9xmenuexit.png rename to companion/src/images/simulator/9X/9xmenuexit.png diff --git a/companion/src/images/9xmenumenu.png b/companion/src/images/simulator/9X/9xmenumenu.png similarity index 100% rename from companion/src/images/9xmenumenu.png rename to companion/src/images/simulator/9X/9xmenumenu.png diff --git a/companion/src/images/simulator/Horus/bottom.png b/companion/src/images/simulator/Horus/bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..239eeb18f03e8014a968325fd7248cf6912486de GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0y~yV0-{%b8#>M$;Wb1lYkUwfk$L90|U1(2s1Lwnj--e zWH0gbb!ETL%^@hHy7^2&IZ#N&)5S3)FVdQ&MBb@0FX@^XaE2J literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/left.png b/companion/src/images/simulator/Horus/left.png new file mode 100644 index 0000000000000000000000000000000000000000..4c1dfd34546af4444975aba166b984bcfa22413e GIT binary patch literal 41117 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

f3no*2^xzv)-qn?5jxeWvwgNgFIc4iP`tIMW8 z9LY!aMj;Ulqms41+>(rn#=ZCL8_yQ!`5~(xvGs| zrk1w5W9V`+52%)e-(EFr}HgzO=jiF zb2?)!X#J^jeafeNurmah+@b9-^au8lR(1;vN5WAsuk^{+t0Ii7 z@_ADa$UO9tr5Q_YE>41#sb&^3gQIS>CL$wwq80?ZzxgP7rd9-#4qsH&*2IVYAkoH< z^k%<4J16AI>erc>5$I?XTiTzu-*16i&w7}FT+(gm7m3Si47|MeJ4@fW`T5Hs%GeEH za7oksS!?|L6Syi|rm=~M$(A-VQYac)sRAmVzG7+4ZmGgLPOH8+ zhzVt1myeWw2%-=xvN>+8J={Tq%lUNqJAHuClK{6udJt)sD3PDLh@-x%)XLP>xMQZU_Y&5BCPjXQprwGt=Ts-zDkJY8-o+j< z%e9w19!!WEPj5^|f4Plm4SiC;z@~&yPzw}?PJ$L{>P~z*?00KNKCL&mK_gyY^SWPA zl^07H5k-Li6gv0}Q+4;EibN%2BXiJ?U;wm31k0f=IvQEG&24gbqW@hpZsq0W1*pM1 z0cOG=k*5NYo>N-IjfqOQKzBH&d-+z4s%(+il8#Gcx+154V?wJXufya77 zQBB_8UY82rAbr=~?ix+Z%wS^$uRurT1C?-g&h*~|XEVdtL_|V?JhHJo?z;@Tvklhp z5@~XF<>Ft=2%9yR2|TiRDlhcaSWDWu%`+MSrx}B)-M=%rxjhUS6>1UWGV6sCq6Njd zU352^+@Gy3{4qxbcCy3c)h_8mU+2?Nh1<*J;AJqY7bLKjG6^sMxKGQ;_|myP*c}K5 zB+43~kIH_GM)wzLJKtXPopbi7s;X-H13pfF6Nv^Ep03FLVnggB8vpNC

jF8MdeA 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 h7d|33|_`CR}2 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/middle.png b/companion/src/images/simulator/Horus/middle.png new file mode 100644 index 0000000000000000000000000000000000000000..8d3f480a0ced49d9f44d665b41f9ecccf5ba30a4 GIT binary patch literal 156118 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 V4eX1v7{>qr002ovPDHLkV1g)>k#PV3 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/right.png b/companion/src/images/simulator/Horus/right.png new file mode 100644 index 0000000000000000000000000000000000000000..6c5011012bb59baebb8cd5cd044b123a94a1bf88 GIT binary patch literal 45445 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 Ub@~$UaKMkWxPn-f=r{lW0F9HiKmY&$ literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/top.png b/companion/src/images/simulator/Horus/top.png new file mode 100644 index 0000000000000000000000000000000000000000..f0b6f726491b3236b2725cb26ea60a87592c33b4 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0y~yV0-{%b8#>M$;Wb1lYkUwfk$L90|U1(2s1Lwnj--e zWH0gbb!ETL%^@fxUwUBWQJ|2Dr;B4q#=W-(8F_)cLkqt9=kl|Q0GSBzA=|=>fzj@x Qg(yhO)78&qol`;+0GziQp8x;= literal 0 HcmV?d00001 diff --git a/companion/src/images/x9b0.png b/companion/src/images/simulator/Taranis/x9b0.png similarity index 100% rename from companion/src/images/x9b0.png rename to companion/src/images/simulator/Taranis/x9b0.png diff --git a/companion/src/images/x9l0.png b/companion/src/images/simulator/Taranis/x9l0.png similarity index 100% rename from companion/src/images/x9l0.png rename to companion/src/images/simulator/Taranis/x9l0.png diff --git a/companion/src/images/x9l1.png b/companion/src/images/simulator/Taranis/x9l1.png similarity index 100% rename from companion/src/images/x9l1.png rename to companion/src/images/simulator/Taranis/x9l1.png diff --git a/companion/src/images/x9l2.png b/companion/src/images/simulator/Taranis/x9l2.png similarity index 100% rename from companion/src/images/x9l2.png rename to companion/src/images/simulator/Taranis/x9l2.png diff --git a/companion/src/images/x9l3.png b/companion/src/images/simulator/Taranis/x9l3.png similarity index 100% rename from companion/src/images/x9l3.png rename to companion/src/images/simulator/Taranis/x9l3.png diff --git a/companion/src/images/x9l4.png b/companion/src/images/simulator/Taranis/x9l4.png similarity index 100% rename from companion/src/images/x9l4.png rename to companion/src/images/simulator/Taranis/x9l4.png diff --git a/companion/src/images/x9r0.png b/companion/src/images/simulator/Taranis/x9r0.png similarity index 100% rename from companion/src/images/x9r0.png rename to companion/src/images/simulator/Taranis/x9r0.png diff --git a/companion/src/images/x9r1.png b/companion/src/images/simulator/Taranis/x9r1.png similarity index 100% rename from companion/src/images/x9r1.png rename to companion/src/images/simulator/Taranis/x9r1.png diff --git a/companion/src/images/x9r2.png b/companion/src/images/simulator/Taranis/x9r2.png similarity index 100% rename from companion/src/images/x9r2.png rename to companion/src/images/simulator/Taranis/x9r2.png diff --git a/companion/src/images/x9r3.png b/companion/src/images/simulator/Taranis/x9r3.png similarity index 100% rename from companion/src/images/x9r3.png rename to companion/src/images/simulator/Taranis/x9r3.png diff --git a/companion/src/images/x9s0.png b/companion/src/images/simulator/Taranis/x9s0.png similarity index 100% rename from companion/src/images/x9s0.png rename to companion/src/images/simulator/Taranis/x9s0.png diff --git a/companion/src/images/x9t0.png b/companion/src/images/simulator/Taranis/x9t0.png similarity index 100% rename from companion/src/images/x9t0.png rename to companion/src/images/simulator/Taranis/x9t0.png diff --git a/companion/src/simulation/buttonswidget.h b/companion/src/simulation/buttonswidget.h index 80d09ee06..e196b7b39 100644 --- a/companion/src/simulation/buttonswidget.h +++ b/companion/src/simulation/buttonswidget.h @@ -73,7 +73,9 @@ class ButtonsWidget : public QWidget void setBitmap(const char * bitmap) { - QWidget::setStyleSheet(QString("background:url(:/images/%1);").arg(bitmap)); + if (bitmap) { + QWidget::setStyleSheet(QString("background:url(:/images/simulator/%1);").arg(bitmap)); + } } virtual void mousePressEvent(QMouseEvent * event) diff --git a/companion/src/simulation/simulatordialog-9x.ui b/companion/src/simulation/simulatordialog-9x.ui index 085eb56b3..c7dde17b3 100644 --- a/companion/src/simulation/simulatordialog-9x.ui +++ b/companion/src/simulation/simulatordialog-9x.ui @@ -1289,7 +1289,7 @@ QPushButton:checked { - background:url(:/images/9xdt.png) + background:url(:/images/simulator/9X/9xdt.png) @@ -1308,7 +1308,7 @@ QPushButton:checked { - background:url(:/images/9xdl.png) + background:url(:/images/simulator/9X/9xdl.png) @@ -1327,7 +1327,7 @@ QPushButton:checked { - background:url(:/images/9xmenu.png) + background:url(:/images/simulator/9X/9xmenu.png) @@ -1362,7 +1362,7 @@ QPushButton:checked { - background:url(:/images/9xdb.png) + background:url(:/images/simulator/9X/9xdb.png) @@ -1381,7 +1381,7 @@ QPushButton:checked { - background:url(:/images/9xdr.png) + background:url(:/images/simulator/9X/9xdr.png) @@ -1403,7 +1403,7 @@ QPushButton:checked { true - background:url(:/images/9xcurs.png); + background:url(:/images/simulator/9X/9xcurs.png); diff --git a/companion/src/simulation/simulatordialog-horus.ui b/companion/src/simulation/simulatordialog-horus.ui index c51e173f9..8277cb0fa 100644 --- a/companion/src/simulation/simulatordialog-horus.ui +++ b/companion/src/simulation/simulatordialog-horus.ui @@ -6,8 +6,8 @@ 0 0 - 739 - 740 + 808 + 738 @@ -18,7 +18,7 @@ - 739 + 808 16777215 @@ -55,7 +55,7 @@ - 570 + 700 5 @@ -83,17 +83,129 @@ - Forus Simulator + Horus Simulator + + 5 + + + 5 + + + 0 + - + - QLayout::SetMinimumSize + QLayout::SetFixedSize 0 + + + + + 0 + 0 + + + + + 147 + 292 + + + + + 147 + 292 + + + + true + + + background:url(:/images/simulator/Horus/left.png); + + + + + + + + 0 + 0 + + + + + 147 + 292 + + + + + 147 + 292 + + + + background:url(:/images/simulator/Horus/right.png) + + + + + + + + 0 + 0 + + + + + 480 + 10 + + + + + 480 + 10 + + + + background:url(:/images/simulator/Horus/top.png) + + + + + + + + 0 + 0 + + + + + 480 + 10 + + + + + 480 + 10 + + + + background:url(:/images/simulator/Horus/bottom.png) + + + @@ -110,47 +222,6 @@ - - - - - 0 - 0 - - - - - 89 - 186 - - - - true - - - background:url(:/images/9xmenu.png); - - - - - - - - 0 - 0 - - - - - 128 - 186 - - - - background:url(:/images/9xcurs.png) - - - @@ -160,8 +231,8 @@ - 40 - 20 + 20 + 292 @@ -173,29 +244,12 @@ - 40 - 20 + 20 + 292 - - - - - - Qt::Horizontal - - - - 438 - 6 - - - - - - diff --git a/companion/src/simulation/simulatordialog-taranis.ui b/companion/src/simulation/simulatordialog-taranis.ui index 65c040d92..e2f693cb8 100644 --- a/companion/src/simulation/simulatordialog-taranis.ui +++ b/companion/src/simulation/simulatordialog-taranis.ui @@ -112,7 +112,7 @@ true - background:url(:/images/x9l0.png); + background:url(:/images/simulator/Taranis/x9l0.png); @@ -131,7 +131,7 @@ - background:url(:/images/x9t0.png) + background:url(:/images/simulator/Taranis/x9t0.png) @@ -150,7 +150,7 @@ - background:url(:/images/x9r0.png) + background:url(:/images/simulator/Taranis/x9r0.png) @@ -185,7 +185,7 @@ - background:url(:/images/x9b0.png) + background:url(:/images/simulator/Taranis/x9b0.png) diff --git a/companion/src/simulation/simulatordialog.cpp b/companion/src/simulation/simulatordialog.cpp index fbf96467f..6d4b3f8e0 100644 --- a/companion/src/simulation/simulatordialog.cpp +++ b/companion/src/simulation/simulatordialog.cpp @@ -336,7 +336,7 @@ void SimulatorDialog::initUi(T * ui) QSlider * slider = new QSlider(tabWidget); slider->setEnabled(false); - slider->setMaximumSize(QSize(16777215, 18)); + /*slider->setMaximumSize(QSize(16777215, 18)); slider->setStyleSheet(QString::fromUtf8("QSlider::sub-page:horizontal:disabled {\n" "border-color: #999;\n" "}\n" @@ -349,7 +349,7 @@ void SimulatorDialog::initUi(T * ui) "background: #0000CC;\n" "border: 1px solid #aaa;\n" "border-radius: 4px;\n" - "}")); + "}")); */ slider->setMinimum(-1024); slider->setMaximum(1024); slider->setPageStep(128); diff --git a/companion/src/simulation/simulatordialog9x.cpp b/companion/src/simulation/simulatordialog9x.cpp index 3b84c8064..a2a87455e 100644 --- a/companion/src/simulation/simulatordialog9x.cpp +++ b/companion/src/simulation/simulatordialog9x.cpp @@ -16,17 +16,17 @@ SimulatorDialog9X::SimulatorDialog9X(QWidget * parent, SimulatorInterface *simul initUi(ui); polygon.setPoints(6, 68, 83, 28, 45, 51, 32, 83, 32, 105, 45, 68, 83); - ui->leftbuttons->addArea(polygon, Qt::Key_Up, "9xcursup.png"); + ui->leftbuttons->addArea(polygon, Qt::Key_Up, "9X/9xcursup.png"); polygon.setPoints(6, 74, 90, 114, 51, 127, 80, 127, 106, 114, 130, 74, 90); - ui->leftbuttons->addArea(polygon, Qt::Key_Right, "9xcursmin.png"); + ui->leftbuttons->addArea(polygon, Qt::Key_Right, "9X/9xcursmin.png"); polygon.setPoints(6, 68, 98, 28, 137, 51, 151, 83, 151, 105, 137, 68, 98); - ui->leftbuttons->addArea(polygon, Qt::Key_Down, "9xcursdown.png"); + ui->leftbuttons->addArea(polygon, Qt::Key_Down, "9X/9xcursdown.png"); polygon.setPoints(6, 80, 90, 20, 51, 7, 80, 7, 106, 20, 130, 80, 90); - ui->leftbuttons->addArea(polygon, Qt::Key_Left, "9xcursplus.png"); - ui->leftbuttons->addArea(5, 148, 39, 182, Qt::Key_Print, "9xcursphoto.png"); + ui->leftbuttons->addArea(polygon, Qt::Key_Left, "9X/9xcursplus.png"); + ui->leftbuttons->addArea(5, 148, 39, 182, Qt::Key_Print, "9X/9xcursphoto.png"); - ui->rightbuttons->addArea(25, 60, 71, 81, Qt::Key_Enter, "9xmenumenu.png"); - ui->rightbuttons->addArea(25, 117, 71, 139, Qt::Key_Escape, "9xmenuexit.png"); + ui->rightbuttons->addArea(25, 60, 71, 81, Qt::Key_Enter, "9X/9xmenumenu.png"); + ui->rightbuttons->addArea(25, 117, 71, 139, Qt::Key_Escape, "9X/9xmenuexit.png"); // install simulator TRACE hook simulator->installTraceHook(traceCb); @@ -107,10 +107,10 @@ void SimulatorDialog9X::setLightOn(bool enable) list << "bl" << "gr" << "rd" << "or" << "yl"; bg = QString("-") + list[backLight]; } - ui->top->setStyleSheet(QString("background:url(:/images/9xdt%1.png);").arg(bg)); - ui->bottom->setStyleSheet(QString("background:url(:/images/9xdb%1.png);").arg(bg)); - ui->left->setStyleSheet(QString("background:url(:/images/9xdl%1.png);").arg(bg)); - ui->right->setStyleSheet(QString("background:url(:/images/9xdr%1.png);").arg(bg)); + ui->top->setStyleSheet(QString("background:url(:/images/simulator/9X/9xdt%1.png);").arg(bg)); + ui->bottom->setStyleSheet(QString("background:url(:/images/simulator/9X/9xdb%1.png);").arg(bg)); + ui->left->setStyleSheet(QString("background:url(:/images/simulator/9X/9xdl%1.png);").arg(bg)); + ui->right->setStyleSheet(QString("background:url(:/images/simulator/9X/9xdr%1.png);").arg(bg)); } void SimulatorDialog9X::updateBeepButton() diff --git a/companion/src/simulation/simulatordialoghorus.cpp b/companion/src/simulation/simulatordialoghorus.cpp index 3bd7733bb..cee428f69 100644 --- a/companion/src/simulation/simulatordialoghorus.cpp +++ b/companion/src/simulation/simulatordialoghorus.cpp @@ -11,23 +11,23 @@ SimulatorDialogHorus::SimulatorDialogHorus(QWidget * parent, SimulatorInterface QPolygon polygon; lcdWidth = 480; - lcdHeight = 270; + lcdHeight = 272; lcdDepth = 16; initUi(ui); polygon.setPoints(6, 68, 83, 28, 45, 51, 32, 83, 32, 105, 45, 68, 83); - ui->rightbuttons->addArea(polygon, Qt::Key_Up, "9xcursup.png"); + ui->rightbuttons->addArea(polygon, Qt::Key_Up, NULL); polygon.setPoints(6, 74, 90, 114, 51, 127, 80, 127, 106, 114, 130, 74, 90); - ui->rightbuttons->addArea(polygon, Qt::Key_Right, "9xcursmin.png"); + ui->rightbuttons->addArea(polygon, Qt::Key_Right, NULL); polygon.setPoints(6, 68, 98, 28, 137, 51, 151, 83, 151, 105, 137, 68, 98); - ui->rightbuttons->addArea(polygon, Qt::Key_Down, "9xcursdown.png"); + ui->rightbuttons->addArea(polygon, Qt::Key_Down, NULL); polygon.setPoints(6, 80, 90, 20, 51, 7, 80, 7, 106, 20, 130, 80, 90); - ui->rightbuttons->addArea(polygon, Qt::Key_Left, "9xcursplus.png"); - ui->rightbuttons->addArea(5, 148, 39, 182, Qt::Key_Print, "9xcursphoto.png"); + ui->rightbuttons->addArea(polygon, Qt::Key_Left, NULL); + ui->rightbuttons->addArea(5, 148, 39, 182, Qt::Key_Print, NULL); - ui->leftbuttons->addArea(25, 60, 71, 81, Qt::Key_PageUp, "9xmenumenu.png"); - ui->leftbuttons->addArea(25, 117, 71, 139, Qt::Key_Escape, "9xmenuexit.png"); + ui->leftbuttons->addArea(25, 60, 71, 81, Qt::Key_PageUp, NULL); + ui->leftbuttons->addArea(25, 117, 71, 139, Qt::Key_Escape, NULL); // install simulator TRACE hook simulator->installTraceHook(traceCb); diff --git a/companion/src/simulation/simulatordialogtaranis.cpp b/companion/src/simulation/simulatordialogtaranis.cpp index 432601a65..dc67dfa6c 100644 --- a/companion/src/simulation/simulatordialogtaranis.cpp +++ b/companion/src/simulation/simulatordialogtaranis.cpp @@ -15,18 +15,18 @@ SimulatorDialogTaranis::SimulatorDialogTaranis(QWidget * parent, SimulatorInterf initUi(ui); polygon.setPoints(6, 20, 59, 27, 50, 45, 52, 56, 59, 50, 71, 26, 72); - ui->leftbuttons->addArea(polygon, Qt::Key_PageUp, "x9l1.png"); + ui->leftbuttons->addArea(polygon, Qt::Key_PageUp, "Taranis/x9l1.png"); polygon.setPoints(6, 23, 107, 30, 99, 46, 100, 55, 106, 47, 117, 28, 117); - ui->leftbuttons->addArea(polygon, Qt::Key_PageDown, "x9l2.png"); + ui->leftbuttons->addArea(polygon, Qt::Key_PageDown, "Taranis/x9l2.png"); polygon.setPoints(6, 24, 154, 32, 144, 46, 146, 57, 156, 46, 167, 29, 166); - ui->leftbuttons->addArea(polygon, Qt::Key_Escape, "x9l3.png"); - ui->leftbuttons->addArea(90, 177, 118, 197, Qt::Key_Print, "x9l4.png"); + ui->leftbuttons->addArea(polygon, Qt::Key_Escape, "Taranis/x9l3.png"); + ui->leftbuttons->addArea(90, 177, 118, 197, Qt::Key_Print, "Taranis/x9l4.png"); polygon.setPoints(6, 64, 60, 71, 50, 90, 50, 100, 60, 90, 73, 72, 73); - ui->rightbuttons->addArea(polygon, Qt::Key_Plus, "x9r1.png"); + ui->rightbuttons->addArea(polygon, Qt::Key_Plus, "Taranis/x9r1.png"); polygon.setPoints(6, 63, 109, 73, 100, 88, 100, 98, 109, 88, 119, 72, 119); - ui->rightbuttons->addArea(polygon, Qt::Key_Minus, "x9r2.png"); + ui->rightbuttons->addArea(polygon, Qt::Key_Minus, "Taranis/x9r2.png"); polygon.setPoints(6, 63, 155, 72, 146, 90, 146, 98, 155, 88, 166, 72, 166); - ui->rightbuttons->addArea(polygon, Qt::Key_Enter, "x9r3.png"); + ui->rightbuttons->addArea(polygon, Qt::Key_Enter, "Taranis/x9r3.png"); // install simulator TRACE hook simulator->installTraceHook(traceCb); From d74092d6eb44dd4fc75e1a9da8f124baad7b8fe9 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 8 Nov 2015 20:56:37 +0100 Subject: [PATCH 55/74] Compilation failure --- radio/src/targets/Horus/adc_driver.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/radio/src/targets/Horus/adc_driver.cpp b/radio/src/targets/Horus/adc_driver.cpp index 60b5c726f..01538cfe3 100644 --- a/radio/src/targets/Horus/adc_driver.cpp +++ b/radio/src/targets/Horus/adc_driver.cpp @@ -140,8 +140,8 @@ void adcInit() // Enable the DMA channel here, DMA2 stream 1, channel 2 DMA2_Stream1->CR = DMA_SxCR_PL | DMA_SxCR_CHSEL_1 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC; - DMA2_Stream1->PAR = (uint32_t) &ADC3->DR; - DMA2_Stream1->M0AR = (uint32_t) &adcValues[MOUSE1]; + DMA2_Stream1->PAR = CONVERT_PTR_UINT(&ADC3->DR); + DMA2_Stream1->M0AR = CONVERT_PTR_UINT(&adcValues[MOUSE1]); DMA2_Stream1->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0; } @@ -184,7 +184,7 @@ void adcRead() DMA2_Stream1->CR &= ~DMA_SxCR_EN; // Disable DMA ADC3->SR &= ~(uint32_t) ( ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR ); DMA2->LIFCR = DMA_LIFCR_CTCIF1 | DMA_LIFCR_CHTIF1 |DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CFEIF1; // Write ones to clear bits - DMA2_Stream1->M0AR = (uint32_t) &adcValues[MOUSE1]; + DMA2_Stream1->M0AR = CONVERT_PTR_UINT(&adcValues[MOUSE1]); DMA2_Stream1->NDTR = 2; DMA2_Stream1->CR |= DMA_SxCR_EN; // Enable DMA ADC3->CR2 |= (uint32_t)ADC_CR2_SWSTART; From 34fef4fefeeedd6a5760eb6cb6d56e71cbcd227e Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 9 Nov 2015 20:02:08 +0100 Subject: [PATCH 56/74] [Horus] Some LCD timings added in comments --- radio/src/debug.h | 3 ++ radio/src/gui/Horus/view_main.cpp | 5 +++ radio/src/opentx.cpp | 4 +++ radio/src/targets/Horus/adc_driver.cpp | 3 -- radio/src/targets/Horus/board_horus.cpp | 34 ++++++++++---------- radio/src/targets/Horus/hal.h | 20 ++++++------ radio/src/targets/Horus/haptic_driver.cpp | 3 -- radio/src/targets/Horus/i2c_driver.cpp | 3 -- radio/src/targets/Horus/keys_driver.cpp | 2 -- radio/src/targets/Horus/lcd_driver.cpp | 14 ++------ radio/src/targets/Horus/led_driver.cpp | 4 +-- radio/src/targets/Horus/pulses_driver.cpp | 2 +- radio/src/targets/Horus/pwr_driver.c | 3 -- radio/src/targets/Horus/serial2_driver.cpp | 4 --- radio/src/targets/Horus/telemetry_driver.cpp | 4 --- 15 files changed, 44 insertions(+), 64 deletions(-) diff --git a/radio/src/debug.h b/radio/src/debug.h index 0832db330..92640a397 100644 --- a/radio/src/debug.h +++ b/radio/src/debug.h @@ -78,6 +78,9 @@ void debugPrintf(const char * format, ...); #define TRACE_WARNING_WP(...) debugPrintf(__VA_ARGS__) #define TRACE_ERROR(...) debugPrintf("-E- " __VA_ARGS__) +#define TIME_MEASURE_START(id) uint16_t t0 ## id = getTmr2MHz() +#define TIME_MEASURE_STOP(id) TRACE("Measure(" # id ") = %.1fus", float((uint16_t)(getTmr2MHz() - t0 ## id))/2) + #if defined(DEBUG_TRACE_BUFFER) #define TRACE_BUFFER_LEN 50 diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index 1ca93a9cc..e3fdacdb5 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -345,7 +345,9 @@ bool menuMainView(evt_t event) break; } + TIME_MEASURE_START(backgroundbitmap); lcdDrawBitmap(0, 0, LBM_MAINVIEW_BACKGROUND); + TIME_MEASURE_STOP(backgroundbitmap); // 23ms // Header lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); @@ -378,7 +380,10 @@ bool menuMainView(evt_t event) drawTrims(mode); // Model panel + TIME_MEASURE_START(filledRect); lcdDrawFilledRect(MODELPANEL_LEFT, MODELPANEL_TOP, MODELPANEL_WIDTH, MODELPANEL_HEIGHT, SOLID, TEXT_BGCOLOR | OPACITY(5)); + TIME_MEASURE_STOP(filledRect); // 9ms ! + lcdDrawBitmapPattern(MODELPANEL_LEFT+6, MODELPANEL_TOP+4, LBM_MODEL_ICON, TITLE_BGCOLOR); lcdDrawTextWithLen(MODELPANEL_LEFT+45, MODELPANEL_TOP+10, g_model.header.name, LEN_MODEL_NAME, ZCHAR|SMLSIZE); lcdDrawSolidHorizontalLine(MODELPANEL_LEFT+39, MODELPANEL_TOP+27, MODELPANEL_WIDTH-48, TITLE_BGCOLOR); diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 10e71b2b7..c5b4e81d8 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -1048,7 +1048,11 @@ void doSplash() if (!(g_eeGeneral.splashMode & 0x04)) { #endif +#if defined(COLORLCD) + if (keyDown()) return; +#else if (keyDown() || inputsMoved()) return; +#endif #if defined(FSPLASH) } diff --git a/radio/src/targets/Horus/adc_driver.cpp b/radio/src/targets/Horus/adc_driver.cpp index 01538cfe3..960708dab 100644 --- a/radio/src/targets/Horus/adc_driver.cpp +++ b/radio/src/targets/Horus/adc_driver.cpp @@ -121,9 +121,6 @@ static void ADS7952_Init() void adcInit() { - RCC_AHB1PeriphClockCmd(ADC_RCC_AHB1Periph, ENABLE); - RCC_APB2PeriphClockCmd(ADC_RCC_APB2Periph, ENABLE); - ADS7952_Init(); configure_pins( ADC_GPIO_PIN_MOUSE1 | ADC_GPIO_PIN_MOUSE2, PIN_ANALOG | PIN_PORTF ); diff --git a/radio/src/targets/Horus/board_horus.cpp b/radio/src/targets/Horus/board_horus.cpp index a3ef084b1..18a7b73b7 100644 --- a/radio/src/targets/Horus/board_horus.cpp +++ b/radio/src/targets/Horus/board_horus.cpp @@ -58,27 +58,22 @@ void watchdogInit(unsigned int duration) // Start TIMER7 at 2000000Hz void init2MhzTimer() { - // Now for timer 7 - RCC->APB1ENR |= RCC_APB1ENR_TIM7EN ; // Enable clock - - TIM7->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 2000000 - 1 ; // 0.5 uS, 2 MHz - TIM7->ARR = 65535; - TIM7->CR2 = 0; - TIM7->CR1 = TIM_CR1_CEN; + TIMER_2MHz_TIMER->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 2000000 - 1 ; // 0.5 uS, 2 MHz + TIMER_2MHz_TIMER->ARR = 65535; + TIMER_2MHz_TIMER->CR2 = 0; + TIMER_2MHz_TIMER->CR1 = TIM_CR1_CEN; } // Starts TIMER at 200Hz, 5mS period void init5msTimer() { - // Timer14 - RCC->APB1ENR |= RCC_APB1ENR_TIM14EN ; // Enable clock - TIM14->ARR = 999 ; // 5mS - TIM14->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 1000000 - 1 ; // 1uS from 30MHz - TIM14->CCER = 0 ; - TIM14->CCMR1 = 0 ; - TIM14->EGR = 0 ; - TIM14->CR1 = 5 ; - TIM14->DIER |= 1 ; + INTERRUPT_5MS_TIMER->ARR = 999 ; // 5mS + INTERRUPT_5MS_TIMER->PSC = (PERI1_FREQUENCY * TIMER_MULT_APB1) / 1000000 - 1 ; // 1uS from 30MHz + INTERRUPT_5MS_TIMER->CCER = 0 ; + INTERRUPT_5MS_TIMER->CCMR1 = 0 ; + INTERRUPT_5MS_TIMER->EGR = 0 ; + INTERRUPT_5MS_TIMER->CR1 = 5 ; + INTERRUPT_5MS_TIMER->DIER |= 1 ; NVIC_EnableIRQ(TIM8_TRG_COM_TIM14_IRQn) ; NVIC_SetPriority(TIM8_TRG_COM_TIM14_IRQn, 7); } @@ -121,6 +116,7 @@ extern "C" void TIM8_TRG_COM_TIM14_IRQHandler() interrupt5ms() ; } +#if 0 void pinCheck(GPIO_TypeDef * gpio, uint32_t pin, uint32_t RCC_AHB1Periph) { GPIO_InitTypeDef GPIO_InitStruct; @@ -141,16 +137,20 @@ void pinCheck(GPIO_TypeDef * gpio, uint32_t pin, uint32_t RCC_AHB1Periph) delay_01us(10); } } +#endif void boardInit() { + RCC_AHB1PeriphClockCmd(PWR_RCC_AHB1Periph | LCD_RCC_AHB1Periph | KEYS_RCC_AHB1Periph_GPIO | ADC_RCC_AHB1Periph | I2C_RCC_AHB1Periph | SERIAL_RCC_AHB1Periph | TELEMETRY_RCC_AHB1Periph | HAPTIC_RCC_AHB1Periph, ENABLE); + RCC_APB1PeriphClockCmd(INTERRUPT_5MS_APB1Periph | TIMER_2MHz_APB1Periph | I2C_RCC_APB1Periph | SERIAL_RCC_APB1Periph | TELEMETRY_RCC_APB1Periph, ENABLE); + RCC_APB2PeriphClockCmd(LCD_RCC_APB2Periph | ADC_RCC_APB2Periph | HAPTIC_RCC_APB2Periph, ENABLE); + pwrInit(); ledInit(); delaysInit(); ledRed(); - if (0) { // pinCheck(SERIAL_GPIO, SERIAL_GPIO_PIN_TX, SERIAL_RCC_AHB1Periph_GPIO); // pinCheck(EEPROM_GPIO, EEPROM_GPIO_PIN_SCK, RCC_AHB1Periph_GPIOB); diff --git a/radio/src/targets/Horus/hal.h b/radio/src/targets/Horus/hal.h index 19c84f484..aff6f574e 100644 --- a/radio/src/targets/Horus/hal.h +++ b/radio/src/targets/Horus/hal.h @@ -108,19 +108,19 @@ #define ADC_IN_MOUSE2 7 // Power -#define PWR_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOJ +#define PWR_RCC_AHB1Periph RCC_AHB1Periph_GPIOJ #define PWR_GPIO GPIOJ #define PWR_GPIO_PIN_ON GPIO_Pin_0 // PJ.00 #define PWR_GPIO_PIN_SWITCH GPIO_Pin_1 // PJ.01 // Led -#define LED_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOI +#define LED_RCC_AHB1Periph RCC_AHB1Periph_GPIOI #define LED_GPIO GPIOI #define LED_GPIO_PIN GPIO_Pin_5 // PI.05 // Serial Port (DEBUG) -#define SERIAL_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOB -#define SERIAL_RCC_APB1Periph_USART RCC_APB1Periph_USART3 +#define SERIAL_RCC_AHB1Periph RCC_AHB1Periph_GPIOB +#define SERIAL_RCC_APB1Periph RCC_APB1Periph_USART3 #define SERIAL_GPIO GPIOB #define SERIAL_GPIO_PIN_TX GPIO_Pin_10 // PB.10 #define SERIAL_GPIO_PIN_RX GPIO_Pin_11 // PB.11 @@ -132,8 +132,8 @@ #define SERIAL_USART_IRQn USART3_IRQn // Telemetry -#define TELEMETRY_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOD -#define TELEMETRY_RCC_APB1Periph_USART RCC_APB1Periph_USART2 +#define TELEMETRY_RCC_AHB1Periph RCC_AHB1Periph_GPIOD +#define TELEMETRY_RCC_APB1Periph RCC_APB1Periph_USART2 #define TELEMETRY_GPIO_DIR GPIOD #define TELEMETRY_GPIO_PIN_DIR GPIO_Pin_4 // PD.04 #define TELEMETRY_GPIO GPIOD @@ -189,8 +189,8 @@ // Audio // I2C Bus: CAT5137 -#define I2C_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOB -#define I2C_RCC_APB1Periph_I2C RCC_APB1Periph_I2C1 +#define I2C_RCC_AHB1Periph RCC_AHB1Periph_GPIOB +#define I2C_RCC_APB1Periph RCC_APB1Periph_I2C1 #define I2C I2C1 #define I2C_GPIO GPIOB #define I2C_GPIO_PIN_SCL GPIO_Pin_8 // PB.08 @@ -202,10 +202,10 @@ #define I2C_ADDRESS_CAT5137 0x5C // Haptic -#define HAPTIC_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOA +#define HAPTIC_RCC_AHB1Periph RCC_AHB1Periph_GPIOA +#define HAPTIC_RCC_APB2Periph RCC_APB2ENR_TIM11EN #define HAPTIC_GPIO GPIOA #define HAPTIC_GPIO_PIN GPIO_Pin_2 -#define HAPTIC_RCC_APB2Periph_TIMER RCC_APB2ENR_TIM11EN #define HAPTIC_GPIO_TIMER TIM11 #define HAPTIC_GPIO_AF GPIO_AF_TIM11 #define HAPTIC_GPIO_PinSource GPIO_PinSource2 diff --git a/radio/src/targets/Horus/haptic_driver.cpp b/radio/src/targets/Horus/haptic_driver.cpp index 0f62cc625..9aa0e4c7e 100644 --- a/radio/src/targets/Horus/haptic_driver.cpp +++ b/radio/src/targets/Horus/haptic_driver.cpp @@ -51,9 +51,6 @@ void hapticOn(uint32_t pwmPercent) void hapticInit(void) { - RCC_AHB1PeriphClockCmd(HAPTIC_RCC_AHB1Periph_GPIO, ENABLE); - RCC_APB2PeriphClockCmd(HAPTIC_RCC_APB2Periph_TIMER, ENABLE); - GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = HAPTIC_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; diff --git a/radio/src/targets/Horus/i2c_driver.cpp b/radio/src/targets/Horus/i2c_driver.cpp index 79ad59260..4793fbc0c 100644 --- a/radio/src/targets/Horus/i2c_driver.cpp +++ b/radio/src/targets/Horus/i2c_driver.cpp @@ -2,9 +2,6 @@ void i2cInit(void) { - RCC_AHB1PeriphClockCmd(I2C_RCC_AHB1Periph_GPIO, ENABLE); - RCC_APB1PeriphClockCmd(I2C_RCC_APB1Periph_I2C, ENABLE); - GPIO_PinAFConfig(I2C_GPIO, I2C_GPIO_PinSource_SCL, I2C_GPIO_AF); GPIO_PinAFConfig(I2C_GPIO, I2C_GPIO_PinSource_SDA, I2C_GPIO_AF); diff --git a/radio/src/targets/Horus/keys_driver.cpp b/radio/src/targets/Horus/keys_driver.cpp index 141238c4e..692cd9a61 100644 --- a/radio/src/targets/Horus/keys_driver.cpp +++ b/radio/src/targets/Horus/keys_driver.cpp @@ -207,8 +207,6 @@ bool switchState(EnumKeys enuk) void keysInit() { - RCC_AHB1PeriphClockCmd(KEYS_RCC_AHB1Periph_GPIO, ENABLE); - GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; diff --git a/radio/src/targets/Horus/lcd_driver.cpp b/radio/src/targets/Horus/lcd_driver.cpp index 945c51b07..51483af09 100644 --- a/radio/src/targets/Horus/lcd_driver.cpp +++ b/radio/src/targets/Horus/lcd_driver.cpp @@ -349,9 +349,6 @@ void LCD_ControlLight(uint16_t dutyCycle) */ void LCD_Init(void) { - RCC_AHB1PeriphClockCmd(LCD_RCC_AHB1Periph, ENABLE); - RCC_APB2PeriphClockCmd(LCD_RCC_APB2Periph, ENABLE); - /* Reset the LCD --------------------------------------------------------*/ LCD_NRSTConfig(); lcd_reset(); @@ -387,13 +384,6 @@ void LCD_SetLayer(uint32_t Layerx) } } -void LCD_Clear(uint16_t Color) -{ - for (uint16_t * index = (uint16_t *)CurrentFrameBuffer; index < (uint16_t *)(CurrentFrameBuffer+DISPLAY_BUFFER_SIZE); index++) { - *index = Color; - } -} - /** * @brief Configure the transparency. * @param transparency: specifies the transparency, @@ -422,12 +412,12 @@ void lcdInit(void) /* Set Background layer */ LCD_SetLayer(LCD_FIRST_LAYER); - LCD_Clear(0); + // lcdClear(); LCD_SetTransparency(0); /* Set Foreground layer */ LCD_SetLayer(LCD_SECOND_LAYER); - LCD_Clear(0); + lcdClear(); LCD_SetTransparency(255); } diff --git a/radio/src/targets/Horus/led_driver.cpp b/radio/src/targets/Horus/led_driver.cpp index 8da96ac4c..e3f1b9547 100644 --- a/radio/src/targets/Horus/led_driver.cpp +++ b/radio/src/targets/Horus/led_driver.cpp @@ -38,7 +38,7 @@ void ledInit() { - RCC_AHB1PeriphClockCmd(LED_RCC_AHB1Periph_GPIO, ENABLE); + RCC_AHB1PeriphClockCmd(LED_RCC_AHB1Periph, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = LED_GPIO_PIN; @@ -51,7 +51,7 @@ void ledInit() void ledOff() { - RCC_AHB1PeriphClockCmd(LED_RCC_AHB1Periph_GPIO, DISABLE); + RCC_AHB1PeriphClockCmd(LED_RCC_AHB1Periph, DISABLE); } void ledRed() diff --git a/radio/src/targets/Horus/pulses_driver.cpp b/radio/src/targets/Horus/pulses_driver.cpp index 9094ee3b9..7daa3f516 100644 --- a/radio/src/targets/Horus/pulses_driver.cpp +++ b/radio/src/targets/Horus/pulses_driver.cpp @@ -55,7 +55,7 @@ void init_no_pulses(uint32_t port) // Timer1, channel 1 GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(EXTMODULE_RCC_AHB1Periph_GPIO, ENABLE); - + GPIO_InitStructure.GPIO_Pin = EXTMODULE_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; diff --git a/radio/src/targets/Horus/pwr_driver.c b/radio/src/targets/Horus/pwr_driver.c index a7202ca8e..2c63ef630 100644 --- a/radio/src/targets/Horus/pwr_driver.c +++ b/radio/src/targets/Horus/pwr_driver.c @@ -44,9 +44,6 @@ void pwrInit() { GPIO_InitTypeDef GPIO_InitStructure; - /* Clock enable */ - RCC_AHB1PeriphClockCmd(PWR_RCC_AHB1Periph_GPIO, ENABLE); - /* GPIO Configuration*/ GPIO_InitStructure.GPIO_Pin = PWR_GPIO_PIN_ON; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; diff --git a/radio/src/targets/Horus/serial2_driver.cpp b/radio/src/targets/Horus/serial2_driver.cpp index 12d4bf94f..ff34fb186 100644 --- a/radio/src/targets/Horus/serial2_driver.cpp +++ b/radio/src/targets/Horus/serial2_driver.cpp @@ -46,8 +46,6 @@ void uart3Setup(unsigned int baudrate) USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; - RCC_AHB1PeriphClockCmd(SERIAL_RCC_AHB1Periph_GPIO, ENABLE); - GPIO_PinAFConfig(SERIAL_GPIO, SERIAL_GPIO_PinSource_RX, SERIAL_GPIO_AF); GPIO_PinAFConfig(SERIAL_GPIO, SERIAL_GPIO_PinSource_TX, SERIAL_GPIO_AF); @@ -58,8 +56,6 @@ void uart3Setup(unsigned int baudrate) GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SERIAL_GPIO, &GPIO_InitStructure); - RCC_APB1PeriphClockCmd(SERIAL_RCC_APB1Periph_USART, ENABLE); - USART_InitStructure.USART_BaudRate = baudrate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; diff --git a/radio/src/targets/Horus/telemetry_driver.cpp b/radio/src/targets/Horus/telemetry_driver.cpp index 998204738..1ce9c880a 100644 --- a/radio/src/targets/Horus/telemetry_driver.cpp +++ b/radio/src/targets/Horus/telemetry_driver.cpp @@ -48,8 +48,6 @@ void telemetryPortInit(uint32_t baudrate) USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; - RCC_AHB1PeriphClockCmd(TELEMETRY_RCC_AHB1Periph_GPIO, ENABLE); - GPIO_PinAFConfig(TELEMETRY_GPIO, TELEMETRY_GPIO_PinSource_RX, TELEMETRY_GPIO_AF); GPIO_PinAFConfig(TELEMETRY_GPIO, TELEMETRY_GPIO_PinSource_TX, TELEMETRY_GPIO_AF); @@ -65,8 +63,6 @@ void telemetryPortInit(uint32_t baudrate) GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(TELEMETRY_GPIO_DIR, &GPIO_InitStructure); GPIO_ResetBits(TELEMETRY_GPIO_DIR, TELEMETRY_GPIO_PIN_DIR); - - RCC_APB1PeriphClockCmd(TELEMETRY_RCC_APB1Periph_USART, ENABLE); USART_InitStructure.USART_BaudRate = baudrate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; From 922141fcf77b18e8539bfa7990d01e4f8df9c6af Mon Sep 17 00:00:00 2001 From: mhotar Date: Mon, 9 Nov 2015 23:29:35 +0100 Subject: [PATCH 57/74] simulator slices --- .../images/originals/simufront/horus_slim.svg | 28801 ++++++++++++++++ companion/src/images/simulator/Horus/left.png | Bin 41117 -> 42819 bytes .../src/images/simulator/Horus/left_btn1.png | Bin 0 -> 42659 bytes .../src/images/simulator/Horus/left_btn2.png | Bin 0 -> 42743 bytes .../images/simulator/Horus/left_scrnsht.png | Bin 0 -> 42773 bytes .../src/images/simulator/Horus/middle.png | Bin 156118 -> 44468 bytes .../src/images/simulator/Horus/right.png | Bin 45445 -> 44170 bytes .../src/images/simulator/Horus/right_btnD.png | Bin 0 -> 44154 bytes .../src/images/simulator/Horus/right_btnL.png | Bin 0 -> 44096 bytes .../src/images/simulator/Horus/right_btnR.png | Bin 0 -> 44090 bytes .../src/images/simulator/Horus/right_btnU.png | Bin 0 -> 44115 bytes .../src/images/simulator/Horus/right_ent.png | Bin 0 -> 43848 bytes .../images/simulator/Horus/right_re_ph2.png | Bin 0 -> 44137 bytes .../images/simulator/Horus/right_re_ph3.png | Bin 0 -> 44170 bytes 14 files changed, 28801 insertions(+) create mode 100644 companion/src/images/originals/simufront/horus_slim.svg create mode 100644 companion/src/images/simulator/Horus/left_btn1.png create mode 100644 companion/src/images/simulator/Horus/left_btn2.png create mode 100644 companion/src/images/simulator/Horus/left_scrnsht.png create mode 100644 companion/src/images/simulator/Horus/right_btnD.png create mode 100644 companion/src/images/simulator/Horus/right_btnL.png create mode 100644 companion/src/images/simulator/Horus/right_btnR.png create mode 100644 companion/src/images/simulator/Horus/right_btnU.png create mode 100644 companion/src/images/simulator/Horus/right_ent.png create mode 100644 companion/src/images/simulator/Horus/right_re_ph2.png create mode 100644 companion/src/images/simulator/Horus/right_re_ph3.png diff --git a/companion/src/images/originals/simufront/horus_slim.svg b/companion/src/images/originals/simufront/horus_slim.svg new file mode 100644 index 000000000..63123eddf --- /dev/null +++ b/companion/src/images/originals/simufront/horus_slim.svg @@ -0,0 +1,28801 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HORUS X12D + HORUS X12D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Activated button= black/opacity 30% + + + diff --git a/companion/src/images/simulator/Horus/left.png b/companion/src/images/simulator/Horus/left.png index 4c1dfd34546af4444975aba166b984bcfa22413e..51d2cdaf430c1b3f8090c9720f467cedd898f575 100644 GIT binary patch literal 42819 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 literal 41117 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

f3no*2^xzv)-qn?5jxeWvwgNgFIc4iP`tIMW8 z9LY!aMj;Ulqms41+>(rn#=ZCL8_yQ!`5~(xvGs| zrk1w5W9V`+52%)e-(EFr}HgzO=jiF zb2?)!X#J^jeafeNurmah+@b9-^au8lR(1;vN5WAsuk^{+t0Ii7 z@_ADa$UO9tr5Q_YE>41#sb&^3gQIS>CL$wwq80?ZzxgP7rd9-#4qsH&*2IVYAkoH< z^k%<4J16AI>erc>5$I?XTiTzu-*16i&w7}FT+(gm7m3Si47|MeJ4@fW`T5Hs%GeEH za7oksS!?|L6Syi|rm=~M$(A-VQYac)sRAmVzG7+4ZmGgLPOH8+ zhzVt1myeWw2%-=xvN>+8J={Tq%lUNqJAHuClK{6udJt)sD3PDLh@-x%)XLP>xMQZU_Y&5BCPjXQprwGt=Ts-zDkJY8-o+j< z%e9w19!!WEPj5^|f4Plm4SiC;z@~&yPzw}?PJ$L{>P~z*?00KNKCL&mK_gyY^SWPA zl^07H5k-Li6gv0}Q+4;EibN%2BXiJ?U;wm31k0f=IvQEG&24gbqW@hpZsq0W1*pM1 z0cOG=k*5NYo>N-IjfqOQKzBH&d-+z4s%(+il8#Gcx+154V?wJXufya77 zQBB_8UY82rAbr=~?ix+Z%wS^$uRurT1C?-g&h*~|XEVdtL_|V?JhHJo?z;@Tvklhp z5@~XF<>Ft=2%9yR2|TiRDlhcaSWDWu%`+MSrx}B)-M=%rxjhUS6>1UWGV6sCq6Njd zU352^+@Gy3{4qxbcCy3c)h_8mU+2?Nh1<*J;AJqY7bLKjG6^sMxKGQ;_|myP*c}K5 zB+43~kIH_GM)wzLJKtXPopbi7s;X-H13pfF6Nv^Ep03FLVnggB8vpNC

jF8MdeA zeC!xX(npZbzN^omTaT+7RS5-Mj(}gg=biECUxJ9qg#u>m8jJ`p5w=FZRn!-OxnG_e zm8fx{CSSj+NMfpC=H?nDkmM#_2B86wl+0GhjZF}e$!o^cNbsTy`UB9~ww`jkQmmeg zTUWJ*RQbLhKWyQ4h5<+q8wl8%0GN;2B|boXQr&e<{#|({m=XLcgbox__)uSuju>!o za4s%-QMZ9;R>3oFceP(LNYYFC(oMPn7C7(g=@E}JJ@N{hs6H&o_8};k9IDK#H0i&G zHn?}G*Gpl)5$W6(Q~5-poz(JODm%UBM4~05!2uHBsq+MpVfN0Bz39IPQED6q6*Ikh zWuuUTBm~A(p!27bwa+{DZC4z}g?POl*p6CUEQNTsRp+@#JWtqPR@YyNJMP=h`4&z9 z_U$Yy&e7fA8|HlT7}QIUJZziJiEVU!(CuTtz~1z_@%wMH z`+Njf^XWuhtT>3YK6UBeNCM1XiR+AFhi#l1+M>QDx*JsnSe%gSrtKMes$!{l??Uf5 zG+wVAwzKwoeIjA6g)AH%Yf7Qp8P$hAT4Db|5>KQNxe>SC;lzdd{F-mFfVJ*Y1h=vF z3IQJD!rN`J>9Ygl_acq1$=dqF-)FB(gIilrA4w93YCy*OOkBJc;@Z(kH(S;Md&fM`j;LwGe2}V?z0HqfJ5zf zhR|<3gxcph5p>Y$}XA{?U>9~qX&g*5HbZ}{j zKrx4ZRZ*0>Q{$V+D<_LV%T9z=-*Yz?1Y4DeYvbozc#vqV=XrK4w?d%cI6EFv95ua_ ziN>6BwBQJE7UO8>kBYmS81!mxfdZMSWrUu9tI>V7(AYCS5780rqW%=@MGV}!KL98I zOBp(Fpn1F~^#I>}&hyItMf$kcC#{st?AG%E;A9kY=J@;#2_#RDppaFGiinT&({Z1s z5-W&G{m#|Wvb&&_#aQt?Z+E+ylvc)qb*cL8wmPbf9QuXoGjJBoyzcvPSG~;e*HNRd zlgzbZHbu`v1xG#(OEPs5ztlGSSBm&<*y)Dk2_?8JC3^XP(ja7hk)Jm*6|v!L0-B(W znKIS09#0^05uy-3ew_6W8a0UdP0;`Oypy!#TaJ`38z{4I404dz0qZYfN`!5eBaRhO zXaz6x80EyOQ^ZakQ!J3wRZA3&#?MwCfCH!Jb(NUYx3si`ysiX9jfZ-JXRb^>$4}DrvF2X1kIN3E$WMVO$rH-vzL$*jlE`JXCMc2MiBX@Sqg_ zA5JU#=8a(IHBA^F(I?5!bmpwJ8D8Z}yj^Q?3TGuxgT9Q4=Eq>&SO&mu6mKjjU<_CA zFnwy=!7g7ATImsiq;f7DB9%DB_-Eo&4%|tT!I*S}_Tq=+3DMu>n>KJ-; zHRp^>UT2;Wl{hw;Fogw~42i11=qN~3SuB{{(1G;J*L|*3=x;5?(R#9S)5Akt5SZs6_SUL zYCLS53aIe&q?q0T1Ibh&25kT~2wON<2f<%Z)_&*02wBQ}JABkH4yrMy380QAU2E;0 zH8nMx;^@n-AJq^O(q}wCzz)0O@KncwV+57dFp410M+~2mS`vJN9B;!R39VC498-5T z#Z}2|W=hPw|Ct+*mQ)A?aT3?}Mt1H_N?MPvwKtugd0rgsi^Pum-2LI1nZg#|fF^x_ z(D?qnro#t&>c~RW_Ri{0ts^eLWkGj=Z2VWRWQAk$wH}4C0XrN1(4@hqM}-6sK@ta} zMPai&pW+b0|MAv7y$YkOxnx{hm3(#<+bRdwR9E{INXKlwJ)gTeFfDg|!_A7T{2~0u ztK&NBd}EvRowqQF*X1Q`EjJDv7OdE)0cLAU>d*un!DrZ6H42rg)4>(I6|K&{dM*db z6@s1gnv!=nEsDUV2I}7-QH@iwK7$a4`8{;B|F&`Oc|xD8G-Db({{0R>2D26Cc|D$! zAZ<-eQDrQ*txgetZ_nG{&fnH>P9wp>@z;Wb73Kz#Cc4n!Xeh>d8Hg=hIZv{CelA2F{A=#B)z^@3{LsLm>w-cStx7cM_gi)ldISDp()}L_Jp5__>Z@`P8<`=7s z`M~vu==Cmu166z8pP{&O5$!aa=Kur$icjDm+)l3~OFWOrbAA^TM&8CZ8ZS%($@O~6 zV}k0qTqfwJ;J`KTeMc=)%IZpcpBWNi)*tU0o&U<~ZI1zPLSX$-5$LrTFtu+xo~`h` zyq&)b1Aqn%pyJM-mp_{;>f^-iLF%>Q<+bhb$M)BGZST9-r^RAQ3jT*X5f%z^3$0s` z%$_7b32l1sc)naPi4~&{M)f}U!m|^>Muc)12^2RT{+SCGoLk1X-8mk2>s-E`w`27^ z(l4R5>5ArG%6Du=3)_ZsF@zi|O~mmPV{gvs*f|SRnRVMT{rJvP|Ev#G)otpr=;Mp< zpI#w7Xmqc4S$*+4Uza5c22$|^|=Gs>Y@JFnytd!^X@;sk@#%L`E)z*CXld2It*IE1em0tBhg?9D!D%<)F zkZ}->vy1S=KCeGW&A*MDjcpokWX}Xp8x>ndWHXj+d&Za+8{$B;HMB%uFf)(Kl1m67*#QH9KNJ~ckAlYy3tk4po!r9isQpYs7Ms>%g#goE5+4! zgCu=k7_>i{p-;WxKS0%#%_67*YTy90`LX2#;k3-76dZj_ui!_nTKUiUsA8G&q@3F{;W4wq^m^a z^^kDy^Z(c+@aRoU){`~!uIH;9?jn7wBj+vqwiJ?hD4+0{&@iQYwh4nG`3=BRh z|H0!qEzX^qqK(C;09DJCA|2=dXcj$Ki|i)oX9WFZ{v69mV+sjlL%?7b;!tlg3^+ro(t-nB$mh z#}_uGEb0mqBw7>7`No^CQ!}DgHr~5LgT+Nhx+UiN$A*G{^tJ8siSVIv{fYSlxrG`l zbw0OtM!Hl416<9RH+zl;fwJwQ7ndqXa#6No_R|b{XHBb(g z(Z)4S$+UhNafGayNG9!@2oDA^qk6D-57+9l*yh^BK z+e~b&SwQ=ZTC_F<13upBcs}bTeZM3%ady5{iaKoG&+h$uRDNk#@x-m85N<_xE6n0L zKa=W$5K}gm;9?DTK*Jmp7)&W06)Mi`#RIZ0UmfthH+bil&#m=yti5nRj} zOmuWCGVTf?-N=I*-vEl9$pRo&YC5?}4d$3qmC4fATF$7#DaYu@0F805#jMRvG`pkz z`pp1A;PG6+oOgiQ83F$|PZ2Bqo<$^eZ7udjf~S$CCA`7gRVpg1`{j)4HzPq36AZ;4 zN3C^rYFDPGC|TO9;-D493hFZ3DXa`5&{*@$l6C?o=ZK1gLl9u|ndL7x;^&H?HL>hQ z>3@GPcta`O7gLL(Yxct59AW*!_G!wtSNT&<{t(`5A~U7fBqWthV`fOEo}ErzQ990gZ0%x@JT9lnPc0`cjEjqVY3_Sx?gqdK z)J)sQn-Y;|R_{Z(jS@4s%JL5IasKL|VRdT-ciKOQgQlYhS-RxG%)kKj)Y6#9x}OHW zL~0HHVu#Jf_TCE#UZ4C4CYMCN$|C;nZQ7ULMh2p~6%j}{6z;d`JIX;!G4X@!{6=At zjT=wo;zwq8?z3v&94&suh3Lz~VCa#m)&7d8?a;>ITGM#syE$LrKkk9DMhc+#Eo6<9 zKaV)=aoD^t&b3N`gfj?c)*==OZti0Uz+ zZ7Q|vyT+Xqq|be8SdK5rKZiJJ^ed`v`T;hpMzbsB_TOEo|NiYPAOuR#L3J8NjB0|( z>amb-1;l3utb6W4;;HkvJVvZbbFO-2;HzsBSq&r9_$zKZ0}Nf4Ca+ru{6 zlM1n+sV{#mA3Ba2Dc&DCd{}gfisdm%JiXD<8)oCNmX zsJU1%$GPZOwI}QWxpB$0x?|L-A-V4g$Lh1+|0am!?51-`xu5x<<)T0Edarvoxm{9# z_1ir*Ru4b;hl`7=*|!Wq)nlE5Wed)@U3@xni$>F?(pd|NF%^mO;t6UxtI-NbW90P{ zC(nKdB_n%1UKgT&EsW7`vz-h>+jiIJIyyspdvSy8wR8bSFAW0v<(b*lCY}V}RS73F zDl3?taJ_?;$8EI4u@f=GCy=04wp%Pf{w5WSnhT5FQ+qVOl!I1*Aq}s>F1R?=HBDjR z$3{CXMeKFIjgggSb|VqO-ym;oxPNJ;8FV#v+%mt6oxhCvM;!vR{6g?sZQ+f_^f96= z63z8*Sc6pCn*&}H-SJy8GS;N+;l3^f?{N-lA+Zh0C=*b(0x}WAKj&6^O@02Y+Ijuu zdl-pq%1DZxqqRChn6ShqPAOxX0`M$H5c?0SZmQrYlcmDzAV${4-*yR9pU4eIzFs5g zjrT(3xg8CP3=~v#LE~Vt}eO9E`P|WU&j1`V>{gyMJpuUxChOdI1^AlUT&;`dy zAq#D48cWB;oC4Kq6R5bNqY1@w$y!ii_)&b$`f)GAf#M#oI|>R}36?G_ zJ|Z}1>*tYu%=UcM_B>dxw|afB^AUicmp89X@kK+9vfoxMI7Q2#!3)-0HDw0$2lDAN zsVWgaCfJ^E+AYb3DP~s8DDytuG;>nAfna@WC?lV0RQ>Y%UU~zP&LlY zJJ-1FfWW}OCyZ4U1HGVOyg)gd-x&U7eG@^jl`bPx?_tT2c|jW;{gRG-HFV-mhZy#7 zYP>bazn)caB=PE(i=X32dh782JT)t!h2Kz0{t;El=bVwW>%SDmmPD*EY$cxW52qRs zdD(v_svL$P5|yI=`E~RivGuohuRUMr2%ql2#?P2FL%;W(<@Ev262(pHalya}gj-SN zAG*_BUIB`c#J&?mR)AG@ zOyNpoKbU^hP)UhKuqTfnIEd#pu;CE%Y`4PoK|T!kOoX+ z^)Ax?b2Iw$6WWh9R?TF}Qx`(Jw#!eoOdJy_t&c<^K7cjQ2t!ulKP`j9*9L87-ZDnM z#_~1YgyBNUJebNKh{Tdi*{1jjOp+C=8v0@78b(OzP-@P5d-6~`u_o1w-|spZVIftV z+o-Kvzoy3KAHw~JVj4$0AG(e=Yi_oYEgkrOEkG*TPyxdQG(OWmN7Hf`eozumNtadg zRYL%oC?$w({lBfeQH7VlDsSW<_MIe(pL!AXPo+Lrre!MX+cZZzSg6C{6q1 z_uhRSu|wB!O)NjfCkRn44ZO}hH9OqUdoS_u6Y)I6>7YbJRgXvNI%a;`O`}@f&XR!J zX&Qd#l2hIR8(UyvZ07ua(zcPEC_1VGx$0$2=M-nD5=T3lhJO?%K`^l)p*Vq0G4(~| zPbDs)W9w>yfnsYpv_yh#a9*1rZMgh~t<9;G@5R7<`W!`xRe|jZ=@^k#lmb*-{7_+U-Ch2-xxLj3-m9`u9)0GNggRysiQpb~P zTJXTXYcH#_Ra;!v&X+6yZ3R@J8?+v_%rZMPPI-PwwYB65%ofWOWA9ocHtsA9!qFd? zne0f3@v;y+Q1Hs_q5OtNZX*RUdkZv)-i$(qNR|m?>H^D!s{?2Dh8qP>XE;(ue3KPT zWm;owVP=1f#;;9(YJhg!LyR!K9ggt9X~taJbwoC=u1*n$AFdwQ{?wg2OZFteAQuhx z>%}8!bwSQKyuZw9TD4Xk*4qNt4KP2b+!FKf7I5 zHlj(!Q+aoe#P0nwX5x8z($A|p_gq+7vHtH z#N7xYb0nh5hC6FfoiuZ?za7Di!so9Lf;Qh) zh^+ft83+VCvqNjd<pXkIiX6+2XqLN zV=BXR%V!YcLq0^76b3CeGDqjnul8LhGDM<^pp_ysr&eM#w3Dci0gw}%G{gciGV-uF zC1}F3k(?~6gCBqfL=jh_FpSrWO*~AxWWZY)kHsmzv8^2Re!Zvg zGF2jsd?b?}q2!E}{Nxfu7iU@#{d6swx%GO_T6 z7U`do8wmpZ0_^nBw95*XSl(X*cieaCD?@(srWAy6jpjQ07Pby;g(|ZzJq{d+F9% z(g&=uezhVui#fM)%2l&Z!buQnEdSGdtBC!iuQJ++`BD!9t#`aHST7Yi5p~PBB@Wus z>hl|ccG?kQhgdn7U%LGDE8#X#=@B zQbZYyGi`+Zfb->9k=*mt_wg*mnK3<(FqbPrz>hzcLyD+Y^2NG!EJ=CkFpz=$B}qLt z6=O>+Rm}TDVqZwGPc`KBVlakr_8+tTyD3JZ$ju!7dpbIsPy3k$CKw79N$|$jX9^wQvb*q4S-CsC!X+L%mfismm@? z-VEn`J$^67fSdqkJElo3^4Hfq6lToi1gI1_n}{#i>C{O97Zl&jX;qGIZW-V${FgLX zNEZe-b;S((GNl4q8Sd;wAicNH7@JaXiYV;%i`c0mKTF|R9^v0K zfl(S!x*oOL;+mVH!q4~m?DtudN^9iJbd|E1g+I@9ugL4vYTw9me*6h%QivAnlhvMb z%@c_&Cha1}su0N7E3|j72dUnUZe+?uWFTZJUkeRH4IelYQAV0v2>4~!!KW{!C$LEr zpbz=9TOm!86ku7`Wb548NuIvfb+~7n{5}Bg^vU0Q5%UiEOi_*$!H1%8t?P1w0##^f zRgLoNqT#(ghkwRvqBsSbTTi|z@CLfL=CMPn%l{@*i#vf7TwRF#DnD5 zlW0+6@!;Cjuok}(1}%YRXcGjGzg!r+=4bMalmB|bxa4FZmwjt;AgA4s%Bs1vnIpy~ z(FKbkctprh$7%b6z#1CJzwX0nEavHa!Y6!V+6IHll{5?@Rj#x~r!`N_`|f}S4o*ey zK9F6Z(fVfes!c+!tRh-Z0kapc*X=Vq`eN7pXuqKHa;X#X?zl9W%q)6p2zf1MvTKev z!)g(hNwC!pMBl)td`w#PADvx0LpVna{1rN-6$?^@OKKn%B;+wB@6eg6&G{3aeyX}2<+)#O^~!GVq3ee zH|T#Xu1fWiQDHjI^h4Zv8sF3PBOj-k2nY;q$@I_@D5MMS5IJCFPz<^hl=*+&wd}ocO|^LgGMZFos;w zxFD4?N__ojrGSom(6Dt;t%mkdw<bh$crH4%|7+t)(NqLGV!X!$qh`k8Q zJNaM~FOe`D%*ig9c64Bf@ShaZ_*YL{Wi-S5gnCOXa`-Pnu1e2qs)Iz|(e1g@u_X4$ zK1~;l+bdYHzGtNHb1lsuYCY{mZPtg9K+(dm>|fvqChpg2yj8~tjEV7vrtV+NY|&(g z+7*}mm*w*01Rwc7(vZ=XouAhF=*p-s1(Ix?7QTS>5+uo}uOFQ7U>}pGSi0jT9`vs$ zvnbup-EJH;;~rB11_=l5OuNP=VZ$Z}i09&mU{I%1rNKbndA??-76~p@U^*}fh&J0= zM&(XcM-bR7ut@fHeNEPnX!B0#I0t0Sr+^%Fv$Ao6~2pde%h``V69m%KdICul3m&sZXmFO7DN?XRb1?R8!4z!_KqEi=h zf(AJVo-q|skkgWl(-JWnIU53P*cQy9VJ-t70R+qzRs4|Htt5#mLOD`6qW>^)-Ir5= znD77&@(nY5nX!o@ry8LZX3bp>$3223A)>T?8yHA}XqvFNteQVe&`t6#V^chq4Htnq z@35KePVg}vhfL$~Dhx&~uGUI_<1Df%Ct4HG@Z3q>a3vS-fy!t8;(ypr4DJ7U)8{?^ z^6ca>U+Ow;eg=n%4D<#`?vaEfCBQqLm?@}{@o!V^w+ zyN9FVev7(ZmH1@&lUuK)F8OS})T6WW{XIEE8r5h>JwGF)6!}BrzOVt*{2D9XkIEGBR z{chs}+a5v{OyYH%XB1{vo`CoBtG-6z`6Ua&-8GN$L!=PH&0nvaDE5w|PV)KYafYlS zO*rd13K{=v$<_8?A3z6!O@{*!In+hFBdUpmGj?p}Ee@1(W1cvfvz~plLoG)Jmy~H! z6kcG33zb2#C}h|BX3N96D*Wc-!=MPtSijBfEK5UWRcX3sCKa_`?r{sCi|Whclw4U` z3)$x7n0MtQz*YNN{u=Hs(qY^)dWED+gs&@485h7&UdlyhY&1u6b}A`nMn>Sw#0JJ5L%v>!pe0GfhKZ%><{Vvug)x$*nJ%%}j);8ZI6 zDYduZ&l5}>@2AD|0aRif3X}gFVWPUxp#soe{ExRp#vDx{vL6n%n=URMJ-oS`+6q~J z9Oz`XA}f3%=dHDeS+%|IlFqcH!+!vP(9gywWHAOhN@NI}jl}wz=lf%DVxaN#5#5Uwga5)1Q73 zOTGRpKHhqNy!qqSIo<7hGbV5lBlRP6>qv3%R%mLh`B0_>t=P)7-}!PKGeAV+AI&$B zTP&ZqvXaUT<7j7&GwM1>uCF7c?i2K+5(T22|X%LMj~LAU#?gVOBxz-E^L==AD6C|2LCM7PT!^p~mr z$cK|B{$GjVrax<%KK&Q1X&8D9C((zxI~W7GSMW&eRrRh9p#_LGWI z@&WU)PS&%RFNs?Fzf2B5X-W_+wcRz~1n8KD9nc2rpTOYC?t4L|o9Fs!z4^0HWh#$h zHL#QmGFegf9|$(#YqGY+b(Owcs5~@H6Qui-KjVY$YnVgl_PW9-cFT@qnB&bPy*NxBCC8Q7#6Hc{{(cO`3?*CSU_f#P~u^D(z6l%~FL zghqw)hLn&I9-uT<^|&jYpSKmX)DEWiHml%+ZH6(;YGtR33^eyNhs)Cct|;zbNA$Iu zH}p@npHXY{oQ3I`@b}vP)(3Xb1Ed8}MdUF|JV3XZBguXHE6d_b*^6Kn;O(P_efi7&Z z1HfHjzu5;5tg;CIX+z&PMEwKn%Jb73YO-6P|Hu+w@>3z(?JE3>C0=`NZNAm|(Ji(v z&@Hj{;IbylY``3J*l=00!TgpGt{)>cAzHP}Mbhti-Wj{Ll4*rd83C8RQWJzG6GJ4H z8wDgJ(h_OOg1!D=J&?wp=;eM4tS$qnAQ)a#rf$) ztYJ4XbxfW}Wp4TYlU1+B4%H5q+J|xL8T(NN)btgQjc$A(*CB%v4&N?^Y2{Wi?1)?$RFB_J-ZR z&_i`dFvJKc2PGPU8SxEj1ylbyNOtFx<~heNXZYJ&L9|{mz`!JUo=dviGITW~>Hmx& zrH0R5+~?~!;idM%9CoZ8h7l{!V(4MgW>m6pLDw-@5c-_GUK^Q?fZiA#449M- z1&B@vJSVo7iC}6aHR-nlqdUl^*@w4cjN@k$e|~N-e4{Ya%GXbM3z$1> zWz{krSRo&2`j#W?O_%n&vXZ&a0crgjXl%A}u-%DT-BR1b#hVt}H7=!PoCksoF@o_V zFqUVO$Gc&P=KHC_NBNe{?~%W$dp!xu>#wm(&uZe&I6jwpb|#q>ZJ6ha-zpOoh^+G< zoTIqTHcIulEEA`vDNe!UL9O%LI?jTvQ#u3!#Z!brQq&206R64g`xk;B1bW5T2ZV34 z5{fS5skL4`w5}1aKTLWv=zmfFlRxix20LX>HEFD~AvoO5A+wEry%VMHKiX@dcgw9^ z0qz{Ms3)&olx%Qq*lpI2cBCU2edvapbXmplxT@O1+qCK6BkJlz`LZ*|zW*%TvTD1D zq}jLZ5^v$0>R(i~d6u1Lr>=pGrw=@jIzeF4W}owu;&AQA9OF|2K7b_NJ_N>qHa}EF zFs_*1=ca(PY(l)f1MpF9{6d#Z-CurUDDhv?%TuV+N-QjJ%iiT{;BJHfhhH(iE`z#< zRZHZ9S)kDn{5uy4Vj`g*Qn%UHW&{$yzTCwUGaj45A~`o6O?ASLVsgHErF1m>w(8_j zKr^7Vwn91)8Zi2s53%>`E%^R&T?=!9=2FD7?u7}=ZJ)IoopH6XQ8-i=;;qw5f``d5ii{fp;k)u z3eF>*dXg-S?#X-C1BVv4@RB52NUC))li-U+X5BwLb#M3~y^(UD;?ZsQGGLc>mIn9A zHgNHJ?n|2Kcye|&cSJbI-oVX0dG1njVpQ%Laks>ohn4h9ALRE1q{0$s&eycDu(Txp zi|PW(K&7>C#mz6kAt?ION=f#o=X()0S6Y;f^$_wm8@#O`MzN#iC?h#IdVdw4vzzVM z5gI-#Ml*Uw6~?F$97G0^f_4Nw^1N-5OozoTsd%HqH)?g@p;L5BE_${DB|D1D7ESQ9 z=72|LmopHqWL36bwDd0eJ(%r*VLq1Gy|=fQ2l0g!@Bwril9eTrS>5Vs>BX0E4V0Yt z^Qz%wDdh_oLB$}190pNQ7v`o;H@ZqoN%73{Ktl2!`4rjR*Aigoq6)z{+>|y8SSCbZ zb?q|s7BTe}Bakmh8>4Kj!I6l*=W!H&3p43<$zxzMwCR1~Q+`UJ_(M=&Hc~#_h=tE; zx@eLFd=`-UIBux_+(>41l+NOk@zyp4v3hMH0=FZCP7%n1{5zj2I$J6`Dy*%ixH)Zy z8{kwXC_Q4Jf4;5siVt#?Qi@^qJpE7|WGCaY_m?X0r_J%v{?S-wvV|--5}9BI`QOVk zsp0+Wy%-*c8#PJ=#Pm{g>G9Ud_&d}di7>61)}s)|CkU|Vp<85BEPU@M6%3Nb%FJq+ z{!eNxY;R&~B+3zjFQp{*v+Pm70mk;bsqp-Zfw*PmZ^e?p^*8BvP{%o;=Bb#-M`Mfh zb&}L5PXx6b4ME*Iw3l4p^Hkju)he{%)L$h+a8w?c`2Pn6O|)cHtYq~QEWmrxg4kiU z4_sP474yZ2|A*}AV10{|lA1D^W1V5Q(7+%N5NCsa_WsY@{<{6%emfXf^b<6uMon}? z6AR!H&wKtyJE5b`U|KfB2-&S$#hgVC32&*s9tR{bpa26R`)b@*R%mO>{j2|)Q!KMk zYVJSp;gs+ z3gQ2(?fPR0njEIF$GMc4zGAAG4XSa(kpfVa`;>J1u)37wXeG{dmUMV7XAMQwE>ljj)> zOpfQ9`_2A7>OYXVFCbsN1O#sR%cm)Ow#Nbl$Z}bK!|7s-O;Gnd$bpgIFTDU^+hwQ4 zcg6k_MpLJ>+;+zzd=)QUQ&(p(HNrO%IR-UDA2nTPfo&znItA0BUZOVu6E~IBH5S4# z7SS4#02}s;-RXSI7^w>?lend$`LUHnd1_akfRwG$PAg>~@vs@dRO3Epe;$UUg@0NY zL1ZBSGQy64`g$&=f8(jZLp|?h*crRJ8ZJUh8_~`jWbCJ>d%`ymmVaXOC&j~)OrQzd z!i!>eucM2)A0_0OICWNF$}htx5tod--N-&3E*6gXOgoIROY(dnSm7&^O{itl&osjO z#cH^AaH1Asxc8tUL|i)s2^q^o&8RxG%x+{$vHZw-8XMnR&+cnVNOwa9vs-qk!Rv~- z!~5$!>BkF3^?mrF+v!6*7)z{b_BQ<#BSi_g1MuAJH{kZ#m+x{HU7XXpos&xPGy426 zlwb!M5wJhlc4vtO=n(r3Et)PeZ3p8!>KDS_?u@Tmq~in+>-`)1-+fUbdRlL*_jbLdRk=HmrA+M0@CnOJDqEN47--Pokw6d{CtpKWBQK!^}uI24ND}I>Xc+d0&NX5q{sEr z)LYkF08~^=v$&6jJ}$1V_UF7E=4>5IVd#!Ck55jynwd#DAj>a9mtl2HsVJ!%{39pN zZ^^=$p8cfzQ|yS!4vc@?%r-H`G>%RUzt-we?^jxGlT&7+48lS)B;RoiR*M@ivuf_A zJ}miLY(18jzyw6$3Qj5V5t1(>wSy0qhXT~rAsspyI&Qnk-|esWM!yWVF2Z#7%)Kpusg zpMfF}L&^H%sIyA9C5SfLOI%OW9lpq1TcIXDtuRceT+Rs=TZwiJ1&>JA;x#Izf+|2Yj=;!=_4E3@^PO+bnS$iVmf8k<_gPuQ0J1g$uf`v1 znxOWH$NeP#TCHb;3SQ-rbp8EFrtCJ+QdxzbnBuR^CR!K`IUvXgfEyDG`&ys$SbXFe zfuq)dONR4A0$GqTGDW8#!;2F>xL8J`Sbhp@{{MQ-4Q@g+Y#WI5^D_R31velxq&rRJp}yia`jQn zaT)5KWPd5^PQ}ju4UmwyU5F`5mvMwsf(Edgz2Y+nrSpcrFtf6x^7@jP zlX?ubWzfmS6(;=4fU=tLk$N5)&##g;UZRJ=unZ8HX5^^ zu?1g-DA6n#Vq=(~M;xn}5`D)%+YfZjtZQYPRCmOT&Z_?|qURPwU3Q3)<%Jws?6?Vr z7-$vvP=iHKC=HNdtmsEy!Y%3btws?EPqp~DgY|7P>r@LefDIB9=0~%kyZ6}U=t1~gY1=S zLm7%z%r2tML4n1Aq_93{|7zwKK@6(K~uy73sEa1^oq!#F_JS!`d6_`51KpE zV~O!tJXCI!KMg#{7FpAz^P%|U;CP`ca085>E;{yZiSrp z_Mf`}Z{&x874R6Jw(@R70yg@kQ*NACqB0nvkz>!>BXxNx6IJ*x46EEWo@pN63z;I( zR9Kv+#JE>`%5y1AsUL!bDQ_Kz{fDAe>^d$#WxH*`L)%m%&nY4=Xqww%;~(wEZ2NK0 z=wxZPLH~Nmc0~CK%JzIPy73e$EKnX+s=dUUERx%BK-%At1&uQjs`WSBC{kPzQ7LTh z@>!(#@A2uravU8@4W_)cuPpgrF?~rXzIs>h{*mJd6GVm$#iMNRZZ&?@j;r5w=y}dK zp5+J_FWTOoj)5UbCpN6xz$K>!FI%|C{fb0`)LM5>XJ=U)*Z{c?VC{yl+L7lGJ8eIMGnBGiSv|W$268x;0^Tufrpn9f8ZAy! zy{4IVNRRGuxjFoN>CY4;`t^ZV1#@Q5U!;9@t{Fw>w;ycw_PtZhN_4``@2%_#aUG7< zgm_zbd%N3sLfC~DD6CWmm(Wg;>DSZ)cTgh&2LqHQlzIb_-_EpfQr!;*%b%weO8uQF;w{0TC0MLddnU@k@s@#N#6`?R zu>Zz;YV)Io#^a>N;eqs<9g5pyniMdX09h&bmiEMtDD9KTaWO@T3_ChwG%SBB^t?Q) z`{8c1@)&92cIqJ{l5yc5a>MWIeSE)~0UAXZ6%hu5Q4ZNtU3s15hYK+o-2*i_cRyIn z2fD}KD`nx-z&pJkuePFTA98o}4`=4cBf8KLtPTYQ;gvr}ll)BWpE6!fSSfG|+>d@< z|JKH;(oY|S)t3edl3Fg#eu(<1Sj@ zJC6=4EZQyqz0%eORQaJwqo>{(Xlo-4Z^Jr|oPFK%ZJ^BFS-ITTQFZQHW!qzxWth0X ze~bUAUf5uETPLCyG5C?0Ou46pXi&u?&^R)^%FiuIiC>6pEiyf<{c|S#Uu1I(yWW{f@_yyMc!+&pKard!YHTEY z>xq)@?!oiG6J8b~C8gzW@YiPRLr`T3X0cV$%B6_!b=TV6LuDR#`{1>Y{Wtrvdxhx#DEYwb|TPg%Wp}UN~wi$u^z9hnVS#H$8(sk@> zdgT6BcAc!2QthQ2y_|6GR}cM82N>>5wNVar)<;+S=2!UuLyw80+IZ~AdrGQ7F@?Xc z)X}E|2phYcz0tB{^QU-PPQC#^#3yn4-whJ-XKwB4L(}?iKh&yJ?ZZ4?I0w6IXmX7! zX9CSMN}OOe$!)uaMQw+aK*KW2Ymd7#ZFB~i+s(fT&HpDw-F9mL2UFi85sd#)Zm)-L z;xYUKU~~8MWNk97jXNUu9pW+cv^SyUR0KK z2!kRwy+S;Kc<5Q(Q!T0sOZ`C+74zMX_U&`1R zw|>VOi#V2$O1+s6sO;%aGtY^rJZRXuLpNt=#VXsg0`$Gyi6n++KMA@_J-9K?ILCP6 zw)Vy#Zt53z-ITAMGvBeJPieP1Ws0TtSO!hY13VnewgWVo^)!6nT0I2cB_;GVfxuMKlm`L^QyiM~x@)28MelC)d7VOhGDzQ0rNW)uCJb#EZ= zrM9sSG3Sifh}Gv`SIjR|7u4F$gNyP|iT3j_mq&(xINH64TRe&xQ5LtxG8JQ&j^RLx zM>f$2=%lIJx44iWRwmxHrzhJs?2L9uT(#MFRWU&}^xUhnKdk{pWzcr+2_eT67DfvnpIWKG04F(5f81q(jzIv++}=EwWEXUxf`RA6oo6i5sd%n7yfO_^i;}PY+K5+V z^=w#6$VFO#^Hupe+FGHf1IWPIS73Wnaq+Ihwzc3p`Bl(G@BM{90d7~{rHn2QlH6FAP%Zd$pg z!|agC>`*YOuVQ=miKg((cgKR=pe!;?11qnBNp7*t9FDxa??=@dX**s42c~TAqC;_& ziWY+JjLuwvDaKoy)pojGMd|Gq9z`5#lf|FvPx!ALwW-Ow|NS@8DzWzU!+LPIqnuTw zqJ6B`?bVcDy`TI(HwUxeB~OB7GQe}+>0hiy22)tAI%*ua&qVerE4VpP-mdcx@G}^p z!+0?ZKV><;fKVfSO)1yToTA>ecx}ZJ?$n)Wg%QbT$ye;jnNEC#fjT^2b~C_5*Efed z|MA(!Tz6Y#{g<0P8PxYvM7JWtL9Y+j6>Ya)0+;*QdHbne)#2OgF%8FfY>3QU02$Q{ zNtFX7x*gI>1OW0td|F&!cjx$-4@xMpCr`TTmTR=0bZ-NX@9ou!mOQRlm10+<@io1# zvRX8;!DaWCUjHFbEPViC##V=aX4iVu>&m>^$+V@v!^KD9f9)yfSF_P;Z8MYb$C^?7 zWuwGF9rp@0oc!ajJXnJqqA)VuDHZbJhr_aRCo?r>HACPFfE_Kp`ZqF5w~bcSoB7+t z1}nJqZ+~xcRI_WsS)>pA{)bLyIVZRsj3!_#>5JC`koy@d*|e1D#f;t zUaBl#o(Iw)&+i+j(-PIBJyElQ zgHeKVRdy^b$)G;5WYQxB5;Wz{kvDPb{|OZ2$p|_+X>))p^CTrkN0Q$DWs%C>!sL&t zS>N%&M%Ao_)*--D1KlzFKxg^M&5QNnUmO+$dlBS7xXGq)f@?#k{-H&bY zI;|_nJGp^6<;hBGL$6L!VP9LEIVW#54uAyq>V`aG8gX3cfpL=XVUOKYC2w*W2yW{C z+h&2p4_>{TSKy!I>UQT%NcD0Tq3Qfl9>pX$P2C!uqmz>DHBvVOsos>?+^W!_sOm}f zqx?$B{SwSvb;(3U1%);gY?yvVD<#2!ki&|64}MeD*KHn&h@z9mERPl>6IFiCC zV8*Gz?}8?$xY7`b*n&X=_J+kco8r?{PL9|lawcM)`$XMg^z&aOI+d20O+-q2JVFu7 ztFv;LqdG2TVKP`F$3b51HaeXD?pS)oCN$?F&Htj(yjU&=)FB;&!y0i0|6K6?V31rf zIVq1q#n?g84g;Hm@GhHc_VSIm`b%@TT@RJjq>$D?n9*zXUL}Kl^w*B`#XfyQD#IUv z+JUZt%Uq!ZV5H%3wVyZe>f2|ixJOEPvwm7brmmkIZaPkbz_s>91L4&|t7)IiMRK<0kFUXcPf==wdTZIgG$=#i^G*K%5ruvh`U_&6g$Ph$fZ< zyB=}L2%$o!VMC;iB#sWb#3j|)8Eww{CTzD33iW}JbS9*}qb**IjZF~GKwiB!d?Ns{ zc)R{Oo}qwU-6lWo(ys|R7W~2LG3|^l`s#^UQS5qv&~x4wkLL_LhJ#67T1NC!8uH5J z3a1KvFrb-*0g(RvGYN;@2UX6wr(Ob#6U)t7`u5Lez9f0A;~Px#*VIU;9H(_B)D}z=F6k7P zke6H?QtG;b{soqL(e6F!BUrECfq@XjL_}ht64ZkQ;R9eh8IiDSm(3g{yGT3OCTSE5 zDP)|F$PZ%{k==1j7BW7Lv2uL-R4bBdgT~92_PP!jxpzu&Q@KS1j&KpRw06Hvn>LvW zjRu%teEH*NjvQJ>B}f!;JL@(O)UcmA;dXJO0KM~ zK50glI0C3Tl|%5(!4d&?=8{nm4))v1p%3do+W=RHknuWpF-Ks-!-Z65zUgcH42w|8 zcVBWIJ>v(h@mXy%5E6ilHz_3VWx=#`Tv$cjX^NY(@ZVeZ){LWPO z-g3xeTJ2fr+ub@JvsdwH+F1~GbWvOXnoC>ssHK)%Nh-+k{q`uH`+04`^&j;bC_`fy zH-x8bHK5=lCsiZ%)(J!W#2C+jh?6mLwqP5v@9T)4_f0Iu@_tND=!V+mkXKr@_-e5Y z_)imZ!{|2;bzO*0RcWJfb4y6Yv#%G#w!LX#w!S83Y!Liuoj)iZglO(51zqO(5wEb1J7U0``(*a zK%t136bk8iG{bm`!?@FYSf3RLP0Fu+x4@RC3m+%!>W8RQ86?lq3$iuNd_9pU*SGx@ zRq_4!6ZK|`Sy4M+AN3`w`EgjGP5UjH1p=71Z>R1bNI-iG}B3BIoF z;C4Q&$DaqzucJtXrR!Ee3UQ z(gDme;#{kpN4mbgf1u}~iD3gmCwmK zhCS((<5NMhs6JPVCEsIivbHRd!%qqff*wJO#OR*hfBv*7%FNFhVryN)G$Ij zTmo38u+W8(&>{op*6mW7$m)sWk6UZ(|GJII}6az@wC~ZKHXy$4O z^$rxCw*a4oUlKxMf2^Ni*77TN86F#6ahVj$OV;@nY%hUF4JsQb{t-qG`&n63k=+Bs ziX7oV{Mb#ST$g1l&erBI>-P&?mnxTYdG@Y>-5;LiHuT^d!(z~98)t@Yn36hdS)J_9 z@g&;vf&e77%e$jcx@Y8e#W8i%E?T9}rM+lotxfK7Q`pJ)xO>G)O&YC;^*y}gS|r%A zE_R{p^mZ}d?Rc_P58`9g;F!#f$dXzTDo|xJlqRrQR#vm>S(Hy4zhLWWz$m4ot z#+VCUK4yj*dW5x9M0zT~^d^%w?+dJdE0sQrRJroXV1gQIE&-k#1*Yf~% zP;c|y$wlMW9yNGMcb49aTTK$yG^qGGA)b${(}|K?Itc%hO{Nc;`u;wgktCr}%WqOo zQ{^H&)EvI)!^Cys$_s{45IfdU$s4jHT2PIuIMxXzGR3EYQbj$pQ15_pBNYa~hUId? z=73<7$fTP)d^HX*Y$I(}@<7Nc>xqTgBtzx&A!;f?-~WEie&~nWEZKMU+c+4H6dd$Q zkO`0>Dl#B~tIYsv(!V`kJ-jiq6tllTT*Yfg7pgXeO=(T!x)Sd@?B6dh8`pr`qciNH64OF#(%-*S?l-Z>aq2iF=Ex z3V_OUCH!(33_EsB%8llLtgXw4Q+~Q2Z@R0$sQDT7+2%Sp+beEclMQ|)Tz{cS z6P5SkwMH{mC|yAot^~H`8@hitM8UpIywUhomV}Mrx=^R0yZrqU`-=_I%g+Hn=jpl94&i&#w=Nj9NwvDKJ*+{{&*1zhIvUG@UhFilx(@uf zm`#61>Qq<}oGX$Z>Gu-l9RBX2yJA^N@KRZ4<8K-RSlsLi0VejZ} zk@yZKGcjtTr-KT$Sm4T)icV!r0jwF7S|?RiQ-}dfpuVW?0C?Qg zW^1xsHSm2~O2`z=R_fwxupUwlT!EGxQp8Ip%E{RqBo-JC7zWlzdrpo28A~g$E&cj= z!UY_P;+>J0iw;Oj*P>l7#s$F%gK;$ zMJ5AbKOVi$v{6FPyEJB1LV5||<9uxz0R&s`Rk@WqV29BslYo)0-(5e!ht`Laa??l{ z!WEKd-XguK=_NNYpQQCH>%&vEINlKxS1D%T80-75v%rSPu`c19n!;;WAXLVf!zZ*E z)$g5>O!NpD!=uq=%sw40W}NzKN$^1lmZc;(kfxQlm__SFvs2GV47SrU4}cBO81DM2 zn)&4}$0d7YSCg+br(%3cW`EeuH&<|47;{xg?;nqMCB!g+^M@w9PJjmc}?)ckRw1Y5LCAhVB!bhH!PsL$%7nCmB#pO!K#Qk?pO_o!1y z3|v;8=DC>H9edK~>xN)3O#YkW&ff$|4bcGwN{%BfP9qBzjG(XPf?l&%KXRu-q8o`k#U@?~^MF6>(+W|g8;Xhe7XgH*-n_0EJ zi-Yw3bAbe|tfOxJhXGx6|39-e*n51_1@PmLG0R9JCBlhY?YSZdVFCI zKvCSatS_qvGyAx z0P8EPg=AnFgp8$)I3Xvd9M7T(g{Fd)2YeqCuPi3PDauv50vyE}0vZY@x6c=1lJAOg znRwAQ^pxwWk1i}^Qs=IE0>p?5RbDE=DK5YnSQCSl$z3|Uv7dck2yr{3j(U5#E#k3$ z4oWE7X)a{>sDX8^Hz&leyk(s|jmAGc6r8^&S7LavhN(yAxVlBB_LOhSRPY>OnIyCoTZTD^8j(&h9LaeWrmgNa+Na6(NgQf&|yhcVMiEyeuoIsRTXHgWQzrvD)laYb{3jey?6Es zrlE-VrF7k0^`e`51=j!y=({LdP*BuMI*bJK=-%FJ9yt~Fet1_#nOTW%QNykfQd;(q7w70`*}pVqY!kumbrccAbq zd};`Z@81<#c)sZTo#xq*gcbFeSUOw7k`lqsAvJiB6u}fPH4C!P6BKNpL#1YLuR!Y~ zBM6o`zw4TUgnhHUKq8P9$K1vscFSHAfdeLc;awz5H}tM$^Y)}3xbJxq?A&|b0m9%z zjE&LWN7L^eW~uQJ|zc$SOhk&|r%@ z+LXjtP|bd5EZa-2*3bJCvxy5_N&O!^;br;)7b$aa^NW?}#^zibq|rI)D=k^0+-1Aj z)(_1EYPD1Ij{_yoT6+3J2`9ptw)u#9LS1R4`2}c+rtY;kP^Zkdh@bFixkK^hE4d!r zKKC8G&tBy`BCAn;1!P^=X?AARImk|rczA&8{en??#@%ImJ4Iue^z&X6s*1>P(HNgp z?}Uyz)92^52{K*SJOVx1dt!qMC%7P|`?j)j#x`6DnVDnCidXjl7mRsbE@GIh6J*!% zbBWIcxh_BiHBSCk_C1|Uj;69fXmwRQQcqQ79s44+p%4>26o%FLz~i~LE(WwD@O|B& z&g8p?(p;yT7^m_j<}cF|b)WuoYP%@#?K{O+CT1VFo*y)H&rai?HlJy1A*a(qvs7DC h7d|33|_`CR}2 diff --git a/companion/src/images/simulator/Horus/left_btn1.png b/companion/src/images/simulator/Horus/left_btn1.png new file mode 100644 index 0000000000000000000000000000000000000000..4da7ef401cfe2f82a85bbb7ce7d4f350e25db701 GIT binary patch literal 42659 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

A)mb4y`3n`9juVv(9;{k_@CbU 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 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/left_btn2.png b/companion/src/images/simulator/Horus/left_btn2.png new file mode 100644 index 0000000000000000000000000000000000000000..6a77606ae0268a91298bc370cadf85c230fb22e2 GIT binary patch literal 42743 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

JqYPC}h7lN>9rF!JLmjlC$FXX+Ho{=&hbmhr>;& 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 D?5LkM literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/left_scrnsht.png b/companion/src/images/simulator/Horus/left_scrnsht.png new file mode 100644 index 0000000000000000000000000000000000000000..625aa7208653034315412222dc27fca1f30b0d2b GIT binary patch literal 42773 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 BdF}uJ literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/middle.png b/companion/src/images/simulator/Horus/middle.png index 8d3f480a0ced49d9f44d665b41f9ecccf5ba30a4..2964334daa451df84878950dd686219294129618 100644 GIT binary patch literal 44468 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 literal 156118 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 V4eX1v7{>qr002ovPDHLkV1g)>k#PV3 diff --git a/companion/src/images/simulator/Horus/right.png b/companion/src/images/simulator/Horus/right.png index 6c5011012bb59baebb8cd5cd044b123a94a1bf88..67a77e8f759b45579be53e3e85951d2ac1425ccb 100644 GIT binary patch literal 44170 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 Ub@~$UaKMkWxPn-f=r{lW0F9HiKmY&$ diff --git a/companion/src/images/simulator/Horus/right_btnD.png b/companion/src/images/simulator/Horus/right_btnD.png new file mode 100644 index 0000000000000000000000000000000000000000..0fff8f9ef22c3679fff572fdf7627cfbaebaf369 GIT binary patch literal 44154 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 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/right_btnL.png b/companion/src/images/simulator/Horus/right_btnL.png new file mode 100644 index 0000000000000000000000000000000000000000..af5500fd4aea2f29e96d90ca0e8bc75129eb78a8 GIT binary patch literal 44096 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 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/right_btnR.png b/companion/src/images/simulator/Horus/right_btnR.png new file mode 100644 index 0000000000000000000000000000000000000000..d7fb279708aea0a0042f40ca074906b143bc1589 GIT binary patch literal 44090 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 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/right_btnU.png b/companion/src/images/simulator/Horus/right_btnU.png new file mode 100644 index 0000000000000000000000000000000000000000..16a9b6cd10215a5968b6c3601c33f17742c29eb8 GIT binary patch literal 44115 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 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/right_ent.png b/companion/src/images/simulator/Horus/right_ent.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc69d4709c0abb60b4945481a30a7d679f2677e GIT binary patch literal 43848 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 I-ty!B0LRL3;s5{u literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/right_re_ph3.png b/companion/src/images/simulator/Horus/right_re_ph3.png new file mode 100644 index 0000000000000000000000000000000000000000..67a77e8f759b45579be53e3e85951d2ac1425ccb GIT binary patch literal 44170 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 azW)y|HF{H;#6xoc0000 Date: Mon, 9 Nov 2015 21:16:22 +0100 Subject: [PATCH 58/74] [Horus] More timings --- radio/src/gui/Horus/view_main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/radio/src/gui/Horus/view_main.cpp b/radio/src/gui/Horus/view_main.cpp index e3fdacdb5..d199e2bc7 100644 --- a/radio/src/gui/Horus/view_main.cpp +++ b/radio/src/gui/Horus/view_main.cpp @@ -345,9 +345,11 @@ bool menuMainView(evt_t event) break; } + // 23ms if 24bits per pixel (with transparency) 5/6/5/8 + // 6ms if 16bits per pixel 5/6/5 no DMA TIME_MEASURE_START(backgroundbitmap); lcdDrawBitmap(0, 0, LBM_MAINVIEW_BACKGROUND); - TIME_MEASURE_STOP(backgroundbitmap); // 23ms + TIME_MEASURE_STOP(backgroundbitmap); // Header lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); From 312c3701627157bf14a5b8e8cb43f5d5453e6551 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 10 Nov 2015 21:55:12 +0100 Subject: [PATCH 59/74] [Horus] DMA2D more used. Some graphics are really faster. --- radio/src/bitmaps/Horus/asterisk.png | Bin 14224 -> 9376 bytes radio/src/bmp.cpp | 8 +-- radio/src/gui/Horus/lcd.cpp | 53 +++++++++------ radio/src/gui/Horus/lcd.h | 2 +- radio/src/gui/Horus/view_main.cpp | 3 +- radio/src/main_arm.cpp | 2 + radio/src/targets/Horus/board_horus.h | 1 + radio/src/targets/Horus/lcd_driver.cpp | 88 +++++++++++++++++++++---- radio/util/img2lbm.py | 3 +- 9 files changed, 122 insertions(+), 38 deletions(-) diff --git a/radio/src/bitmaps/Horus/asterisk.png b/radio/src/bitmaps/Horus/asterisk.png index 00c24413dc71305ca3c8fc7ba722f31df58be875..1e12a2492be698456ded6c653d33afe9e17d3ce0 100644 GIT binary patch literal 9376 zcmW++WmFs877Y%?X@O8Ig&>9CUffA>mr~r_-J!S!EAGYJ9g4dbC|W2`+}&Qj_hV+( z%9^=za?d?`_TBqLC@Dx|zj^-#003Y^Wh7MK*9iF0gN_P6vpS2|!Y>#OGTP1n0Os5O z4g^4YCJ_E3nhR835^V$X%{w}>txv3_000O8l@L|)_{k3;>jL)m;B+mz>VcgeBo zzd6hY&9gn%Dfa|V?=b)ff;d z_Eac-%ln?AqoXn)f8K0H!TN{0$-5A-;Z=9%pwnnYhk1SmPb@}avXeDC!>vmU!y*&- zx!0;uLsSJP#t>+DFqotSIX}8ooA-FZ40u(Aw#`{JQk|Is4o~%~4+Tz`!YhGvwkc))32YJVH7S$vzYQZmQJ+u8l1`fRZ@8~@ZjVO2f|b+Uy=5wgDB`4gT6 z&ln#s{fk*+3f!e{r7YKyW_!E4$cx*|3$6JsrSGe>npjbp^)>bvLt=ienR$v zvXwVzIisF{aHP)W;uSJR<)bPmD&x~-Wec0EQXqRdHvBqh?!k@4Ek(&|1j;|Ib6e*A zvpsqfZkk0?ykW>TH?_Bw`S+EHUnhY4_hU}Sr-)ivW1tX!^d@odf^lMtdppVx9cPv9mv-z{yz?|`y~9@)#S8g%N`!nW ztM@h0wza_t5o*=-nGKeq0a2KJRH&{SyHgN8`QlIMwSI(Oyi-oa81v;lK04~=@?NPx zMu;I$nQm~yf|}PDA%4}Fp*2CJumLFY`+I}J zk}m(!VDQYiS-r!_IN!22u$5PZ50sE-M2D4%DML{C7-P5mnCt0a`c}>uNXvnGHd6TG znjr*_m(uCyOYT=L=H5C8VsAi6@piI#L(}UIa<_JJCPf+B_9kE3fBPpN?K33oD8y|Q zPy%XRH~kFl6#R$%b_b(rQy6$?duJH}uGqBm3V$Ty<}Op#s7{?{joW@$*r^>O#2^3! z?eR@TmY?L0uZ47Ds}>*M3bpeTzhpFX2ji)*vk@Cn)mdrgGoRL^pSW+%1H4{W86D$n zHNJ?ZW)RWrhZC_3%bpCpKkt3_MJLB{U_6Gz!JSXyg(QzVcAM4NSV~Zd2<>UHFwL#> zQk@V9wg9U1)?vF_&#g)`gG0{4#{SSt@Z)}>e~#Sl>i|QM_NMwWWLjPe~JS}5<-p+>;b^Cs7VGg5qA9O&T=+QT%%tSU@ zEH7yMZA)O^&a*wXU;U%D)6<(P*f+U6!<(Q<#_MlV`&izH$RE|#`5&x=2un7WaH&?K>Nk2?A*iK;q>Sv|=~mfgId5?cK?A^C}QGyFTM zfIwBG^vF>)TNnW{)GHa(oug+weqDmOH<_ubYe-9HYti>d6Q|&uIHugpJS!XnI~B}f4gmK%b)$6p?^pf?@1Vn6BF3fPDc0yz^6jW`+gn$4rSG5;w2rf-3vs18 zWq7l#TNe`7l;f3_s0CViXkj8&v2p_IQ9~>Mi8R~xiwZiLrzzI2oux3ou ztl0+-*aZ3}l2;AsqD(nVG0G%;*4ID@f8q#&<0hMnAAR{w0c0%5R6C>#$B=B69e=V? zp(V$`cyaQp_M%-mJ2b)Mq1+NHhjQ@j{^u`-OaFtgSJm#8L3j9&bh-|wE(J5oT0wGk zO`~88hlcLe*qQFQs=G$hJ~q@9*^{%j=G;K z=mF5w%|=iYkNtn2D}o{3tBVg;9n19$u-r0kC^vU6m+(`LX|wmYR-&5k2(nBDP2v74 z<6C>IfXhpfzc_elXlB$~-3k0#d!`t$g>kS~e=CW@DL|JWu+nzK%;%2=>_o=;ia01N zzRarS%2X4ChsbIF6&2O^GbJA)a2#L-zylyl1>*zIm`S4l26`X^F#A2fTO&+S)Se5l ze!{`hN0h1t_{cqW(u~GdXSQj#UmS!2(LKH`luPCaaseK10b#g^D)Mpwqu?0>S%k0- zps9W*I$m7Bwz!B7MF`+zsDJQMWD29!a^(EN|6piyU-Za!t({6-g-I}h>SQReG^(eT z`XhktyCEl6@34mfn7PNG7*RqS5|D==HKzw8!4=7*B4w)87!ozuHBK;XID4G)&iSQY z(ESERRY8#8$w}d|UtfZ|@4wiGC2WY9;3U zt_V0~>1p)~`2J52>(v&h*ev+fl%SK{!%K;)KDHEvIR{n8{qr$SK`DmRY^M3{bPwdm zSAe68kgFhsJ#(Q9F{!@KsG~SvM5QO(deaIp)hwteIM{`Oj)gSoRoZOC*_xsz>)0=o zo;t5M+Nl#bVyH|r`**lpn$^v;Viase5Fs&r0rn*ce^fTu!ZP3<699>yn;XXzvIQ#x z6H5qgg~f$$&*>Q7O7$a`9KTQW|2OK__+m;@)K0OgsNr#c@4eyxq}O-xSi z>Y_DMiL$#{%6ZO7gokcy>L(o)KuG!nib~2;QnJ`G4$ccdpITeqsHI2ilT019mFBnf z^yg-7r_1+WPmhHqRhuG)t|wrW({-eAK39_frxS}a$PJdp zO@-Y#?$$wE!Q+e6Ug#Iy*)zKbqk-wXjU;Wgj(`Nt9>7*d>~Gg(WrHfh-tK43i#(qc z)0s;%+zu8`&DOSieBSxIaVl~&1kJa{ALcELV_h@=_y~1B6gp|N!-nfIvt&~OAS|)+ z`3HHe1NW>(W53(^ZhohAVg3!4m#X)yf&=KJ_!TutxKb9N#!S({iF({kf-ORrMutLoMA z6{>{|5T#g_z=2qaC~Fc=G6h(ZN*+I*3?G=lUfh#00dCFKM_`H>^!TeV>yVpPaw_Kv zm5qIoKY*BZy}hx~4eVE2Ub>Y+h$unJd!^`#Pn3x1l)v5(xGi7iB7#XBhJkr7=VUEQ z5_&Sq+0}MmB*9^BA3qP*z>u5ZIEwj2;vuxTb-ni@1Qd!y+_8bEMBfevubFTTbDwVV zF2&X30Yr&(-4~vNXhNy{(qkVWzrSCLad_-Wf z{aijAnYAC4#B}-VIP&@Yl8iVCk-)gjH4r70*a+#~^F-~haYV%LDJjuNjTv*G77hD; z;IS|w^WFhk!XtFPfcT>->cbJ>+{54A^X&t@+*#mJZX7w~wkuN6kw|`_6*@wC)^gjH zM4|h7&PB~UAfJ$4ikz7Rmzn!be}Q)U>W%!U^QT^GQ3>OjQ8R)Q=HixvD$jDc?C{Cv ztf;U+wS`0uk@@I9YM@#w5xiuIRB3KQZK7TSC7z5VLK(4*(7JqW(_om102XuCRn4q1 zy*5$^Y7z~1gko@1jHsM9&#uj53%wPO0O_v@T2n&Dq6MdV`|*Wbxq|0AaE~IhvOhB* zuSP}yv42p4N4M7hnV~okM)kIp5CQ%dYN6UiZ^osQRNpc+w%#!N2FC>a3mSahO6u%I z1wy8|y&3y-U`08GGuH|st27%dg2^6;AfM6KMvOj@^UV8|%kaT`5F29Dh>9mDVpYYA zz}dE|it2KAP#f^!{Lg!HlZCIfq|t7zlG#hjjl*h5AIA8 z0-?d+nEJAni=`et|JjC$+40M z5nB18hFTx56_daBSGc3Oi3ChEtc?0ZQK25a!vv83GR{ zK;P&~1;<1TINr6~utf~!t0b#JL(3C}Ig|`kg^#Mm& zoHsRyG1mdxqP0&%*P8plhC0NEWt)_&RaH{FWv%EB!~x*0s35)a5cOK+*+*6SqqlD{ zSILA?rk%pf%m>^a?62r1hdg?aOH+}Eq9Z=~$7?}|P`Hw?`3sj2_j4nVSRKYffBf~f z+kcg4;QOT{Lgnb5+}ooJWVpgUyQ=mK>6(}V(yp#qK;Af|_84izB`2x(FVrc#piOLy z++trG*n0`|f|bl+yexfG0+ObV01vE8)ALKZXq3rJ9qZvh6>VQIS>QvAoXkH@(3h3Zt|u?=lW4w`>*PEF>T`dVXPj0tQhZp zhvej`eDC*XiYlbm{sN+@A>30HyNW=Q2Z+CsF~YZPia`cZ^*LA`^$+njJbUR~nq(W- zBi+Qf;k^}Q1-@I9I8;)6Ve~`J64j!pPohXP$=J49u|Q%Q$>I0;*xnM{Q5m0)1Gb{u z;l+6Sv9ZXR9Z)xKoZjhF;!IKBJa4~p-Ga%%V}=J84B#B$s(UW3X8}OetuL{m|7fsg zupofkTOCx!mRKjN?;`vsrFhCdz(J#mRk2n+Wj>ZnX}*U*(UJt~CsY-!$M`6b9(7%_ z8MkfY-{3b~mSMX>6VCTDH#xTG8(7<`XM9?f2L@1^#j*3P(?nIi9W+>$-N31o@MyBU z3bsZz&buvSIlEf?w3NHap2C1IS>t$9K%;vZb;o6+f)!JW+A^F#;E)OAdg;cLqLNJo z#?(TkplF|$7w8-X4^-qzNV<^8JeXI$qeGQ(O+J24xf77Q2BwJF6e6%%(H9KF6$K!X zY$65B$SQ|)<+;zb6%&T}7k7#Q+*8Hj^yI21JuT38&gnfV@mqh656Ct)e28D%m#3A;` zR&3g7Nla26tt$h_?_l|T-w`ZoZzd5*@b2+C&UWc>1d^KLKc%mWZ}9t;l+Nf$(|Ylj z={zmkaP^rrLbFk(E}<)h6}AKb2f;roe!=UbZW`Y;5#BfCKP4^ zVF#rSmIi<7j7xY^pKQjb`d(3#50@ zgV&kErn7OWY^-?zqDr~CelkY|L?9QDqm5gxXIR(!`Z;}h>MPgqvV#nFP9)TT9c>IC z>;@`V=5|tB(FhO1?Rd3lm{tIn%$G5B-7_+8ER)a0cfIX-Xl5KWknj)we>}K9zpmJ+K zE->ZE{`pILj?B5e*kI^NbB@NeUy2I9;I9bRJrsffWS;ZZ&kHd%E@2W3|n$kvKP}jQJz+@ zqwk}f{a;x6UD-HK`#M*$)Hf3SJ|%uRxDOof?t4$6JtDa= z6bQTs2X3FHW|vv6&AlmLYd8AY@U<^I0#rK_62gU!;LL(l@KfxjPM%Qd?RKY>?%igP z)kMsVrsSeAcuqL=svf8B;0+LoG9@(SRfZ0w&D*rqANbf`!k7 zYMz&q*({`o_G|GE;o@IbjP`YD{_Z08h`3g^P|iWak`y=P1xLI`@H$B;Zd*YOR*iyCm*-h5qNo!h@!OQWo(C5 z*@XY`RiK)XlOpt2co?c@P z=buFuo6~TYbs07I8|EV)UnF-2P>wF$=Z{Hm6`HHS=7JE!@OwUtl+8Iha?{BBVZu7L zf4iw3o?HDEtcVh1A#L)myf5FAR#z1vD!0UkSUd7IvsLs#17~*Y_5gN`>OL@ad5A`xG%FTQGlA?G9|j>Mo9=^F|rCS;_U$%82z9$ENmWUdm+OkQx<#VoG; z8lD&oLlQ1ckhNe1tIIFY2Zcxp3ruN(3u+A5yxiVB(><^W3+qpBEMLha+W02;1jEI- zk$ycG+xBnum=ZfX^RADukR;9#GoE1;w8+;;O54}vCZlzEZdo3}g^7;3Ht64YnGTZR zrX`uORchjBZ7w;)&83`J!*=V(NI<{xY0oekwRSPGNy$q)*|jHYV|0ip0Y|rv5p@|e zD;FC#&ZhJ^HpKi7FAjW9U)|QCQ_YHn6t#@_BPDRaC~(;(6s|h;C9Fku*l2@Cz@e+p z+TwR+TE7Gfa9F2$TL!h701*;Y7LJX7i7dahbo4j&ef#l)mUEsbTvkoc zS7)O|NS!)JqPPuOlIhc8FKHOxP6k(tG*^psnVM$c)S-%;nbY1{YT|eLR*!i z`|45z&U|Bk-DO^k2?u##^#Db#%}#3fLy=toJVdzOO~&g1DjsR^iL#0Yn?`<0m0;J7 zFUhOWHKqxBx&09R;;TmN>_V~M4__upU}r0g5!nusEWMciXgHF98Wkea{3!WsVpl~_Ec6)gBP=vPN_5s20(5jbF${davj^X zQaVCrin7s@1CCm8ZD@z1qmwG$%5b?GHQ1!U3JikAb^JcQQnxRlj7hlyDLl||)uOA8 zZT_>CR<=afRZ)F0C6(>&8*PRW4?ZHv2pKeHel35rnEx!IT;5vBC- z`F}Qrp{*GdmZPjbQ8aUTmeGE^k>}@*61&tjwhein)*Lub4ge1P#$gR7}d-$^hZM_Wiex_Dlk)KWg zA+gltKj*KKSa0L_4F9yFz&+YGtRmL}G4d~%p@!^sO}8G^R$-HslLx93^({G0szaHE zz&{Uri~q4ZLpwVKABB6QaCR90I^1IYYeq~=EE!Vf^7~oJ{d!~`c&TfI8N$r0k>i3h+g7Ze$i(h!Oluhh2Zx3W5CRD9Sr)p# z27{I;Ys$0tJSSCsXSE|w3l&{`CJx7c&ZnwEiCSOouf+foq%y!z{N1iZFD6xe1(3 zSZmyApcoaWuxw`)YtBESTVfJXdgHO;y0!9nnZV0TwzPj_m3JbH8KXj(u6 zo^BHfE`<14%}*MLpygoXfa&Fhv}FGrp^rDtY*Tvh3&K0J%R!o(Ef8-naK@t(lRcKY1 zyRK$yzHqEY*%%o`jIy6Dwlq>>{+m5Y5fvqgAmb;JZ&ENmF@XW!g=$}$>zNaM(!+NK zkIwG4uu;2^?JG_6CaCv@pm!w-;R9tiwRCmg(F$ebgz$qGzh~!a=3vh49oCF*5pi_jO*}t5BwI;D89&kdchdOCqAl>%(a~c43tOYN zev0}|;UO{`rTOkT|3A)~2UiIkZ@7jwc1zsRIXF0=24I~81ekw3cnjq8!RHU!hf})@ z&n)f#cOZ2A`UrnI1pbl>+-ypTRM)L-jJ)b@#j~SJi*%I5?LV&YJERI!dp@8C!Dm-n z&H){OJRtGUC;b1u>uB&AJOTsI{4)1CSFlZR0Y8*3@T$iCIN&)s*jIVoO^l)4`tz+bC!S}rB6T%WQ+jxw$n{|+w)M+n^A zEb`c3bY}}S3`vNh9<6uYduscPl;fRxYt{IDe#;lVb=r{<|fkQJWNr1T8DCC=7ye zz>E9aFLRwJ+XeFttgY2~IPHQ`t6`AEdP$eQW>cxycLt9_BGP6PaHZg8{W^?N4nElG z!MlftN3~0{QDm|KG-#P4BM(~PyA0sHfy7ypH_DKiG@IBwtS9yHWApT_CflM~R1yjA zn^VL?!cfCa+uM9w{R|UpLM}K3L-!|@;w6*%x1w}$dS(N(i6*p8>96mmgB!-$=7AGt z;@i7YyiL5DCGHkg0(_pX1byak*U-&kzzvJZLp@LhPr4Hc-oOI4dTB5h>r*Hbq65#^ zD9Bh&LPiF)B$nx|tecdRk6lN4?SjlVdR34eu@jKqVC< Js>BR}{s**3=BWSx literal 14224 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 i--) { - uint8_t * dst = ((uint8_t *)dest) + i*w*3; + uint8_t * dst = ((uint8_t *)dest) + i*w*2; for (unsigned int j=0; j>24) & 0xff, (pixel>>16) & 0xff, (pixel>>8) & 0xff); dst += 2; - *dst++ = 0x0F; } } break; + case 1: break; @@ -355,13 +355,13 @@ const char * bmpLoad(uint8_t * bmp, const char * filename, uint16_t width, uint1 f_close(&bmpFile); return SDCARD_ERROR(result); } - uint8_t * dst = ((uint8_t *)dest) + i*w*3; + uint8_t * dst = ((uint8_t *)dest) + i*w*2; for (uint32_t j=0; j> ((j & 1) ? 0 : 4)) & 0x0F; uint8_t val = palette[index]; *((uint16_t *)dst) = RGB(val, val, val); dst += 2; - *dst++ = 0x0F; + // *dst++ = 0x0F; } } break; diff --git a/radio/src/gui/Horus/lcd.cpp b/radio/src/gui/Horus/lcd.cpp index 09a6a4a09..a9c675251 100644 --- a/radio/src/gui/Horus/lcd.cpp +++ b/radio/src/gui/Horus/lcd.cpp @@ -400,14 +400,13 @@ void lcdDrawSolidFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlag #endif #if !defined(BOOT) -void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) +void lcdDrawFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) { for (scoord_t i=y; i> 1) + ((pat & 1) << 7); + lcdDrawHorizontalLine(x, i, w, SOLID, att); } } #endif @@ -847,15 +846,23 @@ void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlag display_t color = lcdColorTable[COLOR_IDX(att)]; uint8_t opacity = 0x0F - (att >> 24); - while (w--) { - if (pat & 1) { + if (pat == SOLID) { + while (w--) { lcdDrawTransparentPixel(p, opacity, color); - pat = (pat >> 1) | 0x80; + p++; } - else { - pat = pat >> 1; + } + else { + while (w--) { + if (pat & 1) { + lcdDrawTransparentPixel(p, opacity, color); + pat = (pat >> 1) | 0x80; + } + else { + pat = pat >> 1; + } + p++; } - p++; } } @@ -883,6 +890,13 @@ void lcdDrawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags } } +#if defined(SIMU) +inline void lcdDrawBitmapDMA(coord_t x, coord_t y, coord_t width, coord_t height, const uint8_t * img) +{ + lcdDrawBitmap(x, y, img-4, 0, 0, -1); +} +#endif + #if !defined(BOOT) void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * bmp, coord_t offset, coord_t width, int scale) { @@ -898,16 +912,15 @@ void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * bmp, coord_t offset, co } if (scale == 0) { - scale = -1; + lcdDrawBitmapDMA(x, y, width, height, bmp+4); } - - if (scale < 0) { + else if (scale < 0) { for (coord_t i=0, row=0; row> 11; - uint16_t blue = 0x001F & color; - uint16_t green = (0x07E0 & color) >> 5; + uint8_t red = (0xF800 & color) >> 11; + uint8_t blue = 0x001F & color; + uint8_t green = (0x07E0 & color) >> 5; - /* configure DMA2D */ - DMA2D_InitTypeDef DMA2D_InitStruct; DMA2D_DeInit(); + + DMA2D_InitTypeDef DMA2D_InitStruct; DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M; DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; DMA2D_InitStruct.DMA2D_OutputGreen = green; @@ -450,18 +450,84 @@ void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, u while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); } +void lcdDrawBitmapDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t * bitmap) +{ + uint32_t addr = CurrentFrameBuffer + 2*(LCD_W*y + x); + + DMA2D_DeInit(); + + DMA2D_InitTypeDef DMA2D_InitStruct; + DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M; + DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = addr; + DMA2D_InitStruct.DMA2D_OutputGreen = 0; + DMA2D_InitStruct.DMA2D_OutputBlue = 0; + DMA2D_InitStruct.DMA2D_OutputRed = 0; + DMA2D_InitStruct.DMA2D_OutputAlpha = 0; + DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_W - w); + DMA2D_InitStruct.DMA2D_NumberOfLine = h; + DMA2D_InitStruct.DMA2D_PixelPerLine = w; + DMA2D_Init(&DMA2D_InitStruct); + + DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; + DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); + DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(bitmap); + DMA2D_FG_InitStruct.DMA2D_FGO = 0; + DMA2D_FG_InitStruct.DMA2D_FGCM = CM_RGB565; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; + DMA2D_FGConfig(&DMA2D_FG_InitStruct); + + /* Start Transfer */ + DMA2D_StartTransfer(); + + /* Wait for CTC Flag activation */ + while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); +} + +void DMAcopy(void * src, void * dest, int len) +{ + DMA2D_DeInit(); + + DMA2D_InitTypeDef DMA2D_InitStruct; + DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M; + DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest); + DMA2D_InitStruct.DMA2D_OutputGreen = 0; + DMA2D_InitStruct.DMA2D_OutputBlue = 0; + DMA2D_InitStruct.DMA2D_OutputRed = 0; + DMA2D_InitStruct.DMA2D_OutputAlpha = 0; + DMA2D_InitStruct.DMA2D_OutputOffset = 0; + DMA2D_InitStruct.DMA2D_NumberOfLine = LCD_H; + DMA2D_InitStruct.DMA2D_PixelPerLine = LCD_W; + DMA2D_Init(&DMA2D_InitStruct); + + DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; + DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); + DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src); + DMA2D_FG_InitStruct.DMA2D_FGO = 0; + DMA2D_FG_InitStruct.DMA2D_FGCM = CM_RGB565; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; + DMA2D_FGConfig(&DMA2D_FG_InitStruct); + + /* Start Transfer */ + DMA2D_StartTransfer(); + + /* Wait for CTC Flag activation */ + while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); +} + void lcdStoreBackupBuffer() { - for (uint16_t * src = (uint16_t *)CurrentFrameBuffer, * dest = (uint16_t *)LCD_BACKUP_FRAME_BUFFER; src < (uint16_t *)(CurrentFrameBuffer+DISPLAY_BUFFER_SIZE); src++, dest++) { - *dest = *src; - } + uint16_t * src = (uint16_t *)CurrentFrameBuffer, * dest = (uint16_t *)LCD_BACKUP_FRAME_BUFFER; + DMAcopy(src, dest, DISPLAY_BUFFER_SIZE); } void lcdRestoreBackupBuffer() { - for (uint16_t * dest = (uint16_t *)CurrentFrameBuffer, * src = (uint16_t *)LCD_BACKUP_FRAME_BUFFER; dest < (uint16_t *)(CurrentFrameBuffer+DISPLAY_BUFFER_SIZE); src++, dest++) { - *dest = *src; - } + uint16_t * dest = (uint16_t *)CurrentFrameBuffer, * src = (uint16_t *)LCD_BACKUP_FRAME_BUFFER; + DMAcopy(src, dest, DISPLAY_BUFFER_SIZE); } void lcdRefresh() diff --git a/radio/util/img2lbm.py b/radio/util/img2lbm.py index 7d8a152c2..e02ac7a25 100755 --- a/radio/util/img2lbm.py +++ b/radio/util/img2lbm.py @@ -51,7 +51,8 @@ elif what == "5/6/5/8": for x in range(width): pixel = image.pixel(x, y) val = ((Qt.qRed(pixel) >> 3) << 11) + ((Qt.qGreen(pixel) >> 2) << 5) + ((Qt.qBlue(pixel) >> 3) << 0) - f.write("%d,%d,%d," % (val%256, val/256, Qt.qAlpha(pixel) >> 4)) + # f.write("%d,%d,%d," % (val%256, val/256, Qt.qAlpha(pixel) >> 4)) + f.write("%d,%d," % (val%256, val/256)) f.write("\n") elif what == "4bits": colors = [] From c4d7feaa9b4e090e201a8a2be44a622ea7bb01fb Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 11 Nov 2015 10:02:12 +0100 Subject: [PATCH 60/74] [Horus] Simulator graphics --- companion/src/companion.qrc | 2 + .../images/simulator/Horus/border-right.png | Bin 0 -> 11375 bytes .../src/images/simulator/Horus/bottom.png | Bin 133 -> 5162 bytes companion/src/images/simulator/Horus/led.png | Bin 0 -> 11652 bytes companion/src/images/simulator/Horus/top.png | Bin 133 -> 4363 bytes .../src/simulation/simulatordialog-horus.ui | 236 ++++++++++++------ radio/src/debug.h | 5 + 7 files changed, 164 insertions(+), 79 deletions(-) create mode 100644 companion/src/images/simulator/Horus/border-right.png create mode 100644 companion/src/images/simulator/Horus/led.png diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index 018d2e9e2..ba6baa82e 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -10,6 +10,8 @@ images/simulator/Horus/right.png images/simulator/Horus/top.png images/simulator/Horus/bottom.png + images/simulator/Horus/led.png + images/simulator/Horus/border-right.png images/simulator/9X/9xdb.png images/simulator/9X/9xdl.png images/simulator/9X/9xdr.png diff --git a/companion/src/images/simulator/Horus/border-right.png b/companion/src/images/simulator/Horus/border-right.png new file mode 100644 index 0000000000000000000000000000000000000000..8c23988857d40f285e9d4d82f8cc64dfb08c86ee GIT binary patch literal 11375 zcmV-#ERfTQP)DreYOY#UALU3|K8o1`J6H zOaH;L_J%k9X_kcc!V3$~#v2Q;0k1SPO;SU%OWkBmZ+bD~5 zD+F|Xj}QU`1e&G>0xe_Tcj@O!DcnEYW4GHu{K=pGEJ!KSqXK{s0#Zr{A=2OD_wn!L zYeNY1n&LeI0=y6C`woBlC;tUse(?qLZ~o>_5xj?#k~Ti%Kk?1H50jgTdt6?eo{3)* z0(|h;ZFhL{c7yG9i`8lc{j;C_8UEl8{t&1mzt+CneCr{$N2d#_;_`-1+>38u4EG>Beg7>h_q8oY)Lk|Igx~if7{LlY8th4wJ zfBeTV0_eJZUNRv72uL9yq<{#td_n+HNd%df6VL$S%JBK}bR?8ku+Gw;NnlOK7bj z1Yb;RjoEw-B_*Cee_lA&e6C_(jM3O7qX{YAdA3N;W^dhy(OFn)F^pq8)MT0hfVJb1 zmyl9I>r)0ZjRJ(IXz{&s=(|1Ix=zNU%MF!6U>L^icHqeZIku%YjSnfJ;tP~i5K=&EeSkee5y#jBR6N+?=*bSz8Tm+sN*j7I=L5RFLmet?HXB|D zk-S8Zl0!;_1(ME>K#I(vh_3PHQc6gvlE)#hFC}Je3#BB^&d!o|FWpVKJo)Z%biT?b zP0kyI2LS*f>5i3B>`AV7kApY;wp z856njuD9riF*|~owan);NGWl3b#>CB=&PG}iwQI@_9mX);78174U93UOtMCYei)#& z#%8-IVpx7tr4(D1;9#qc;CkLq+Dl_dB0BN- z+R|6jq29xe7J&n@j(152OW~tuSIHpCJ1^6satWmrCvm2=9=qKR+896}N9yPZ1VqlG zotq?rhlyeQ6M}>H(duGsBgZ`qoWVGoFD4)$71^s$ntdKXDGBX?3xT~llEW23z`Gng zMGy(ddS|iU?Xd59iha@aopb2+1FA}6vtA|h1p-7gcNq~f@12y>6j&%g0*ZeRC1FgR z5P(uDo-+xfB?ms4oNv4%a_|L01^{;|8=a=R<2bahiV2)^lvigkngeCr(P+|# z;bfAX^4&S-u-kXMj4mH)QJAfi>L`ysW)8|H5tB?O2&Bl~^*x%Vfe@lF5CIrNYr9-7 zjwpx{QXhJkDQaeKBLus$02!TfOa_rMlG98-_*AGVllx&9;hjrICYksMMsN`a;_6O2(pZ7BjgL;|P@3UK_&7i6zLjqsEp3bD1}7R)R0Qz{QPXIUs>(o0l@2wb zT9*<9$Bx;12WwplH91OW1Cd;H+U@q}x(?PkAYy&z0{VW0F#)^%zRU&L5eLenB}HFj zLMrT(yjGbWJLeMCS5gw4L}7zcCLw^;dIK;`ON>qQ5VAU%;VTyiq4#O~Dxm-dBVCcDs5V%4l zrH6Cl0S`>ps>Jjh6>*uV_W-3dW03$I0<^@0j!O9%5z-RIRE1Xv1%cuZN?jWkuFf9aV6lm0-bW z@$~eBrm6AWci*MuIm}WbN}?}Q6dVGG)a1wZ-ebGnq3gPI{t_&xn;OO#oSmH~Ys`*V zes9t~-CH@~t#;cD>ZZwspX?=M3Yu}5oU`!WV{{$?9>O0VV68PgXQsX|9`7t39v-pY zY^bCanN$R%+&O!11u5%<6%+M$Kw0F2TqYb6&U-Yo7XS46m#C{cTO(Pap`y2`QaT#~ z74Hs9_lF=SFR>_+!hp?ch5ddHYb^ji)T15ov|6EU8(iO9C%+iYUog2@B4No-M(mF) zm>?ARk>R;29I+GvrlLAmJ8RP+OiWJ@RE~_agAj?|N>RB0XK_WW9bv75_aT?Gd(v><$EPj#F8F}Q#}#(F9c@Fx`r6?9>N$aIqwqJmE6^Oy+zwL_;7P~GD$!DS)yW#vljQ)hk)Q5 z_WM1CeqdapGDqAtL~1SPvm?AvOp>K)u8;y!sdTu@@Sm4b9nlbR zZ{znRZ(^;*Znr^QnN%Ome4~`OICWWBm}2 zD1~I&!6#ZUq?Cw63?bMfgK;k4?(PmZx3}o}E`!u5z~$0vj0Eu?+rZUb#vooKSrire z%W6H~;o-dFLyQz;53q1|TL3k(xNXD1jk*v5eIoMrxGWN(dFvmB^9WF$>oZ195~tYu+rEDD%GL zkdpO2;0kbd&NnwVxWBu{e&1mj8Kf$$Fq_X|l){VWA3-qDmApQm6YpFi#8PmKXHJ0h z2r12Vu;YmNyoJ(*fjH~1S*^&r(If~lI(VB66t}dZSMl6h$xHvyY+q7hj`Al=cAb654xq_Pb zXVfiaExY`=_YV8r9*zN1h}Jk-hxK}crm6Amo40wdIL$qk^u-HE0Pf(z(P-}OZgG8k zi(wdw%AitMEaosqfLv{cu{brF@d1S`XqVI=PBGYBT)OnAt+ZRRgg5ncJ_$Ze$)v~kG4S~*P#}^7E z=@LaJT5DWfKFdXn0-pJRld`Dtu&2aAkdOi1hfG7~LQVMlbqpB*LpP%O^iu|$^B+gSAhNG~^EDZ(V9cQ7A z5LQ10HgYGsL_M)?PAeR-FcD*Jim zZnMFxp^|j6#=xasr9#rR#eeSu4wRdPPUNr{R3PK1+Zn9|0PbJrEZwaEU4Sy9nlU2&m)+V%R!$?V;Us;+T!eUnV}kSz0=usW_DMll1wwRawm50BVxcS-rAbH+#&A(B@3gktlC z`-}oAOw0EncE;Dag^w5fBM!{W;~R!(wO6k)+I;_l%AZQJ7YufFB;k_E3|c2r8mnN4P_qlk+@s)CZ4PTcMH=m(qV z`lwZdVNAYU%;`?2_C{4(UVCEa*gCx;3x)-PE`Tk`=ACmGttF5eHHlI%#0>PpXb^#o zBzQLY1X3k}7YJhDebWeGn20UA?H>KmC%r)?HGd%&cR86?RLwC5#VC6)VXw~l8!NCL8V|&BT6O@0$Qq+!T1=diEmtE zK5KDvd&lfRm^?+M)F*WzmSi6@LC!hc-QHof-e4GqTwqO=L8d59@lr?KJt7ICCJ--r zeyyqcQ5iE8z}A())g={JKl|*nlQ>=+Je4arl2bIUa}MkE3Z^npl66-?2w3OQ4;_ST zug5|-YpG`KF*#$-TI@EP#Oo*1*Gi$O8yKb0R?UGnhrodK4pK-sPp~>} zTJRn|<Iqh!gcA8u;ZyB zD+)oSJ4`eX5S&M_4#C-}wAXu2rDG|u>-NNf6z3~P0iX0evGvk(qT!uS>2LHz_m7WQ zZ8jLkG1p?0R+!IPXsvL*TowkBpT-GR^o7_`8X%Q~@_-9$p^kp0wL(>CES7V$O+y9N z$X)eakGJpMW4V~)>%aSlgfj%|1hi7At0$JV;yz2r)G;L>>5y~5w(ok31(bNsj221>c`Z7fD{fG8N92 zOJc&sw87L*q}Z&yWv-H>zaFk2WX55DGSom9!%o_>VH_cqpf&a(8A5^pOsK?e;+W

^pROPn83)#RpRDLK?yVLm4x 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 literal 0 HcmV?d00001 diff --git a/companion/src/images/simulator/Horus/bottom.png b/companion/src/images/simulator/Horus/bottom.png index 239eeb18f03e8014a968325fd7248cf6912486de..7d3fb11b1b1f0e52c84b3309686933af5d3121b2 100644 GIT binary patch literal 5162 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 Y0XQm(eX@Mt?f?J)07*qoM6N<$f_0@i$^ZZW delta 117 zcmZ3b(aJbMJdcZmiGhLPv7FQ-AjMhW5n0T@z%2~Ij105pNB{-dOFVsD+3$062nwlg zK2uN*6jJeYaSX}0_x2zoFOYX=!FT^$es&Qc69GPCTX-=r+I_ST1&MjO`njxgN@xNA D*RdKG diff --git a/companion/src/images/simulator/Horus/led.png b/companion/src/images/simulator/Horus/led.png new file mode 100644 index 0000000000000000000000000000000000000000..faa808888659bfae7531349ed3e4aa46688800c4 GIT binary patch literal 11652 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 D(>NL_ diff --git a/companion/src/simulation/simulatordialog-horus.ui b/companion/src/simulation/simulatordialog-horus.ui index 8277cb0fa..dad0695bf 100644 --- a/companion/src/simulation/simulatordialog-horus.ui +++ b/companion/src/simulation/simulatordialog-horus.ui @@ -6,8 +6,8 @@ 0 0 - 808 - 738 + 960 + 850 @@ -18,8 +18,14 @@ - 808 - 16777215 + 960 + 850 + + + + + 960 + 850 @@ -96,7 +102,7 @@ 0 - + QLayout::SetFixedSize @@ -131,7 +137,145 @@ - + + + + + 0 + 0 + + + + + 480 + 9 + + + + + 480 + 9 + + + + + 5 + + + + background:url(:/images/simulator/Horus/top.png) + + + + + + + + 0 + 0 + + + + + 480 + 275 + + + + + 480 + 272 + + + + + 5 + + + + + + + + + 0 + 0 + + + + + 480 + 11 + + + + + 480 + 11 + + + + + 5 + false + + + + background:url(:/images/simulator/Horus/bottom.png) + + + + + + + + 0 + 0 + + + + + 24 + 292 + + + + + 24 + 292 + + + + background:url(:/images/simulator/Horus/led.png) + + + + + + + + 0 + 0 + + + + + 27 + 292 + + + + + 27 + 292 + + + + background:url(:/images/simulator/Horus/border-right.png) + + + + @@ -156,76 +300,10 @@ - - - - - 0 - 0 - - - - - 480 - 10 - - - - - 480 - 10 - - - - background:url(:/images/simulator/Horus/top.png) - - - - - - - - 0 - 0 - - - - - 480 - 10 - - - - - 480 - 10 - - - - background:url(:/images/simulator/Horus/bottom.png) - - - - - - - - 0 - 0 - - - - - 480 - 272 - - - - - - + + Qt::Horizontal @@ -237,8 +315,8 @@ - - + + Qt::Horizontal @@ -254,7 +332,7 @@ - Outputs + O&utputs @@ -267,7 +345,7 @@ - Outputs2 + Ou&tputs2 @@ -280,7 +358,7 @@ - Gvars + &Gvars diff --git a/radio/src/debug.h b/radio/src/debug.h index 92640a397..db2591144 100644 --- a/radio/src/debug.h +++ b/radio/src/debug.h @@ -78,8 +78,13 @@ void debugPrintf(const char * format, ...); #define TRACE_WARNING_WP(...) debugPrintf(__VA_ARGS__) #define TRACE_ERROR(...) debugPrintf("-E- " __VA_ARGS__) +#if defined(DEBUG) && !defined(SIMU) #define TIME_MEASURE_START(id) uint16_t t0 ## id = getTmr2MHz() #define TIME_MEASURE_STOP(id) TRACE("Measure(" # id ") = %.1fus", float((uint16_t)(getTmr2MHz() - t0 ## id))/2) +#else +#define TIME_MEASURE_START(id) +#define TIME_MEASURE_STOP(id) +#endif #if defined(DEBUG_TRACE_BUFFER) From 86f1363d0ae2eaf9de40a39b948108fe9bdc24d8 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 11 Nov 2015 10:12:09 +0100 Subject: [PATCH 61/74] =?UTF-8?q?[Horus]=C2=A0Compilation=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- radio/src/cli.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index 0e0d51e76..422b17171 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -320,13 +320,14 @@ int cliDisplay(const char ** argv) int cliDebugVars(const char ** argv) { +#if defined(PCBHORUS) extern unsigned int ioMutexReq, ioMutexRel; extern unsigned int sdReadRetries; serialPrint("ioMutexReq=%d", ioMutexReq); serialPrint("ioMutexRel=%d", ioMutexRel); serialPrint("sdReadRetries=%d", sdReadRetries); - +#endif return 0; } From f32231cab2ab6f99d383a862f4f336a649a8c0ba Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 12 Nov 2015 22:22:31 +0100 Subject: [PATCH 62/74] [Horus] Small optimization --- radio/src/gui/Horus/popups.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/radio/src/gui/Horus/popups.cpp b/radio/src/gui/Horus/popups.cpp index 5614994d3..ff146aee0 100644 --- a/radio/src/gui/Horus/popups.cpp +++ b/radio/src/gui/Horus/popups.cpp @@ -50,8 +50,8 @@ void displayAlertBox() { lcdClear(); lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); - lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); - lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); + lcdDrawSolidRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, ALARM_COLOR); + lcdDrawSolidRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, ALARM_COLOR); lcdDrawBitmap(POPUP_X-80, POPUP_Y-30, LBM_ASTERISK); } @@ -59,8 +59,8 @@ void displayWarningBox() { // lcdDrawBlackOverlay(); lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); - lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, ALARM_COLOR); - lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, ALARM_COLOR); + lcdDrawSolidRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, ALARM_COLOR); + lcdDrawSolidRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, ALARM_COLOR); // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_WARNING); } @@ -68,8 +68,8 @@ void displayMessageBox() { // lcdDrawBlackOverlay(); lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); - lcdDrawRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, SOLID, WARNING_COLOR); - lcdDrawRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, SOLID, WARNING_COLOR); + lcdDrawSolidRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, WARNING_COLOR); + lcdDrawSolidRect(POPUP_X+1, POPUP_Y+1, POPUP_W-2, POPUP_H-2, WARNING_COLOR); // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_MESSAGE); } From ed2f66aedd6a392cfe43e31f818345b61dc675f5 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 14 Nov 2015 17:19:20 +0100 Subject: [PATCH 63/74] [Horus] UI continued --- radio/src/bitmaps/Horus/mask_cvpoint.png | Bin 0 -> 369 bytes .../src/bitmaps/Horus/mask_cvpoint_center.png | Bin 0 -> 222 bytes radio/src/bitmaps/Horus/mask_point.png | Bin 0 -> 212 bytes .../fonts/{extra_17px.png => extra_16px.png} | Bin radio/src/gui/Horus/bitmaps.cpp | 12 ++ radio/src/gui/Horus/gui.h | 10 +- radio/src/gui/Horus/lcd.cpp | 69 +++--- radio/src/gui/Horus/lcd.h | 27 ++- .../src/gui/Horus/menu_general_sdmanager.cpp | 8 +- radio/src/gui/Horus/menu_general_setup.cpp | 116 +++++----- radio/src/gui/Horus/menu_general_trainer.cpp | 2 +- radio/src/gui/Horus/menu_general_version.cpp | 8 +- radio/src/gui/Horus/menu_model.cpp | 10 +- radio/src/gui/Horus/menu_model_curves.cpp | 8 +- radio/src/gui/Horus/menu_model_heli.cpp | 11 +- .../src/gui/Horus/menu_model_inputs_mixes.cpp | 199 ++++++++++-------- radio/src/gui/Horus/menu_model_setup.cpp | 97 +++++---- radio/src/gui/Horus/menu_model_telemetry.cpp | 65 +++--- radio/src/gui/Horus/menus.h | 10 +- radio/src/gui/Horus/navigation.cpp | 39 ++-- radio/src/gui/Horus/popups.cpp | 4 +- radio/src/gui/Horus/view_statistics.cpp | 8 +- radio/src/gui/Horus/widgets.cpp | 44 +++- radio/src/gui/Taranis/lcd.cpp | 2 +- radio/src/myeeprom.h | 8 +- radio/src/strhelpers.cpp | 6 +- radio/src/translations/en.h.txt | 7 +- radio/util/font2png.py | 6 +- 28 files changed, 449 insertions(+), 327 deletions(-) create mode 100644 radio/src/bitmaps/Horus/mask_cvpoint.png create mode 100644 radio/src/bitmaps/Horus/mask_cvpoint_center.png create mode 100644 radio/src/bitmaps/Horus/mask_point.png rename radio/src/fonts/{extra_17px.png => extra_16px.png} (100%) diff --git a/radio/src/bitmaps/Horus/mask_cvpoint.png b/radio/src/bitmaps/Horus/mask_cvpoint.png new file mode 100644 index 0000000000000000000000000000000000000000..48f9609dbfbfe9f380deac1d22af97d9c12d1f06 GIT binary patch literal 369 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VY)RhkE)4%caKYZ?lR?r29+AaB z9oIpa(Me-=1xT>0z%d;}gYjee@D3oO)YHW=MB;MqgpIsM40xD3mk2GeG-+VFAR?tP z_1K5Jbum>8qJLhkKF@CXz>0~5bJClxcfm<_lJB4~-9<+CK9kr}5#5Gy4~wIkLL6;`3#8ll8d)iXTP3 z03D!O;u=wsT$GwvlB$~mBp8eg3{7lpinR(g8$%zH2dih1^v)|cB0TnTLy85}S Ib4q9e09JQ+wEzGB literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/Horus/mask_cvpoint_center.png b/radio/src/bitmaps/Horus/mask_cvpoint_center.png new file mode 100644 index 0000000000000000000000000000000000000000..e3dac7b206d634495d874713e6243c2ed6c4fdfd GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VEX7WqAsj$Z!;#Vfk}U9uEC#B- z4#JF18nY{af|4b!5hcO-X(i=}MX3zs<>h*rdD+Fui3O>8`9jQu#-bvMW2V7ki-8iQ8?fi LCD_H) { h = LCD_H - y; } - if (pat==DOTTED && !(y%2)) { - pat = ~pat; - } + display_t color = lcdColorTable[COLOR_IDX(att)]; + uint8_t opacity = 0x0F - (att >> 24); - while (h--) { - if (pat & 1) { - lcdDrawPoint(x, y, att); - pat = (pat >> 1) | 0x80; + if (pat == SOLID) { + while (h--) { + lcdDrawTransparentPixel(x, y, opacity, color); + y++; } - else { - pat = pat >> 1; + } + else { + if (pat==DOTTED && !(y%2)) { + pat = ~pat; + } + while (h--) { + if (pat & 1) { + lcdDrawTransparentPixel(x, y, opacity, color); + pat = (pat >> 1) | 0x80; + } + else { + pat = pat >> 1; + } + y++; } - y++; } } diff --git a/radio/src/gui/Horus/lcd.h b/radio/src/gui/Horus/lcd.h index 7864964f1..e54e06ea8 100644 --- a/radio/src/gui/Horus/lcd.h +++ b/radio/src/gui/Horus/lcd.h @@ -191,7 +191,6 @@ void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags attr=TEXT_COL void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s, uint8_t idx, LcdFlags attr=TEXT_COLOR); void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags attr=TEXT_COLOR); void lcdDrawTextWithLen(coord_t x, coord_t y, const pm_char * s, unsigned char len); -void lcd_putsLeft(coord_t y, const pm_char * s); void lcd_putsCenter(coord_t y, const pm_char * s, LcdFlags attr=0); void lcd_outhex4(coord_t x, coord_t y, uint32_t val, LcdFlags mode=0); void lcdDrawNumber(coord_t x, coord_t y, int32_t val, LcdFlags flags=0, uint8_t len=0, const char * prefix=NULL, const char * suffix=NULL); @@ -222,16 +221,36 @@ void getTimerString(char * str, putstime_t tme, LcdFlags att=0); void putsRtcTime(coord_t x, coord_t y, LcdFlags att=0); void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att=0); -#define SOLID 0xff -#define DOTTED 0x55 +#define SOLID 0xff +#define DOTTED 0x55 +#define STASHED 0x70 + +#define PIXEL_PTR(x, y) &displayBuf[(y)*LCD_W + (x)] + + +inline void lcdDrawPixel(display_t * p, display_t value) +{ + *p = value; +} + +inline void lcdDrawPixel(coord_t x, coord_t y, display_t value) +{ + display_t * p = PIXEL_PTR(x, y); + lcdDrawPixel(p, value); +} -void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color); void lcdDrawTransparentPixel(display_t * p, uint8_t opacity, uint16_t color); void lcdDrawPoint(coord_t x, coord_t y, LcdFlags att=0); void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); void lcdDrawVerticalLine(coord_t x, scoord_t y, scoord_t h, uint8_t pat, LcdFlags att=0); void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); +inline void lcdDrawTransparentPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color) +{ + display_t * p = PIXEL_PTR(x, y); + lcdDrawTransparentPixel(p, opacity, color); +} + #if !defined(SIMU) inline void lcdDrawSolidFilledRect(coord_t x, scoord_t y, coord_t w, coord_t h, LcdFlags att) { diff --git a/radio/src/gui/Horus/menu_general_sdmanager.cpp b/radio/src/gui/Horus/menu_general_sdmanager.cpp index 81679ca18..fb02d63b3 100644 --- a/radio/src/gui/Horus/menu_general_sdmanager.cpp +++ b/radio/src/gui/Horus/menu_general_sdmanager.cpp @@ -47,13 +47,13 @@ bool menuGeneralSdManagerInfo(evt_t event) { SIMPLE_SUBMENU(STR_SD_INFO_TITLE, 1, DEFAULT_SCROLLBAR_X); - lcd_putsLeft(2*FH, STR_SD_TYPE); + lcdDrawText(MENUS_MARGIN_LEFT, 2*FH, STR_SD_TYPE); lcdDrawText(100, 2*FH, SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD); - lcd_putsLeft(3*FH, STR_SD_SIZE); + lcdDrawText(MENUS_MARGIN_LEFT, 3*FH, STR_SD_SIZE); lcdDrawNumber(100, 3*FH, sdGetSize(), LEFT, 0, NULL, "M"); - lcd_putsLeft(4*FH, STR_SD_SECTORS); + lcdDrawText(MENUS_MARGIN_LEFT, 4*FH, STR_SD_SECTORS); #if defined(SD_GET_FREE_BLOCKNR) lcdDrawNumber(100, 4*FH, SD_GET_FREE_BLOCKNR()/1000, LEFT, 0, NULL, "/"); lcdDrawNumber(150, 4*FH, sdGetNoSectors()/1000, LEFT); @@ -61,7 +61,7 @@ bool menuGeneralSdManagerInfo(evt_t event) lcdDrawNumber(100, 4*FH, sdGetNoSectors()/1000, LEFT, 0, NULL, "k"); #endif - lcd_putsLeft(5*FH, STR_SD_SPEED); + lcdDrawText(MENUS_MARGIN_LEFT, 5*FH, STR_SD_SPEED); lcdDrawNumber(100, 5*FH, SD_GET_SPEED()/1000, LEFT, 0, NULL, "kb/s"); return true; diff --git a/radio/src/gui/Horus/menu_general_setup.cpp b/radio/src/gui/Horus/menu_general_setup.cpp index a0d2f41eb..bbe4f5d27 100644 --- a/radio/src/gui/Horus/menu_general_setup.cpp +++ b/radio/src/gui/Horus/menu_general_setup.cpp @@ -46,10 +46,10 @@ #define MINUTE_SEPARATOR_OFFSET 63 #define SECOND_OFFSET 75 -#define SLIDER_5POS(y, value, label, event, attr) { \ +#define SLIDER_5POS(y, value, event, attr) { \ int8_t tmp = value; \ drawSlider(RADIO_SETUP_2ND_COLUMN, y, 2+tmp, 4, attr); \ - value = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, label, NULL, tmp, -2, +2, attr, event); \ + value = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, NULL, tmp, -2, +2, attr, event); \ } #if defined(SPLASH) && !defined(FSPLASH) @@ -139,7 +139,7 @@ bool menuGeneralSetup(evt_t event) switch(k) { case ITEM_SETUP_DATE: { - lcd_putsLeft(y, STR_DATE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_DATE); LcdFlags flags = 0; if (attr && m_posHorz < 0) { flags |= INVERS; @@ -178,7 +178,7 @@ bool menuGeneralSetup(evt_t event) case ITEM_SETUP_TIME: { - lcd_putsLeft(y, STR_TIME); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TIME); LcdFlags flags = 0; if (attr && m_posHorz < 0) { flags |= INVERS; @@ -211,7 +211,7 @@ bool menuGeneralSetup(evt_t event) #if 0 case ITEM_SETUP_BATT_RANGE: { - lcd_putsLeft(y, STR_BATTERY_RANGE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BATTERY_RANGE); LcdFlags color = WHITE; if (attr && m_posHorz < 0) { color = BLACK; @@ -231,11 +231,12 @@ bool menuGeneralSetup(evt_t event) #endif case ITEM_SETUP_SOUND_LABEL: - lcd_putsLeft(y, STR_SOUND_LABEL); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SOUND_LABEL); break; case ITEM_SETUP_BEEP_MODE: - g_eeGeneral.beepMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_SPEAKER, STR_VBEEPMODE, g_eeGeneral.beepMode, -2, 1, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SPEAKER); + g_eeGeneral.beepMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_VBEEPMODE, g_eeGeneral.beepMode, -2, 1, attr, event); #if defined(FRSKY) if (attr && checkIncDec_Ret) frskySendAlarms(); #endif @@ -243,7 +244,7 @@ bool menuGeneralSetup(evt_t event) case ITEM_SETUP_SPEAKER_VOLUME: { - lcd_putsLeft(y, STR_SPEAKER_VOLUME); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SPEAKER_VOLUME); uint8_t b = g_eeGeneral.speakerVolume+VOLUME_LEVEL_DEF; drawSlider(RADIO_SETUP_2ND_COLUMN, y, b, VOLUME_LEVEL_MAX, attr); if (attr) { @@ -256,21 +257,25 @@ bool menuGeneralSetup(evt_t event) } case ITEM_SETUP_BEEP_VOLUME: - SLIDER_5POS(y, g_eeGeneral.beepVolume, STR_BEEP_VOLUME, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BEEP_VOLUME); + SLIDER_5POS(y, g_eeGeneral.beepVolume, event, attr); break; case ITEM_SETUP_WAV_VOLUME: - SLIDER_5POS(y, g_eeGeneral.wavVolume, STR_WAV_VOLUME, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_WAV_VOLUME); + SLIDER_5POS(y, g_eeGeneral.wavVolume, event, attr); break; case ITEM_SETUP_BACKGROUND_VOLUME: - SLIDER_5POS(y, g_eeGeneral.backgroundVolume, STR_BG_VOLUME, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BG_VOLUME); + SLIDER_5POS(y, g_eeGeneral.backgroundVolume, event, attr); break; case ITEM_SETUP_BEEP_LENGTH: - SLIDER_5POS(y, g_eeGeneral.beepLength, STR_BEEP_LENGTH, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BEEP_LENGTH); + SLIDER_5POS(y, g_eeGeneral.beepLength, event, attr); break; case ITEM_SETUP_SPEAKER_PITCH: - lcd_putsLeft( y, STR_SPKRPITCH); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SPKRPITCH); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.speakerPitch*15, attr|LEFT, 0, "+", "Hz"); if (attr) { CHECK_INCDEC_GENVAR(event, g_eeGeneral.speakerPitch, 0, 20); @@ -279,23 +284,24 @@ bool menuGeneralSetup(evt_t event) #if defined(VARIO) case ITEM_SETUP_VARIO_LABEL: - lcd_putsLeft(y, STR_VARIO); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_VARIO); break; case ITEM_SETUP_VARIO_VOLUME: - SLIDER_5POS(y, g_eeGeneral.varioVolume, TR_SPEAKER_VOLUME, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, TR_SPEAKER_VOLUME); + SLIDER_5POS(y, g_eeGeneral.varioVolume, event, attr); break; case ITEM_SETUP_VARIO_PITCH: - lcd_putsLeft(y, STR_PITCH_AT_ZERO); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PITCH_AT_ZERO); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10), attr|LEFT, 0, NULL, "Hz"); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioPitch, -40, 40); break; case ITEM_SETUP_VARIO_RANGE: - lcd_putsLeft(y, STR_PITCH_AT_MAX); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PITCH_AT_MAX); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, VARIO_FREQUENCY_ZERO+(g_eeGeneral.varioPitch*10)+VARIO_FREQUENCY_RANGE+(g_eeGeneral.varioRange*10), attr|LEFT, 0, NULL, "Hz"); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRange, -80, 80); break; case ITEM_SETUP_VARIO_REPEAT: - lcd_putsLeft(y, STR_REPEAT_AT_ZERO); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_REPEAT_AT_ZERO); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, VARIO_REPEAT_ZERO+(g_eeGeneral.varioRepeat*10), attr|LEFT, 0, NULL, STR_MS); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.varioRepeat, -30, 50); break; @@ -303,25 +309,28 @@ bool menuGeneralSetup(evt_t event) #if defined(HAPTIC) case ITEM_SETUP_HAPTIC_LABEL: - lcd_putsLeft(y, STR_HAPTIC_LABEL); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_HAPTIC_LABEL); break; case ITEM_SETUP_HAPTIC_MODE: - g_eeGeneral.hapticMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_MODE, STR_VBEEPMODE, g_eeGeneral.hapticMode, -2, 1, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MODE); + g_eeGeneral.hapticMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_VBEEPMODE, g_eeGeneral.hapticMode, -2, 1, attr, event); break; case ITEM_SETUP_HAPTIC_LENGTH: - SLIDER_5POS(y, g_eeGeneral.hapticLength, STR_LENGTH, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_LENGTH); + SLIDER_5POS(y, g_eeGeneral.hapticLength, event, attr); break; case ITEM_SETUP_HAPTIC_STRENGTH: - SLIDER_5POS(y, g_eeGeneral.hapticStrength, STR_HAPTICSTRENGTH, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_HAPTICSTRENGTH); + SLIDER_5POS(y, g_eeGeneral.hapticStrength, event, attr); break; #endif #if 0 case ITEM_SETUP_CONTRAST: - lcd_putsLeft(y, STR_CONTRAST); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CONTRAST); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.contrast, attr|LEFT); if (attr) { CHECK_INCDEC_GENVAR(event, g_eeGeneral.contrast, CONTRAST_MIN, CONTRAST_MAX); @@ -331,11 +340,11 @@ bool menuGeneralSetup(evt_t event) #endif case ITEM_SETUP_ALARMS_LABEL: - lcd_putsLeft(y, STR_ALARMS_LABEL); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ALARMS_LABEL); break; case ITEM_SETUP_BATTERY_WARNING: - lcd_putsLeft(y, STR_BATTERYWARNING); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BATTERYWARNING); putsVolts(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.vBatWarn, attr|LEFT); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.vBatWarn, 27, 42); // 2.7-4.2V break; @@ -343,62 +352,66 @@ bool menuGeneralSetup(evt_t event) #if 0 case ITEM_SETUP_MEMORY_WARNING: { + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MEMORYWARNING); uint8_t b = 1-g_eeGeneral.disableMemoryWarning; - g_eeGeneral.disableMemoryWarning = 1 - onoffMenuItem(b, RADIO_SETUP_2ND_COLUMN, y, STR_MEMORYWARNING, attr, event); + g_eeGeneral.disableMemoryWarning = 1 - onoffMenuItem(b, RADIO_SETUP_2ND_COLUMN, y, attr, event); break; } #endif case ITEM_SETUP_ALARM_WARNING: { + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ALARMWARNING); uint8_t b = 1-g_eeGeneral.disableAlarmWarning; - g_eeGeneral.disableAlarmWarning = 1 - onoffMenuItem(b, RADIO_SETUP_2ND_COLUMN, y, STR_ALARMWARNING, attr, event); + g_eeGeneral.disableAlarmWarning = 1 - onoffMenuItem(b, RADIO_SETUP_2ND_COLUMN, y, attr, event); break; } #if defined(PCBSKY9X) case ITEM_SETUP_CAPACITY_WARNING: - lcd_putsLeft(y, STR_CAPAWARNING); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CAPAWARNING); putsValueWithUnit(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.mAhWarn*50, UNIT_MAH, attr|LEFT) ; - if(attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.mAhWarn, 0, 100); + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.mAhWarn, 0, 100); break; #endif #if defined(PCBSKY9X) case ITEM_SETUP_TEMPERATURE_WARNING: - lcd_putsLeft(y, STR_TEMPWARNING); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TEMPWARNING); putsValueWithUnit(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.temperatureWarn, UNIT_TEMPERATURE, attr|LEFT) ; - if(attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.temperatureWarn, 0, 120); // 0 means no alarm + if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.temperatureWarn, 0, 120); // 0 means no alarm break; #endif case ITEM_SETUP_INACTIVITY_ALARM: - lcd_putsLeft( y,STR_INACTIVITYALARM); + lcdDrawText(MENUS_MARGIN_LEFT, y,STR_INACTIVITYALARM); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.inactivityTimer, attr|LEFT, 0, NULL, "m"); if (attr) g_eeGeneral.inactivityTimer = checkIncDec(event, g_eeGeneral.inactivityTimer, 0, 250, EE_GENERAL); //0..250minutes break; #if 0 case ITEM_SETUP_BACKLIGHT_LABEL: - lcd_putsLeft(y, STR_BACKLIGHT_LABEL); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BACKLIGHT_LABEL); break; case ITEM_SETUP_BACKLIGHT_MODE: - g_eeGeneral.backlightMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_MODE, STR_VBLMODE, g_eeGeneral.backlightMode, e_backlight_mode_off, e_backlight_mode_on, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MODE); + g_eeGeneral.backlightMode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_VBLMODE, g_eeGeneral.backlightMode, e_backlight_mode_off, e_backlight_mode_on, attr, event); break; case ITEM_SETUP_FLASH_BEEP: - g_eeGeneral.alarmsFlash = onoffMenuItem(g_eeGeneral.alarmsFlash, RADIO_SETUP_2ND_COLUMN, y, STR_ALARM, attr, event ) ; + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ALARM); + g_eeGeneral.alarmsFlash = onoffMenuItem(g_eeGeneral.alarmsFlash, RADIO_SETUP_2ND_COLUMN, y, attr, event ) ; break; case ITEM_SETUP_BACKLIGHT_DELAY: - lcd_putsLeft(y, STR_BLDELAY); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BLDELAY); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.lightAutoOff*5, attr|LEFT, 0, NULL, "s"); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.lightAutoOff, 0, 600/5); break; case ITEM_SETUP_BRIGHTNESS: - lcd_putsLeft(y, STR_BRIGHTNESS); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BRIGHTNESS); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, 100-g_eeGeneral.backlightBright, attr|LEFT) ; if (attr) { uint8_t b = 100 - g_eeGeneral.backlightBright; @@ -408,7 +421,7 @@ bool menuGeneralSetup(evt_t event) break; case ITEM_SETUP_BACKLIGHT_COLOR: - lcd_putsLeft(y, STR_BLCOLOR); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BLCOLOR); drawSlider(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.backlightColor, 20, attr); if (attr) g_eeGeneral.backlightColor = checkIncDec(event, g_eeGeneral.backlightColor, 0, 20, EE_GENERAL | NO_INCDEC_MARKS); break; @@ -417,7 +430,7 @@ bool menuGeneralSetup(evt_t event) #if 0 case ITEM_SETUP_DISABLE_SPLASH: { - lcd_putsLeft(y, STR_SPLASHSCREEN); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SPLASHSCREEN); if (SPLASH_NEEDED()) { lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, SPLASH_TIMEOUT/100, attr|LEFT, 0, NULL, "s"); } @@ -430,21 +443,23 @@ bool menuGeneralSetup(evt_t event) #endif case ITEM_SETUP_TIMEZONE: - lcd_putsLeft(y, STR_TIMEZONE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TIMEZONE); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, g_eeGeneral.timezone, attr|LEFT); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.timezone, -12, 12); break; case ITEM_SETUP_GPSFORMAT: - g_eeGeneral.gpsFormat = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_GPSCOORD, STR_GPSFORMAT, g_eeGeneral.gpsFormat, 0, 1, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_GPSCOORD); + g_eeGeneral.gpsFormat = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_GPSFORMAT, g_eeGeneral.gpsFormat, 0, 1, attr, event); break; case ITEM_SETUP_COUNTRYCODE: - g_eeGeneral.countryCode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_COUNTRYCODE, STR_COUNTRYCODES, g_eeGeneral.countryCode, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_COUNTRYCODE); + g_eeGeneral.countryCode = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_COUNTRYCODES, g_eeGeneral.countryCode, 0, 2, attr, event); break; case ITEM_SETUP_LANGUAGE: - lcd_putsLeft(y, STR_VOICELANG); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_VOICELANG); lcdDrawText(RADIO_SETUP_2ND_COLUMN, y, currentLanguagePack->name, attr); if (attr) { currentLanguagePackIdx = checkIncDec(event, currentLanguagePackIdx, 0, DIM(languagePacks)-2, EE_GENERAL); @@ -456,12 +471,14 @@ bool menuGeneralSetup(evt_t event) break; case ITEM_SETUP_IMPERIAL: - g_eeGeneral.imperial = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_UNITSSYSTEM, STR_VUNITSSYSTEM, g_eeGeneral.imperial, 0, 1, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_UNITSSYSTEM); + g_eeGeneral.imperial = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_VUNITSSYSTEM, g_eeGeneral.imperial, 0, 1, attr, event); break; #if 0 case ITEM_SETUP_FAI: - onoffMenuItem(g_eeGeneral.fai, RADIO_SETUP_2ND_COLUMN, y, PSTR("FAI Mode"), attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, PSTR("FAI Mode")); + onoffMenuItem(g_eeGeneral.fai, RADIO_SETUP_2ND_COLUMN, y, attr, event); if (attr && checkIncDec_Ret) { if (g_eeGeneral.fai) POPUP_WARNING(PSTR("FAI\001mode blocked!")); @@ -473,19 +490,20 @@ bool menuGeneralSetup(evt_t event) #if defined(MAVLINK) case ITEM_MAVLINK_BAUD: - g_eeGeneral.mavbaud = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_MAVLINK_BAUD_LABEL, STR_MAVLINK_BAUDS, g_eeGeneral.mavbaud, 0, 7, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MAVLINK_BAUD_LABEL); + g_eeGeneral.mavbaud = selectMenuItem(RADIO_SETUP_2ND_COLUMN, y, STR_MAVLINK_BAUDS, g_eeGeneral.mavbaud, 0, 7, attr, event); break; #endif case ITEM_SETUP_SWITCHES_DELAY: - lcd_putsLeft(y, STR_SWITCHES_DELAY); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SWITCHES_DELAY); lcdDrawNumber(RADIO_SETUP_2ND_COLUMN, y, 10*SWITCHES_DELAY(), attr|LEFT, 0, NULL, STR_MS); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.switchesDelay, -15, +15); break; case ITEM_SETUP_RX_CHANNEL_ORD: { - lcd_putsLeft(y, STR_RXCHANNELORD); // RAET->AETR + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_RXCHANNELORD); // RAET->AETR char s[5]; for (uint8_t i=0; i<4; i++) { s[i] = STR_RETA123[channel_order(i+1)]; @@ -498,7 +516,7 @@ bool menuGeneralSetup(evt_t event) case ITEM_SETUP_STICK_MODE: { - lcd_putsLeft(y, NO_INDENT(STR_MODE)); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_MODE)); char s[2] = " "; s[0] = '1'+g_eeGeneral.stickMode; lcdDrawText(RADIO_SETUP_2ND_COLUMN, y, s, attr); diff --git a/radio/src/gui/Horus/menu_general_trainer.cpp b/radio/src/gui/Horus/menu_general_trainer.cpp index f005c8cd3..847f55276 100644 --- a/radio/src/gui/Horus/menu_general_trainer.cpp +++ b/radio/src/gui/Horus/menu_general_trainer.cpp @@ -97,7 +97,7 @@ bool menuGeneralTrainer(evt_t event) } attr = (sub==5) ? blink : 0; - lcd_putsLeft(MENU_CONTENT_TOP + 5*FH, STR_MULTIPLIER); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 5*FH, STR_MULTIPLIER); lcdDrawNumber(TRAINER_COLUMN_1, MENU_CONTENT_TOP + 5*FH, g_eeGeneral.PPM_Multiplier+10, LEFT|attr|PREC1); if (attr) CHECK_INCDEC_GENVAR(event, g_eeGeneral.PPM_Multiplier, -10, 40); diff --git a/radio/src/gui/Horus/menu_general_version.cpp b/radio/src/gui/Horus/menu_general_version.cpp index f30e8ab27..e7662b2ca 100644 --- a/radio/src/gui/Horus/menu_general_version.cpp +++ b/radio/src/gui/Horus/menu_general_version.cpp @@ -40,10 +40,10 @@ bool menuGeneralVersion(evt_t event) { SIMPLE_MENU(STR_MENUVERSION, menuTabGeneral, e_Vers, 1, DEFAULT_SCROLLBAR_X); - lcd_putsLeft(MENU_CONTENT_TOP + FH, vers_stamp); - lcd_putsLeft(MENU_CONTENT_TOP + 2*FH, date_stamp); - lcd_putsLeft(MENU_CONTENT_TOP + 3*FH, time_stamp); - lcd_putsLeft(MENU_CONTENT_TOP + 4*FH, eeprom_stamp); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + FH, vers_stamp); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 2*FH, date_stamp); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 3*FH, time_stamp); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 4*FH, eeprom_stamp); // TODO EEPROM erase + backup // lcd_putsCenter(MENU_HEADER_HEIGHT+6*FH, STR_EEBACKUP); diff --git a/radio/src/gui/Horus/menu_model.cpp b/radio/src/gui/Horus/menu_model.cpp index ce0a2f1a9..898c8c6f1 100644 --- a/radio/src/gui/Horus/menu_model.cpp +++ b/radio/src/gui/Horus/menu_model.cpp @@ -77,14 +77,12 @@ void editCurveRef(coord_t x, coord_t y, CurveRef & curve, evt_t event, uint8_t a #define MIXES_2ND_COLUMN 100 -uint8_t editDelay(const coord_t x, const coord_t y, const evt_t event, const uint8_t attr, const pm_char *str, uint8_t delay) +uint8_t editDelay(const coord_t x, const coord_t y, const evt_t event, const uint8_t attr, uint8_t delay) { - lcdDrawText(x+MENUS_MARGIN_LEFT, y, str, TEXT_COLOR); lcdDrawNumber(x+MIXES_2ND_COLUMN, y, (10/DELAY_STEP)*delay, attr|PREC1|LEFT); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, delay, DELAY_MAX); return delay; } -#define EDIT_DELAY(x, y, event, attr, str, delay) editDelay(x, y, event, attr, str, delay) const MenuFuncP menuTabModel[] = { // menuModelSelect, @@ -201,10 +199,4 @@ void editName(coord_t x, coord_t y, char * name, uint8_t size, evt_t event, uint } } -void editSingleName(coord_t x, coord_t y, const pm_char *label, char *name, uint8_t size, evt_t event, uint8_t active) -{ - lcd_putsLeft(y, label); - editName(x, y, name, size, event, active); -} - static uint8_t s_currIdx; diff --git a/radio/src/gui/Horus/menu_model_curves.cpp b/radio/src/gui/Horus/menu_model_curves.cpp index c0c550e22..333c66df3 100644 --- a/radio/src/gui/Horus/menu_model_curves.cpp +++ b/radio/src/gui/Horus/menu_model_curves.cpp @@ -145,11 +145,11 @@ bool menuModelCurveOne(evt_t event) lcdDrawNumber(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, s_curveChan+1, LEFT|HEADER_COLOR); - lcd_putsLeft(MENU_CONTENT_TOP, STR_NAME); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP, STR_NAME); editName(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + FH, g_model.curveNames[s_curveChan], sizeof(g_model.curveNames[s_curveChan]), event, m_posVert==0); LcdFlags attr = (m_posVert==1 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); - lcd_putsLeft(MENU_CONTENT_TOP + 2*FH, "Type"); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 2*FH, "Type"); lcdDrawTextAtIndex(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 3*FH, STR_CURVE_TYPES, crv.type, attr); if (attr) { uint8_t newType = checkIncDecModelZero(event, crv.type, CURVE_TYPE_LAST); @@ -166,7 +166,7 @@ bool menuModelCurveOne(evt_t event) } attr = (m_posVert==2 ? (s_editMode>0 ? INVERS|BLINK : INVERS) : 0); - lcd_putsLeft(MENU_CONTENT_TOP + 4*FH, STR_COUNT); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 4*FH, STR_COUNT); lcdDrawNumber(MENUS_MARGIN_LEFT+INDENT_WIDTH, MENU_CONTENT_TOP + 5*FH, 5+crv.points, LEFT|attr, 0, NULL, STR_PTS); if (attr) { int count = checkIncDecModel(event, crv.points, -3, 12); // 2pts - 17pts @@ -186,7 +186,7 @@ bool menuModelCurveOne(evt_t event) } } - lcd_putsLeft(MENU_CONTENT_TOP + 6*FH, STR_SMOOTH); + lcdDrawText(MENUS_MARGIN_LEFT, MENU_CONTENT_TOP + 6*FH, STR_SMOOTH); drawCheckBox(lcdNextPos + 10, MENU_CONTENT_TOP + 6*FH, crv.smooth, m_posVert==3 ? INVERS : 0); if (m_posVert==3) crv.smooth = checkIncDecModel(event, crv.smooth, 0, 1); diff --git a/radio/src/gui/Horus/menu_model_heli.cpp b/radio/src/gui/Horus/menu_model_heli.cpp index bf84518e0..d9217de75 100644 --- a/radio/src/gui/Horus/menu_model_heli.cpp +++ b/radio/src/gui/Horus/menu_model_heli.cpp @@ -63,17 +63,18 @@ bool menuModelHeli(evt_t event) switch(k) { case ITEM_HELI_SWASHTYPE: - g_model.swashR.type = selectMenuItem(MODEL_HELI_2ND_COLUMN, y, STR_SWASHTYPE, STR_VSWASHTYPE, g_model.swashR.type, 0, SWASH_TYPE_MAX, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SWASHTYPE); + g_model.swashR.type = selectMenuItem(MODEL_HELI_2ND_COLUMN, y, STR_VSWASHTYPE, g_model.swashR.type, 0, SWASH_TYPE_MAX, attr, event); break; case ITEM_HELI_SWASHRING: - lcd_putsLeft(y, STR_SWASHRING); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SWASHRING); lcdDrawNumber(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.value, LEFT|attr); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.swashR.value, 100); break; case ITEM_HELI_ELE: - lcd_putsLeft(y, STR_ELEVATOR); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ELEVATOR); putsMixerSource(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.elevatorSource, attr); if (attr) CHECK_INCDEC_MODELSOURCE(event, g_model.swashR.elevatorSource, 0, MIXSRC_LAST_CH); break; @@ -85,7 +86,7 @@ bool menuModelHeli(evt_t event) break; case ITEM_HELI_AIL: - lcd_putsLeft(y, STR_AILERON); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_AILERON); putsMixerSource(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.aileronSource, attr); if (attr) CHECK_INCDEC_MODELSOURCE(event, g_model.swashR.aileronSource, 0, MIXSRC_LAST_CH); break; @@ -97,7 +98,7 @@ bool menuModelHeli(evt_t event) break; case ITEM_HELI_COL: - lcd_putsLeft(y, STR_COLLECTIVE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_COLLECTIVE); putsMixerSource(MODEL_HELI_2ND_COLUMN, y, g_model.swashR.collectiveSource, attr); if (attr) CHECK_INCDEC_MODELSOURCE(event, g_model.swashR.collectiveSource, 0, MIXSRC_LAST_CH); break; diff --git a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp index 92cd5596f..6fd2210bd 100644 --- a/radio/src/gui/Horus/menu_model_inputs_mixes.cpp +++ b/radio/src/gui/Horus/menu_model_inputs_mixes.cpp @@ -53,8 +53,6 @@ void displayFlightModes(coord_t x, coord_t y, FlightModesType value, uint8_t att FlightModesType editFlightModes(coord_t x, coord_t y, evt_t event, FlightModesType value, uint8_t attr) { - lcd_putsColumnLeft(x, y, STR_FLMODE); - int posHorz = m_posHorz; displayFlightModes(x, y, value, attr); @@ -96,20 +94,46 @@ coord_t getYCoord(FnFuncP fn, coord_t x) void DrawFunction(FnFuncP fn, int offset) { - lcdDrawVerticalLine(CURVE_CENTER_X-offset, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2, 0xee, CURVE_AXIS_COLOR); - lcdDrawHorizontalLine(CURVE_CENTER_X-CURVE_SIDE_WIDTH-offset, CURVE_CENTER_Y, CURVE_SIDE_WIDTH*2, 0xee, CURVE_AXIS_COLOR); + int left = CURVE_CENTER_X-offset-CURVE_SIDE_WIDTH; + int right = CURVE_CENTER_X-offset+CURVE_SIDE_WIDTH; + int center = CURVE_CENTER_X-offset; + + // Axis + lcdDrawSolidHorizontalLine(left, CURVE_CENTER_Y, CURVE_SIDE_WIDTH*2+1, CURVE_AXIS_COLOR); + lcdDrawSolidVerticalLine(center, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2, CURVE_AXIS_COLOR); + + // Extra lines + lcdDrawVerticalLine(left+CURVE_SIDE_WIDTH/2, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2, STASHED, CURVE_AXIS_COLOR); + lcdDrawVerticalLine(right-CURVE_SIDE_WIDTH/2, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2, STASHED, CURVE_AXIS_COLOR); + lcdDrawHorizontalLine(left, CURVE_CENTER_Y-CURVE_SIDE_WIDTH/2, CURVE_SIDE_WIDTH*2+1, STASHED, CURVE_AXIS_COLOR); + lcdDrawHorizontalLine(left, CURVE_CENTER_Y+CURVE_SIDE_WIDTH/2, CURVE_SIDE_WIDTH*2+1, STASHED, CURVE_AXIS_COLOR); + + // Outside border + lcdDrawSolidVerticalLine(left, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2, TEXT_COLOR); + lcdDrawSolidVerticalLine(right, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2, TEXT_COLOR); + lcdDrawSolidHorizontalLine(left, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2+1, TEXT_COLOR); + lcdDrawSolidHorizontalLine(left, CURVE_CENTER_Y+CURVE_SIDE_WIDTH, CURVE_SIDE_WIDTH*2+1, TEXT_COLOR); + + // Horizontal / Vertical scale + for (int i=0; i<=20; i++) { + lcdDrawSolidHorizontalLine(left-15, CURVE_CENTER_Y-CURVE_SIDE_WIDTH+i*CURVE_SIDE_WIDTH/10, 10, TEXT_COLOR); + lcdDrawSolidVerticalLine(left+i*CURVE_SIDE_WIDTH/10, CURVE_CENTER_Y+CURVE_SIDE_WIDTH+5, 10, TEXT_COLOR); + } coord_t prev_yv = (coord_t)-1; - for (int xv=-CURVE_SIDE_WIDTH; xv<=CURVE_SIDE_WIDTH; xv++) { + for (int xv=-CURVE_SIDE_WIDTH; xv<=CURVE_SIDE_WIDTH; xv+=1) { coord_t yv = getYCoord(fn, xv); if (prev_yv != (coord_t)-1) { - if (abs(yv-prev_yv) <= 1) { - lcdDrawPoint(CURVE_CENTER_X+xv-offset-1, prev_yv, CURVE_COLOR); + if (prev_yv < yv) { + for (int y=prev_yv; y<=yv; y+=1) { + lcdDrawBitmapPattern(CURVE_CENTER_X+xv-offset-2, y-2, LBM_POINT, TEXT_COLOR); + } } else { - uint8_t tmp = (prev_yv < yv ? 0 : 1); - lcdDrawSolidVerticalLine(CURVE_CENTER_X+xv-offset-1, yv+tmp, prev_yv-yv, CURVE_COLOR); + for (int y=yv; y<=prev_yv; y+=1) { + lcdDrawBitmapPattern(CURVE_CENTER_X+xv-offset-2, y-2, LBM_POINT, TEXT_COLOR); + } } } prev_yv = yv; @@ -325,7 +349,6 @@ enum ExposFields { EXPO_FIELD_INPUT_NAME, EXPO_FIELD_NAME, EXPO_FIELD_SOURCE, - EXPO_FIELD_SCALE, EXPO_FIELD_WEIGHT, EXPO_FIELD_OFFSET, CASE_CURVES(EXPO_FIELD_CURVE) @@ -342,102 +365,111 @@ bool menuModelExpoOne(evt_t event) { ExpoData *ed = expoAddress(s_currIdx); - SUBMENU(STR_MENUINPUTS, EXPO_FIELD_MAX, EXPO_ONE_2ND_COLUMN+85, { 0, 0, 0, (ed->srcRaw >= MIXSRC_FIRST_TELEM ? (uint8_t)0 : (uint8_t)HIDDEN_ROW), 0, 0, CASE_CURVES(CURVE_ROWS) CASE_FLIGHT_MODES((MAX_FLIGHT_MODES-1) | NAVIGATION_LINE_BY_LINE) 0 /*, ...*/}); + SUBMENU(STR_MENUINPUTS, EXPO_FIELD_MAX, 0, { 0, 0, (ed->srcRaw >= MIXSRC_FIRST_TELEM ? (uint8_t)1 : (uint8_t)0), 0, 0, CASE_CURVES(CURVE_ROWS) CASE_FLIGHT_MODES((MAX_FLIGHT_MODES-1) | NAVIGATION_LINE_BY_LINE) 0 /*, ...*/}); - putsMixerSource(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, MIXSRC_FIRST_INPUT+ed->chn, HEADER_COLOR); - - int8_t sub = m_posVert; + int sub = m_posVert; coord_t y = MENU_CONTENT_TOP; DrawFunction(expoFn); { + char textx[5]; + char texty[5]; int x = getValue(ed->srcRaw); if (ed->srcRaw >= MIXSRC_FIRST_TELEM) { - putsTelemetryChannelValue(LCD_W-8, 6*FH, ed->srcRaw - MIXSRC_FIRST_TELEM, x); + sprintf(textx, "%d", calcRESXto100(x)); + // TODO putsTelemetryChannelValue(LCD_W-8, 6*FH, ed->srcRaw - MIXSRC_FIRST_TELEM, x); if (ed->scale > 0) x = (x * 1024) / convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale); } else { - lcdDrawNumber(CURVE_CENTER_X+CURVE_SIDE_WIDTH, CURVE_CENTER_Y+CURVE_SIDE_WIDTH-FH+6, calcRESXto1000(x), PREC1); + sprintf(textx, "%d", calcRESXto100(x)); } x = limit(-1024, x, 1024); int y = limit(-1024, expoFn(x), 1024); - lcdDrawNumber(CURVE_CENTER_X-CURVE_SIDE_WIDTH, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, calcRESXto1000(y), LEFT|PREC1); + sprintf(texty, "%d", calcRESXto100(y)); x = divRound(x*CURVE_SIDE_WIDTH, RESX); y = getYCoord(expoFn, x); - lcdDrawVerticalLine(CURVE_CENTER_X+x, y-3, 7, SOLID, CURVE_CURSOR_COLOR); - lcdDrawHorizontalLine(CURVE_CENTER_X+x-3, y, 7, SOLID, CURVE_CURSOR_COLOR); + lcdDrawSolidFilledRect(CURVE_CENTER_X+x, CURVE_CENTER_Y-CURVE_SIDE_WIDTH, 2, 2*CURVE_SIDE_WIDTH+2, CURVE_CURSOR_COLOR); + lcdDrawSolidFilledRect(CURVE_CENTER_X-CURVE_SIDE_WIDTH-2, y-1, 2*CURVE_SIDE_WIDTH+2, 2, CURVE_CURSOR_COLOR); + lcdDrawBitmapPattern(CURVE_CENTER_X+x-4, y-4, LBM_CURVE_POINT, CURVE_CURSOR_COLOR); + lcdDrawBitmapPattern(CURVE_CENTER_X+x-4, y-4, LBM_CURVE_POINT_CENTER, TEXT_BGCOLOR); + + int left = (x < -30 ? CURVE_CENTER_X+x : (x > 30 ? CURVE_CENTER_X+x-34 : CURVE_CENTER_X-17+x/2)); + lcdDrawSolidFilledRect(left, CURVE_CENTER_Y+CURVE_SIDE_WIDTH+2, 36, 17, CURVE_CURSOR_COLOR); + lcdDrawText(left+3+(35-getTextWidth(textx, SMLSIZE))/2, CURVE_CENTER_Y+CURVE_SIDE_WIDTH+3, textx, LEFT|SMLSIZE|TEXT_BGCOLOR); + int top = (y < CURVE_CENTER_Y-8 ? y-1 : (y > CURVE_CENTER_Y+8 ? y-16 : CURVE_CENTER_Y-8+(y-CURVE_CENTER_Y)/2)); + lcdDrawSolidFilledRect(CURVE_CENTER_X-CURVE_SIDE_WIDTH-37, top, 36, 17, CURVE_CURSOR_COLOR); + lcdDrawText(CURVE_CENTER_X-CURVE_SIDE_WIDTH-34+(35-getTextWidth(texty, SMLSIZE))/2, top+1, texty, LEFT|SMLSIZE|TEXT_BGCOLOR); } - for (unsigned int k=0; k0 ? BLINK|INVERS : INVERS) : 0); - switch(i) - { + switch (i) { case EXPO_FIELD_INPUT_NAME: - editSingleName(EXPO_ONE_2ND_COLUMN, y, STR_INPUTNAME, g_model.inputNames[ed->chn], sizeof(g_model.inputNames[ed->chn]), event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_INPUTNAME); + editName(EXPO_ONE_2ND_COLUMN, y, g_model.inputNames[ed->chn], sizeof(g_model.inputNames[ed->chn]), event, attr); break; case EXPO_FIELD_NAME: - editSingleName(EXPO_ONE_2ND_COLUMN, y, STR_EXPONAME, ed->name, sizeof(ed->name), event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_EXPONAME); + editName(EXPO_ONE_2ND_COLUMN, y, ed->name, sizeof(ed->name), event, attr); break; case EXPO_FIELD_SOURCE: - lcd_putsLeft(y, NO_INDENT(STR_SOURCE)); - putsMixerSource(EXPO_ONE_2ND_COLUMN, y, ed->srcRaw, STREXPANDED|attr); - if (attr) ed->srcRaw = checkIncDec(event, ed->srcRaw, INPUTSRC_FIRST, INPUTSRC_LAST, EE_MODEL|INCDEC_SOURCE|NO_INCDEC_MARKS, isInputSourceAvailable); - break; - - case EXPO_FIELD_SCALE: - lcd_putsLeft(y, STR_SCALE); - putsTelemetryChannelValue(EXPO_ONE_2ND_COLUMN, y, (ed->srcRaw - MIXSRC_FIRST_TELEM)/3, convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale), LEFT|attr); - if (attr) ed->scale = checkIncDec(event, ed->scale, 0, maxTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1), EE_MODEL); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_SOURCE)); + putsMixerSource(EXPO_ONE_2ND_COLUMN, y, ed->srcRaw, STREXPANDED|(m_posHorz==0?attr:0)); + if (attr && m_posHorz==0) ed->srcRaw = checkIncDec(event, ed->srcRaw, INPUTSRC_FIRST, INPUTSRC_LAST, EE_MODEL|INCDEC_SOURCE|NO_INCDEC_MARKS, isInputSourceAvailable); + if (ed->srcRaw >= MIXSRC_FIRST_TELEM) { + putsTelemetryChannelValue(EXPO_ONE_2ND_COLUMN+60, y, (ed->srcRaw - MIXSRC_FIRST_TELEM)/3, convertTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1, ed->scale), LEFT|(m_posHorz==1?attr:0)); + if (attr && m_posHorz == 1) ed->scale = checkIncDec(event, ed->scale, 0, maxTelemValue(ed->srcRaw - MIXSRC_FIRST_TELEM + 1), EE_MODEL); + } + else if (attr) { + m_posHorz = 0; + } break; case EXPO_FIELD_WEIGHT: - lcd_putsLeft(y, STR_WEIGHT); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_WEIGHT); ed->weight = GVAR_MENU_ITEM(EXPO_ONE_2ND_COLUMN, y, ed->weight, MIN_EXPO_WEIGHT, 100, LEFT|attr, 0, event); break; case EXPO_FIELD_OFFSET: - lcd_putsLeft(y, NO_INDENT(STR_OFFSET)); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_OFFSET)); ed->offset = GVAR_MENU_ITEM(EXPO_ONE_2ND_COLUMN, y, ed->offset, -100, 100, LEFT|attr, 0, event); break; #if defined(CURVES) case EXPO_FIELD_CURVE: - lcd_putsLeft(y, STR_CURVE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CURVE); editCurveRef(EXPO_ONE_2ND_COLUMN, y, ed->curve, event, attr); break; #endif #if defined(FLIGHT_MODES) case EXPO_FIELD_FLIGHT_MODES: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_FLMODE); ed->flightModes = editFlightModes(EXPO_ONE_2ND_COLUMN, y, event, ed->flightModes, attr); break; #endif case EXPO_FIELD_SWITCH: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SWITCH); ed->swtch = switchMenuItem(EXPO_ONE_2ND_COLUMN, y, ed->swtch, attr, event); break; case EXPO_FIELD_SIDE: - ed->mode = 4 - selectMenuItem(EXPO_ONE_2ND_COLUMN, y, STR_SIDE, STR_VSIDE, 4-ed->mode, 1, 3, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SIDE); + ed->mode = 4 - selectMenuItem(EXPO_ONE_2ND_COLUMN, y, STR_VSIDE, 4-ed->mode, 1, 3, attr, event); break; case EXPO_FIELD_TRIM: uint8_t not_stick = (ed->srcRaw > MIXSRC_Ail); int8_t carryTrim = -ed->carryTrim; - lcd_putsLeft(y, STR_TRIM); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TRIM); lcdDrawTextAtIndex(EXPO_ONE_2ND_COLUMN, y, STR_VMIXTRIMS, (not_stick && carryTrim == 0) ? 0 : carryTrim+1, m_posHorz==0 ? attr : 0); if (attr) ed->carryTrim = -checkIncDecModel(event, carryTrim, not_stick ? TRIM_ON : -TRIM_OFF, -TRIM_AIL); break; @@ -474,6 +506,7 @@ void gvarWeightItem(coord_t x, coord_t y, MixData *md, uint8_t attr, evt_t event MD_UNION_TO_WEIGHT(weight, md); } +#if 0 #define GAUGE_WIDTH 33 #define GAUGE_HEIGHT 6 void drawOffsetBar(uint8_t x, uint8_t y, MixData * md) @@ -492,15 +525,14 @@ void drawOffsetBar(uint8_t x, uint8_t y, MixData * md) barMax = 101; lcdDrawHorizontalLine(x-2, y, GAUGE_WIDTH+2, DOTTED); lcdDrawHorizontalLine(x-2, y+GAUGE_HEIGHT, GAUGE_WIDTH+2, DOTTED); - // lcdDrawSolidVerticalLine(x-2, y+1, GAUGE_HEIGHT-1); - // lcdDrawSolidVerticalLine(x+GAUGE_WIDTH-1, y+1, GAUGE_HEIGHT-1); + lcdDrawSolidVerticalLine(x-2, y+1, GAUGE_HEIGHT-1); + lcdDrawSolidVerticalLine(x+GAUGE_WIDTH-1, y+1, GAUGE_HEIGHT-1); if (barMin <= barMax) { int8_t right = (barMax * GAUGE_WIDTH) / 200; int8_t left = ((barMin * GAUGE_WIDTH) / 200)-1; - // lcdDrawSolidFilledRect(x+GAUGE_WIDTH/2+left, y+2, right-left, GAUGE_HEIGHT-3); + lcdDrawSolidFilledRect(x+GAUGE_WIDTH/2+left, y+2, right-left, GAUGE_HEIGHT-3); } - // lcdDrawSolidVerticalLine(x+GAUGE_WIDTH/2-1, y, GAUGE_HEIGHT+1); -#if 0 // TODO + lcdDrawSolidVerticalLine(x+GAUGE_WIDTH/2-1, y, GAUGE_HEIGHT+1); if (barMin == -101) { for (uint8_t i=0; i<3; ++i) { lcdDrawPoint(x+i, y+4-i); @@ -513,10 +545,10 @@ void drawOffsetBar(uint8_t x, uint8_t y, MixData * md) lcdDrawPoint(x+GAUGE_WIDTH-5+i, y+4-i); } } -#endif } #undef GAUGE_WIDTH #undef GAUGE_HEIGHT +#endif bool menuModelMixOne(evt_t event) { @@ -524,47 +556,45 @@ bool menuModelMixOne(evt_t event) SUBMENU(s_currCh ? STR_INSERTMIX : STR_EDITMIX, MIX_FIELD_COUNT, 0, { 0, 0, 0, 0, 0, CASE_CURVES(1) CASE_FLIGHT_MODES((MAX_FLIGHT_MODES-1) | NAVIGATION_LINE_BY_LINE) 0 /*, ...*/ }); - putsChn(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+2, md2->destCh+1, HEADER_COLOR); + putsChn(MENU_TITLE_NEXT_POS, MENU_TITLE_TOP+1, md2->destCh+1, TEXT_COLOR); // The separation line between 2 columns - lcdDrawVerticalLine(MENU_COLUMN2_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, SOLID, HEADER_COLOR); + lcdDrawSolidVerticalLine(MENU_COLUMN2_X-20, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H+5, TEXT_COLOR); int8_t sub = m_posVert; int8_t editMode = s_editMode; for (int k=0; k<2*NUM_BODY_LINES; k++) { coord_t y; - coord_t x; if (k >= NUM_BODY_LINES) { - y = MENU_CONTENT_TOP + (k-NUM_BODY_LINES)*FH; - x = MENU_COLUMN2_X; + y = MENU_CONTENT_TOP + (k-NUM_BODY_LINES)*FH + 2; } else { - y = MENU_CONTENT_TOP + k*FH; - x = 0; + y = MENU_CONTENT_TOP + k*FH + 2; } int8_t i = k; LcdFlags attr = (sub==i ? (editMode>0 ? BLINK|INVERS : INVERS) : 0); switch(i) { case MIX_FIELD_NAME: - editSingleName(x+MIXES_2ND_COLUMN, y, STR_MIXNAME, md2->name, sizeof(md2->name), event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MIXNAME); + editName(MIXES_2ND_COLUMN, y, md2->name, sizeof(md2->name), event, attr); break; case MIX_FIELD_SOURCE: - lcd_putsLeft(y, NO_INDENT(STR_SOURCE)); - putsMixerSource(x+MIXES_2ND_COLUMN, y, md2->srcRaw, STREXPANDED|attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_SOURCE)); + putsMixerSource(MIXES_2ND_COLUMN, y, md2->srcRaw, STREXPANDED|attr); if (attr) CHECK_INCDEC_MODELSOURCE(event, md2->srcRaw, 1, MIXSRC_LAST); break; case MIX_FIELD_WEIGHT: - lcd_putsColumnLeft(x, y, STR_WEIGHT); - gvarWeightItem(x+MIXES_2ND_COLUMN, y, md2, attr|LEFT, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_WEIGHT); + gvarWeightItem(MIXES_2ND_COLUMN, y, md2, attr|LEFT, event); break; case MIX_FIELD_OFFSET: { - lcd_putsColumnLeft(x, y, NO_INDENT(STR_OFFSET)); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_OFFSET)); u_int8int16_t offset; MD_OFFSET_TO_UNION(md2, offset); - offset.word = GVAR_MENU_ITEM(x+MIXES_2ND_COLUMN, y, offset.word, GV_RANGELARGE_OFFSET_NEG, GV_RANGELARGE_OFFSET, attr|LEFT, 0, event); + offset.word = GVAR_MENU_ITEM(MIXES_2ND_COLUMN, y, offset.word, GV_RANGELARGE_OFFSET_NEG, GV_RANGELARGE_OFFSET, attr|LEFT, 0, event); MD_UNION_TO_OFFSET(offset, md2); #if 0 drawOffsetBar(x+MIXES_2ND_COLUMN+22, y, md2); @@ -572,50 +602,45 @@ bool menuModelMixOne(evt_t event) break; } case MIX_FIELD_TRIM: - lcd_putsColumnLeft(x, y, STR_TRIM); - drawCheckBox(x+MIXES_2ND_COLUMN, y, !md2->carryTrim, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TRIM); + drawCheckBox(MIXES_2ND_COLUMN, y, !md2->carryTrim, attr); if (attr) md2->carryTrim = !checkIncDecModel(event, !md2->carryTrim, 0, 1); break; #if defined(CURVES) case MIX_FIELD_CURVE: - { - lcd_putsColumnLeft(x, y, STR_CURVE); - editCurveRef(x+MIXES_2ND_COLUMN, y, md2->curve, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CURVE); + editCurveRef(MIXES_2ND_COLUMN, y, md2->curve, event, attr); break; - } #endif #if defined(FLIGHT_MODES) case MIX_FIELD_FLIGHT_PHASE: - md2->flightModes = editFlightModes(x+MIXES_2ND_COLUMN, y, event, md2->flightModes, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_FLMODE); + md2->flightModes = editFlightModes(MIXES_2ND_COLUMN, y, event, md2->flightModes, attr); break; #endif case MIX_FIELD_SWITCH: - md2->swtch = switchMenuItem(x+MIXES_2ND_COLUMN, y, md2->swtch, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SWITCH); + md2->swtch = switchMenuItem(MIXES_2ND_COLUMN, y, md2->swtch, attr, event); break; -#if 0 - case MIX_FIELD_WARNING: - lcd_putsColumnLeft(x+MIXES_2ND_COLUMN, y, STR_MIXWARNING); - if (md2->mixWarn) - lcdDrawNumber(x+MIXES_2ND_COLUMN, y, md2->mixWarn, attr|LEFT); - else - lcdDrawText(x+MIXES_2ND_COLUMN, y, STR_OFF, attr); - if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, md2->mixWarn, 3); - break; -#endif case MIX_FIELD_MLTPX: - md2->mltpx = selectMenuItem(x+MIXES_2ND_COLUMN, y, STR_MULTPX, STR_VMLTPX, md2->mltpx, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MULTPX); + md2->mltpx = selectMenuItem(MIXES_2ND_COLUMN, y, STR_VMLTPX, md2->mltpx, 0, 2, attr, event); break; case MIX_FIELD_DELAY_UP: - md2->delayUp = EDIT_DELAY(x, y, event, attr, STR_DELAYUP, md2->delayUp); + lcdDrawText(MENU_COLUMN2_X+MENUS_MARGIN_LEFT, y, STR_DELAYUP); + md2->delayUp = editDelay(MENU_COLUMN2_X, y, event, attr, md2->delayUp); break; case MIX_FIELD_DELAY_DOWN: - md2->delayDown = EDIT_DELAY(x, y, event, attr, STR_DELAYDOWN, md2->delayDown); + lcdDrawText(MENU_COLUMN2_X+MENUS_MARGIN_LEFT, y, STR_DELAYDOWN); + md2->delayDown = editDelay(MENU_COLUMN2_X, y, event, attr, md2->delayDown); break; case MIX_FIELD_SLOW_UP: - md2->speedUp = EDIT_DELAY(x, y, event, attr, STR_SLOWUP, md2->speedUp); + lcdDrawText(MENU_COLUMN2_X+MENUS_MARGIN_LEFT, y, STR_SLOWUP); + md2->speedUp = editDelay(MENU_COLUMN2_X, y, event, attr, md2->speedUp); break; case MIX_FIELD_SLOW_DOWN: - md2->speedDown = EDIT_DELAY(x, y, event, attr, STR_SLOWDOWN, md2->speedDown); + lcdDrawText(MENU_COLUMN2_X+MENUS_MARGIN_LEFT, y, STR_SLOWDOWN); + md2->speedDown = editDelay(MENU_COLUMN2_X, y, event, attr, md2->speedDown); break; } } diff --git a/radio/src/gui/Horus/menu_model_setup.cpp b/radio/src/gui/Horus/menu_model_setup.cpp index a62b614b7..c78429f61 100644 --- a/radio/src/gui/Horus/menu_model_setup.cpp +++ b/radio/src/gui/Horus/menu_model_setup.cpp @@ -122,7 +122,7 @@ void editTimerMode(int timerIdx, coord_t y, LcdFlags attr, evt_t event) { TimerData * timer = &g_model.timers[timerIdx]; if (attr && m_posHorz < 0) { - lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, 90+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN+1, 90+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } putsStrIdx(MENUS_MARGIN_LEFT, y, STR_TIMER, timerIdx+1); putsTimerMode(MODEL_SETUP_2ND_COLUMN, y, timer->mode, (m_posHorz<=0 ? attr : 0)); @@ -219,11 +219,12 @@ bool menuModelSetup(evt_t event) switch(k) { case ITEM_MODEL_NAME: - editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_MODELNAME, g_model.header.name, sizeof(g_model.header.name), event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MODELNAME); + editName(MODEL_SETUP_2ND_COLUMN, y, g_model.header.name, sizeof(g_model.header.name), event, attr); break; case ITEM_MODEL_BITMAP: - lcd_putsLeft(y, STR_BITMAP); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BITMAP); if (ZEXIST(g_model.header.bitmap)) lcdDrawTextWithLen(MODEL_SETUP_2ND_COLUMN, y, g_model.header.bitmap, sizeof(g_model.header.bitmap), attr); else @@ -245,19 +246,23 @@ bool menuModelSetup(evt_t event) break; case ITEM_MODEL_TIMER1_NAME: - editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER_NAME, g_model.timers[0].name, LEN_TIMER_NAME, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TIMER_NAME); + editName(MODEL_SETUP_2ND_COLUMN, y, g_model.timers[0].name, LEN_TIMER_NAME, event, attr); break; case ITEM_MODEL_TIMER1_MINUTE_BEEP: - g_model.timers[0].minuteBeep = onoffMenuItem(g_model.timers[0].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, STR_MINUTEBEEP, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MINUTEBEEP); + g_model.timers[0].minuteBeep = onoffMenuItem(g_model.timers[0].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, attr, event); break; case ITEM_MODEL_TIMER1_COUNTDOWN_BEEP: - g_model.timers[0].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_BEEPCOUNTDOWN, STR_VBEEPCOUNTDOWN, g_model.timers[0].countdownBeep, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BEEPCOUNTDOWN); + g_model.timers[0].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VBEEPCOUNTDOWN, g_model.timers[0].countdownBeep, 0, 2, attr, event); break; case ITEM_MODEL_TIMER1_PERSISTENT: - g_model.timers[0].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_PERSISTENT, STR_VPERSISTENT, g_model.timers[0].persistent, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PERSISTENT); + g_model.timers[0].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VPERSISTENT, g_model.timers[0].persistent, 0, 2, attr, event); break; #if TIMERS > 1 @@ -266,19 +271,23 @@ bool menuModelSetup(evt_t event) break; case ITEM_MODEL_TIMER2_NAME: - editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER_NAME, g_model.timers[1].name, LEN_TIMER_NAME, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TIMER_NAME); + editName(MODEL_SETUP_2ND_COLUMN, y, g_model.timers[1].name, LEN_TIMER_NAME, event, attr); break; case ITEM_MODEL_TIMER2_MINUTE_BEEP: - g_model.timers[1].minuteBeep = onoffMenuItem(g_model.timers[1].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, STR_MINUTEBEEP, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MINUTEBEEP); + g_model.timers[1].minuteBeep = onoffMenuItem(g_model.timers[1].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, attr, event); break; case ITEM_MODEL_TIMER2_COUNTDOWN_BEEP: - g_model.timers[1].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_BEEPCOUNTDOWN, STR_VBEEPCOUNTDOWN, g_model.timers[1].countdownBeep, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BEEPCOUNTDOWN); + g_model.timers[1].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VBEEPCOUNTDOWN, g_model.timers[1].countdownBeep, 0, 2, attr, event); break; case ITEM_MODEL_TIMER2_PERSISTENT: - g_model.timers[1].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_PERSISTENT, STR_VPERSISTENT, g_model.timers[1].persistent, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PERSISTENT); + g_model.timers[1].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VPERSISTENT, g_model.timers[1].persistent, 0, 2, attr, event); break; #endif @@ -288,28 +297,34 @@ bool menuModelSetup(evt_t event) break; case ITEM_MODEL_TIMER3_NAME: - editSingleName(MODEL_SETUP_2ND_COLUMN, y, STR_TIMER_NAME, g_model.timers[2].name, LEN_TIMER_NAME, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TIMER_NAME); + editName(MODEL_SETUP_2ND_COLUMN, y, g_model.timers[2].name, LEN_TIMER_NAME, event, attr); break; case ITEM_MODEL_TIMER3_MINUTE_BEEP: - g_model.timers[2].minuteBeep = onoffMenuItem(g_model.timers[2].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, STR_MINUTEBEEP, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MINUTEBEEP); + g_model.timers[2].minuteBeep = onoffMenuItem(g_model.timers[2].minuteBeep, MODEL_SETUP_2ND_COLUMN, y, attr, event); break; case ITEM_MODEL_TIMER3_COUNTDOWN_BEEP: - g_model.timers[2].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_BEEPCOUNTDOWN, STR_VBEEPCOUNTDOWN, g_model.timers[2].countdownBeep, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BEEPCOUNTDOWN); + g_model.timers[2].countdownBeep = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VBEEPCOUNTDOWN, g_model.timers[2].countdownBeep, 0, 2, attr, event); break; case ITEM_MODEL_TIMER3_PERSISTENT: - g_model.timers[2].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_PERSISTENT, STR_VPERSISTENT, g_model.timers[2].persistent, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PERSISTENT); + g_model.timers[2].persistent = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VPERSISTENT, g_model.timers[2].persistent, 0, 2, attr, event); break; #endif case ITEM_MODEL_EXTENDED_LIMITS: - ON_OFF_MENU_ITEM(g_model.extendedLimits, MODEL_SETUP_2ND_COLUMN, y, STR_ELIMITS, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ELIMITS); + onoffMenuItem(g_model.extendedLimits, MODEL_SETUP_2ND_COLUMN, y, attr, event); break; case ITEM_MODEL_EXTENDED_TRIMS: - ON_OFF_MENU_ITEM(g_model.extendedTrims, MODEL_SETUP_2ND_COLUMN, y, STR_ETRIMS, m_posHorz<=0 ? attr : 0, event==EVT_KEY_BREAK(KEY_ENTER) ? event : 0); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ETRIMS); + onoffMenuItem(g_model.extendedTrims, MODEL_SETUP_2ND_COLUMN, y, m_posHorz<=0 ? attr : 0, event==EVT_KEY_BREAK(KEY_ENTER) ? event : 0); lcdDrawText(MODEL_SETUP_2ND_COLUMN+18, y, STR_RESET_BTN, m_posHorz>0 && !s_noHi ? attr : 0); if (attr && m_posHorz>0) { s_editMode = 0; @@ -325,15 +340,17 @@ bool menuModelSetup(evt_t event) break; case ITEM_MODEL_DISPLAY_TRIMS: - g_model.displayTrims = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, "Display Trims", "\006No\0 ChangeYes", g_model.displayTrims, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_DISPLAY_TRIMS); + g_model.displayTrims = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, "\006No\0 ChangeYes", g_model.displayTrims, 0, 2, attr, event); break; case ITEM_MODEL_TRIM_INC: - g_model.trimInc = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_TRIMINC, STR_VTRIMINC, g_model.trimInc, -2, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TRIMINC); + g_model.trimInc = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VTRIMINC, g_model.trimInc, -2, 2, attr, event); break; case ITEM_MODEL_THROTTLE_LABEL: - lcd_putsLeft(y, STR_THROTTLE_LABEL); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_THROTTLE_LABEL); break; #if 0 @@ -344,7 +361,7 @@ bool menuModelSetup(evt_t event) case ITEM_MODEL_THROTTLE_TRACE: { - lcd_putsLeft(y, STR_TTRACE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TTRACE); if (attr) CHECK_INCDEC_MODELVAR_ZERO(event, g_model.thrTraceSrc, NUM_POTS+NUM_CHNOUT); uint8_t idx = g_model.thrTraceSrc + MIXSRC_Thr; if (idx > MIXSRC_Thr) @@ -356,24 +373,27 @@ bool menuModelSetup(evt_t event) } case ITEM_MODEL_THROTTLE_TRIM: - ON_OFF_MENU_ITEM(g_model.thrTrim, MODEL_SETUP_2ND_COLUMN, y, STR_TTRIM, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TTRIM); + onoffMenuItem(g_model.thrTrim, MODEL_SETUP_2ND_COLUMN, y, attr, event); break; case ITEM_MODEL_PREFLIGHT_LABEL: - lcd_putsLeft(y, STR_PREFLIGHT); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PREFLIGHT); break; case ITEM_MODEL_CHECKLIST_DISPLAY: - ON_OFF_MENU_ITEM(g_model.displayChecklist, MODEL_SETUP_2ND_COLUMN, y, STR_CHECKLIST, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CHECKLIST); + onoffMenuItem(g_model.displayChecklist, MODEL_SETUP_2ND_COLUMN, y, attr, event); break; case ITEM_MODEL_THROTTLE_WARNING: - g_model.disableThrottleWarning = !onoffMenuItem(!g_model.disableThrottleWarning, MODEL_SETUP_2ND_COLUMN, y, STR_THROTTLEWARNING, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_THROTTLEWARNING); + g_model.disableThrottleWarning = !onoffMenuItem(!g_model.disableThrottleWarning, MODEL_SETUP_2ND_COLUMN, y, attr, event); break; case ITEM_MODEL_SWITCHES_WARNING: { - lcd_putsLeft(y, STR_SWITCHWARNING); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SWITCHWARNING); if (!READ_ONLY() && attr && m_posHorz<0 && event==EVT_KEY_LONG(KEY_ENTER)) { killEvents(event); s_noHi = NO_HI_LEN; @@ -431,7 +451,7 @@ bool menuModelSetup(evt_t event) case ITEM_MODEL_POT_WARNING: { - lcd_putsLeft(y, STR_POTWARNING); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_POTWARNING); if (attr) { if (m_posHorz) s_editMode = 0; if (!READ_ONLY() && m_posHorz) { @@ -470,7 +490,7 @@ bool menuModelSetup(evt_t event) } case ITEM_MODEL_BEEP_CENTER: - lcd_putsLeft(y, STR_BEEPCTR); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_BEEPCTR); if (attr && m_posHorz < 0) { lcdDrawSolidFilledRect(MODEL_SETUP_2ND_COLUMN-INVERT_HORZ_MARGIN, y-INVERT_VERT_MARGIN, (NUM_STICKS+NUM_POTS)*13+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); } @@ -493,21 +513,22 @@ bool menuModelSetup(evt_t event) break; case ITEM_MODEL_USE_GLOBAL_FUNCTIONS: - lcd_putsLeft(y, "Use Global Funcs"); + lcdDrawText(MENUS_MARGIN_LEFT, y, "Use Global Funcs"); drawCheckBox(MODEL_SETUP_2ND_COLUMN, y, !g_model.noGlobalFunctions, attr); if (attr) g_model.noGlobalFunctions = !checkIncDecModel(event, !g_model.noGlobalFunctions, 0, 1); break; case ITEM_MODEL_TRAINER_MODE: - g_model.trainerMode = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_MODE, STR_VTRAINERMODES, g_model.trainerMode, TRAINER_MODE_MASTER, TRAINER_MODE_SLAVE, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MODE); + g_model.trainerMode = selectMenuItem(MODEL_SETUP_2ND_COLUMN, y, STR_VTRAINERMODES, g_model.trainerMode, TRAINER_MODE_MASTER, TRAINER_MODE_SLAVE, attr, event); break; case ITEM_MODEL_EXTERNAL_MODULE_LABEL: - lcd_putsLeft(y, TR_EXTERNALRF); + lcdDrawText(MENUS_MARGIN_LEFT, y, TR_EXTERNALRF); break; case ITEM_MODEL_EXTERNAL_MODULE_MODE: - lcd_putsLeft(y, STR_MODE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MODE); lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_TARANIS_PROTOCOLS, g_model.moduleData[EXTERNAL_MODULE].type, (m_posHorz==0 ? attr : 0)); if (IS_MODULE_XJT(EXTERNAL_MODULE)) lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN+40, y, STR_XJT_PROTOCOLS, 1+g_model.moduleData[EXTERNAL_MODULE].rfProtocol, (m_posHorz==1 ? attr : 0)); @@ -540,7 +561,7 @@ bool menuModelSetup(evt_t event) break; case ITEM_MODEL_TRAINER_LABEL: - lcd_putsLeft(y, STR_TRAINER); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TRAINER); break; case ITEM_MODEL_EXTERNAL_MODULE_CHANNELS: @@ -548,7 +569,7 @@ bool menuModelSetup(evt_t event) { uint8_t moduleIdx = CURRENT_MODULE_EDITED(k); ModuleData & moduleData = g_model.moduleData[moduleIdx]; - lcd_putsLeft(y, STR_CHANNELRANGE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CHANNELRANGE); if ((int8_t)PORT_CHANNELS_ROWS(moduleIdx) >= 0) { putsStrIdx(MODEL_SETUP_2ND_COLUMN, y, STR_CH, moduleData.channelsStart+1, m_posHorz==0 ? attr : 0); lcdDrawText(MODEL_SETUP_2ND_COLUMN+30, y, "-"); @@ -577,7 +598,7 @@ bool menuModelSetup(evt_t event) uint8_t moduleIdx = CURRENT_MODULE_EDITED(k); ModuleData & moduleData = g_model.moduleData[moduleIdx]; if (IS_MODULE_PPM(moduleIdx)) { - lcd_putsLeft(y, STR_PPMFRAME); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PPMFRAME); lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, (int16_t)moduleData.ppmFrameLength*5 + 225, (m_posHorz<=0 ? attr : 0) | PREC1|LEFT, 0, NULL, STR_MS); lcdDrawNumber(MODEL_SETUP_2ND_COLUMN+70, y, (moduleData.ppmDelay*50)+300, (CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0, 0, NULL, "us"); lcdDrawText(MODEL_SETUP_2ND_COLUMN+90, y, moduleData.ppmPulsePol ? "+" : "-", (CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0); @@ -601,11 +622,11 @@ bool menuModelSetup(evt_t event) coord_t xOffsetBind = MODEL_SETUP_BIND_OFS; if (IS_MODULE_XJT(moduleIdx) && IS_D8_RX(moduleIdx)) { xOffsetBind = 0; - lcd_putsLeft(y, INDENT "Receiver"); + lcdDrawText(MENUS_MARGIN_LEFT, y, INDENT "Receiver"); if (attr) l_posHorz += 1; } else { - lcd_putsLeft(y, STR_RXNUM); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_RXNUM); } if (IS_MODULE_XJT(moduleIdx) || IS_MODULE_DSM2(moduleIdx)) { if (xOffsetBind) lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0|LEFT, 2); @@ -632,7 +653,7 @@ bool menuModelSetup(evt_t event) { uint8_t moduleIdx = CURRENT_MODULE_EDITED(k); ModuleData & moduleData = g_model.moduleData[moduleIdx]; - lcd_putsLeft(y, TR_FAILSAFE); + lcdDrawText(MENUS_MARGIN_LEFT, y, TR_FAILSAFE); if (IS_MODULE_XJT(moduleIdx)) { lcdDrawTextAtIndex(MODEL_SETUP_2ND_COLUMN, y, STR_VFAILSAFE, moduleData.failsafeMode, m_posHorz==0 ? attr : 0); if (moduleData.failsafeMode == FAILSAFE_CUSTOM) lcdDrawText(MODEL_SETUP_2ND_COLUMN + MODEL_SETUP_SET_FAILSAFE_OFS, y, STR_SET, m_posHorz==1 ? attr : 0); diff --git a/radio/src/gui/Horus/menu_model_telemetry.cpp b/radio/src/gui/Horus/menu_model_telemetry.cpp index daf2222f3..1b6a22618 100644 --- a/radio/src/gui/Horus/menu_model_telemetry.cpp +++ b/radio/src/gui/Horus/menu_model_telemetry.cpp @@ -233,11 +233,13 @@ bool menuModelSensor(evt_t event) switch (k) { case SENSOR_FIELD_NAME: - editSingleName(SENSOR_2ND_COLUMN, y, STR_NAME, sensor->label, TELEM_LABEL_LEN, event, attr); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_NAME); + editName(SENSOR_2ND_COLUMN, y, sensor->label, TELEM_LABEL_LEN, event, attr); break; case SENSOR_FIELD_TYPE: - sensor->type = selectMenuItem(SENSOR_2ND_COLUMN, y, NO_INDENT(STR_TYPE), STR_VSENSORTYPES, sensor->type, 0, 1, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_TYPE)); + sensor->type = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_VSENSORTYPES, sensor->type, 0, 1, attr, event); if (attr && checkIncDec_Ret) { sensor->instance = 0; if (sensor->type == TELEM_TYPE_CALCULATED) { @@ -250,7 +252,7 @@ bool menuModelSensor(evt_t event) case SENSOR_FIELD_ID: if (sensor->type == TELEM_TYPE_CUSTOM) { - lcd_putsLeft(y, STR_ID); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ID); lcd_outhex4(SENSOR_2ND_COLUMN, y, sensor->id, LEFT|(m_posHorz==0 ? attr : 0)); lcdDrawNumber(SENSOR_3RD_COLUMN, y, sensor->instance, LEFT|(m_posHorz==1 ? attr : 0)); if (attr) { @@ -266,7 +268,8 @@ bool menuModelSensor(evt_t event) } } else { - sensor->formula = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_FORMULA, STR_VFORMULAS, sensor->formula, 0, TELEM_FORMULA_LAST, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_FORMULA); + sensor->formula = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_VFORMULAS, sensor->formula, 0, TELEM_FORMULA_LAST, attr, event); if (attr && checkIncDec_Ret) { sensor->param = 0; if (sensor->formula == TELEM_FORMULA_CELL) { @@ -286,7 +289,7 @@ bool menuModelSensor(evt_t event) break; case SENSOR_FIELD_UNIT: - lcd_putsLeft(y, STR_UNIT); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_UNIT); // TODO flash saving with selectMenuItem where I copied those 2 lines? lcdDrawTextAtIndex(SENSOR_2ND_COLUMN, y, STR_VTELEMUNIT, sensor->unit, attr); if (attr) { @@ -298,7 +301,8 @@ bool menuModelSensor(evt_t event) break; case SENSOR_FIELD_PRECISION: - sensor->prec = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_PRECISION, STR_VPREC, sensor->prec, 0, 2, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PRECISION); + sensor->prec = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_VPREC, sensor->prec, 0, 2, attr, event); if (attr && checkIncDec_Ret) { telemetryItems[s_currIdx].clear(); } @@ -307,7 +311,7 @@ bool menuModelSensor(evt_t event) case SENSOR_FIELD_PARAM1: if (sensor->type == TELEM_TYPE_CALCULATED) { if (sensor->formula == TELEM_FORMULA_CELL) { - lcd_putsLeft(y, STR_CELLSENSOR); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CELLSENSOR); putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->cell.source ? MIXSRC_FIRST_TELEM+3*(sensor->cell.source-1) : 0, attr); if (attr) { sensor->cell.source = checkIncDec(event, sensor->cell.source, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isCellsSensor); @@ -315,7 +319,7 @@ bool menuModelSensor(evt_t event) break; } else if (sensor->formula == TELEM_FORMULA_DIST) { - lcd_putsLeft(y, "GPS sensor"); + lcdDrawText(MENUS_MARGIN_LEFT, y, "GPS sensor"); putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->dist.gps ? MIXSRC_FIRST_TELEM+3*(sensor->dist.gps-1) : 0, attr); if (attr) { sensor->dist.gps = checkIncDec(event, sensor->dist.gps, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isGPSSensor); @@ -323,7 +327,7 @@ bool menuModelSensor(evt_t event) break; } else if (sensor->formula == TELEM_FORMULA_CONSUMPTION) { - lcd_putsLeft(y, "Amps sensor"); + lcdDrawText(MENUS_MARGIN_LEFT, y, "Amps sensor"); putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->consumption.source ? MIXSRC_FIRST_TELEM+3*(sensor->consumption.source-1) : 0, attr); if (attr) { sensor->consumption.source = checkIncDec(event, sensor->consumption.source, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isCurrentSensor); @@ -332,7 +336,7 @@ bool menuModelSensor(evt_t event) } } else { - lcd_putsLeft(y, "Ratio"); + lcdDrawText(MENUS_MARGIN_LEFT, y, "Ratio"); if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.ratio, 0, 30000); if (sensor->custom.ratio == 0) lcdDrawText(SENSOR_2ND_COLUMN, y, "-", attr); @@ -345,11 +349,12 @@ bool menuModelSensor(evt_t event) case SENSOR_FIELD_PARAM2: if (sensor->type == TELEM_TYPE_CALCULATED) { if (sensor->formula == TELEM_FORMULA_CELL) { - sensor->cell.index = selectMenuItem(SENSOR_2ND_COLUMN, y, "Cell index", "\007Lowest\0001\0 2\0 3\0 4\0 5\0 6\0 HighestDelta\0", sensor->cell.index, 0, 8, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, "Cell index"); + sensor->cell.index = selectMenuItem(SENSOR_2ND_COLUMN, y, "\007Lowest\0001\0 2\0 3\0 4\0 5\0 6\0 HighestDelta\0", sensor->cell.index, 0, 8, attr, event); break; } else if (sensor->formula == TELEM_FORMULA_DIST) { - lcd_putsLeft(y, "Alt sensor"); + lcdDrawText(MENUS_MARGIN_LEFT, y, "Alt sensor"); putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->dist.alt ? MIXSRC_FIRST_TELEM+3*(sensor->dist.alt-1) : 0, attr); if (attr) { sensor->dist.alt = checkIncDec(event, sensor->dist.alt, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isAltSensor); @@ -358,7 +363,7 @@ bool menuModelSensor(evt_t event) } } else { - lcd_putsLeft(y, NO_INDENT(STR_OFFSET)); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_OFFSET)); if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.offset, -30000, +30000); if (sensor->prec > 0) attr |= (sensor->prec == 2 ? PREC2 : PREC1); lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.offset, LEFT|attr); @@ -387,15 +392,18 @@ bool menuModelSensor(evt_t event) } case SENSOR_FIELD_AUTOOFFSET: - ON_OFF_MENU_ITEM(sensor->autoOffset, SENSOR_2ND_COLUMN, y, STR_AUTOOFFSET, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_AUTOOFFSET); + onoffMenuItem(sensor->autoOffset, SENSOR_2ND_COLUMN, y, attr, event); break; case SENSOR_FIELD_FILTER: - ON_OFF_MENU_ITEM(sensor->filter, SENSOR_2ND_COLUMN, y, STR_FILTER, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_FILTER); + onoffMenuItem(sensor->filter, SENSOR_2ND_COLUMN, y, attr, event); break; case SENSOR_FIELD_PERSISTENT: - ON_OFF_MENU_ITEM(sensor->persistent, SENSOR_2ND_COLUMN, y, NO_INDENT(STR_PERSISTENT), attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_PERSISTENT)); + onoffMenuItem(sensor->persistent, SENSOR_2ND_COLUMN, y, attr, event); break; } @@ -470,7 +478,7 @@ bool menuModelTelemetry(evt_t event) } TelemetryItem & telemetryItem = telemetryItems[index]; if (telemetryItem.isAvailable()) { - LcdFlags color = telemetryItem.isOld() ? RED : WHITE; + LcdFlags color = telemetryItem.isOld() ? ALARM_COLOR : TEXT_COLOR; putsTelemetryChannelValue(TELEM_COL2, y, index, getValue(MIXSRC_FIRST_TELEM+3*index), LEFT|color); } else { @@ -494,11 +502,12 @@ bool menuModelTelemetry(evt_t event) switch (k) { case ITEM_TELEMETRY_PROTOCOL_TYPE: - g_model.telemetryProtocol = selectMenuItem(TELEM_COL2, y, STR_TELEMETRY_TYPE, "\017FrSky S.PORT\0 FrSky D\0 FrSky D (cable)", g_model.telemetryProtocol, PROTOCOL_TELEMETRY_FIRST, PROTOCOL_FRSKY_D, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TELEMETRY_TYPE); + g_model.telemetryProtocol = selectMenuItem(TELEM_COL2, y, "\017FrSky S.PORT\0 FrSky D\0 FrSky D (cable)", g_model.telemetryProtocol, PROTOCOL_TELEMETRY_FIRST, PROTOCOL_FRSKY_D, attr, event); break; case ITEM_TELEMETRY_SENSORS_LABEL: - lcd_putsLeft(y, STR_TELEMETRY_SENSORS); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TELEMETRY_SENSORS); break; case ITEM_TELEMETRY_NEWSENSOR: @@ -514,13 +523,13 @@ bool menuModelTelemetry(evt_t event) break; case ITEM_TELEMETRY_RSSI_LABEL: - lcd_putsLeft(y, PSTR("RSSI")); + lcdDrawText(MENUS_MARGIN_LEFT, y, PSTR("RSSI")); break; case ITEM_TELEMETRY_RSSI_ALARM1: case ITEM_TELEMETRY_RSSI_ALARM2: { uint8_t alarm = k-ITEM_TELEMETRY_RSSI_ALARM1; - lcd_putsLeft(y, (alarm==0 ? STR_LOWALARM : STR_CRITICALALARM)); + lcdDrawText(MENUS_MARGIN_LEFT, y, (alarm==0 ? STR_LOWALARM : STR_CRITICALALARM)); lcdDrawNumber(TELEM_COL2, y, getRssiAlarmValue(alarm), LEFT|attr, 3); if (attr && s_editMode>0) { CHECK_INCDEC_MODELVAR(event, g_model.frsky.rssiAlarms[alarm].value, -30, 30); @@ -530,11 +539,11 @@ bool menuModelTelemetry(evt_t event) #if defined(VARIO) case ITEM_TELEMETRY_VARIO_LABEL: - lcd_putsLeft(y, STR_VARIO); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_VARIO); break; case ITEM_TELEMETRY_VARIO_SOURCE: - lcd_putsLeft(y, STR_SOURCE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_SOURCE); putsMixerSource(TELEM_COL2, y, g_model.frsky.varioSource ? MIXSRC_FIRST_TELEM+3*(g_model.frsky.varioSource-1) : 0, attr); if (attr) { g_model.frsky.varioSource = checkIncDec(event, g_model.frsky.varioSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isSensorAvailable); @@ -542,7 +551,7 @@ bool menuModelTelemetry(evt_t event) break; case ITEM_TELEMETRY_VARIO_RANGE: - lcd_putsLeft(y, STR_LIMIT); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_LIMIT); lcdDrawNumber(TELEM_COL2, y, -10+g_model.frsky.varioMin, (m_posHorz<=0 ? attr : 0)|LEFT); lcdDrawNumber(TELEM_COL2+70, y, -5+g_model.frsky.varioCenterMin, ((CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0)|PREC1); lcdDrawNumber(TELEM_COL2+115, y, 5+g_model.frsky.varioCenterMax, ((CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0)|PREC1); @@ -567,11 +576,11 @@ bool menuModelTelemetry(evt_t event) #endif case ITEM_TELEMETRY_TOP_BAR_LABEL: - lcd_putsLeft(y, "Main Telemetry Fields"); + lcdDrawText(MENUS_MARGIN_LEFT, y, "Main Telemetry Fields"); break; case ITEM_TELEMETRY_TOP_BAR_VOLTAGE: - lcd_putsLeft(y, STR_VOLTAGE); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_VOLTAGE); putsMixerSource(TELEM_COL2, y, g_model.frsky.voltsSource ? MIXSRC_FIRST_TELEM+3*(g_model.frsky.voltsSource-1) : 0, attr); if (attr) { g_model.frsky.voltsSource = checkIncDec(event, g_model.frsky.voltsSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isVoltsSensor); @@ -579,7 +588,7 @@ bool menuModelTelemetry(evt_t event) break; case ITEM_TELEMETRY_TOP_BAR_ALTITUDE: - lcd_putsLeft(y, INDENT "Altitude Source"); + lcdDrawText(MENUS_MARGIN_LEFT, y, INDENT "Altitude Source"); putsMixerSource(TELEM_COL2, y, g_model.frsky.altitudeSource ? MIXSRC_FIRST_TELEM+3*(g_model.frsky.altitudeSource-1) : 0, attr); if (attr) { g_model.frsky.altitudeSource = checkIncDec(event, g_model.frsky.altitudeSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isAltSensor); @@ -594,7 +603,7 @@ bool menuModelTelemetry(evt_t event) uint8_t screenIndex = TELEMETRY_CURRENT_EDIT_SCREEN(k); putsStrIdx(MENUS_MARGIN_LEFT, y, STR_SCREEN, screenIndex+1); TelemetryScreenType oldScreenType = TELEMETRY_SCREEN_TYPE(screenIndex); - TelemetryScreenType newScreenType = (TelemetryScreenType)selectMenuItem(TELEM_SCRTYPE_COL, y, PSTR(""), STR_VTELEMSCREENTYPE, oldScreenType, 0, TELEMETRY_SCREEN_TYPE_MAX, (m_posHorz==0 ? attr : 0), event); + TelemetryScreenType newScreenType = (TelemetryScreenType)selectMenuItem(TELEM_SCRTYPE_COL, y, STR_VTELEMSCREENTYPE, oldScreenType, 0, TELEMETRY_SCREEN_TYPE_MAX, (m_posHorz==0 ? attr : 0), event); if (newScreenType != oldScreenType) { g_model.frsky.screensType = (g_model.frsky.screensType & (~(0x03 << (2*screenIndex)))) | (newScreenType << (2*screenIndex)); memset(&g_model.frsky.screens[screenIndex], 0, sizeof(g_model.frsky.screens[screenIndex])); diff --git a/radio/src/gui/Horus/menus.h b/radio/src/gui/Horus/menus.h index 0eb9f8edf..083bcabd7 100644 --- a/radio/src/gui/Horus/menus.h +++ b/radio/src/gui/Horus/menus.h @@ -50,8 +50,6 @@ #define MENU_COLUMN2_X 280 -#define lcd_putsColumnLeft(x, y, str) lcdDrawText((x > MENU_COLUMN2_X+MENUS_MARGIN_LEFT+68) ? MENU_COLUMN2_X+MENUS_MARGIN_LEFT : MENUS_MARGIN_LEFT, y, str) - typedef int16_t vertpos_t; typedef evt_t & check_event_t; @@ -220,7 +218,7 @@ void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); #define SUBMENU(title, lines_count, scrollbar_X, ...) \ MENU_TAB(__VA_ARGS__); \ if (!check(event, 0, NULL, 0, mstate_tab, DIM(mstate_tab)-1, lines_count)) return false; \ - drawMenuTemplate(title, scrollbar_X); + drawSubmenuTemplate(title, scrollbar_X); #define SIMPLE_SUBMENU_NOTITLE(lines_count) \ if (!check_submenu_simple(event, lines_count)) return false @@ -231,12 +229,10 @@ void drawSlider(coord_t x, coord_t y, uint8_t value, uint8_t max, uint8_t attr); typedef int select_menu_value_t; -select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, evt_t event); -uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, const pm_char *label, LcdFlags attr, evt_t event); +select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char * values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, evt_t event); +uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, LcdFlags attr, evt_t event); int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, evt_t event); -#define ON_OFF_MENU_ITEM(value, x, y, label, attr, event) value = onoffMenuItem(value, x, y, label, attr, event) - #if defined(GVARS) #define GVAR_MENU_ITEM(x, y, v, min, max, lcdattr, editflags, event) gvarMenuItem(x, y, v, min, max, lcdattr, editflags, event) #else diff --git a/radio/src/gui/Horus/navigation.cpp b/radio/src/gui/Horus/navigation.cpp index 08b27b681..a8aca4edd 100644 --- a/radio/src/gui/Horus/navigation.cpp +++ b/radio/src/gui/Horus/navigation.cpp @@ -422,8 +422,12 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t } while (CURSOR_NOT_ALLOWED_IN_ROW(m_posVert)); s_editMode = 0; // if we go down, we must be in this mode uint8_t newmaxcol = MAXCOL(m_posVert); - if (m_posHorz < 0 || (m_posHorz==0 && maxcol==0) || m_posHorz > newmaxcol) + if (COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE) { + m_posHorz = -1; + } + else if (m_posHorz < 0 || m_posHorz > newmaxcol) { m_posHorz = POS_HORZ_INIT(m_posVert); + } break; } @@ -442,7 +446,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t do { DEC(m_posVert, MENU_FIRST_LINE_EDIT, rowcount-1); } while (CURSOR_NOT_ALLOWED_IN_ROW(m_posVert)); - if ((COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) + if (COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE) m_posHorz = -1; else m_posHorz = min((uint8_t)m_posHorz, MAXCOL(m_posVert)); @@ -456,18 +460,21 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t } while (CURSOR_NOT_ALLOWED_IN_ROW(m_posVert)); s_editMode = 0; // if we go up, we must be in this mode uint8_t newmaxcol = MAXCOL(m_posVert); - if (m_posHorz < 0 || (m_posHorz==0 && maxcol==0) || m_posHorz > newmaxcol) { - if ((COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) - m_posHorz = -1; - else - m_posHorz = min((uint8_t)m_posHorz, newmaxcol); + if ((COLATTR(m_posVert) & NAVIGATION_LINE_BY_LINE)) { + m_posHorz = -1; + } + else if (m_posHorz < 0) { + m_posHorz = POS_HORZ_INIT(m_posVert); + } + else if (m_posHorz > newmaxcol) { + m_posHorz = min((uint8_t)m_posHorz, newmaxcol); } - break; } } linesCount = rowcount; + int maxBodyLines = (menuTab ? NUM_BODY_LINES : NUM_BODY_LINES+1); if (m_posVert <= MENU_FIRST_LINE_EDIT) { s_pgOfs = 0; @@ -481,7 +488,7 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t } } else if (horTab) { - if (rowcount > NUM_BODY_LINES) { + if (rowcount > maxBodyLines) { while (1) { vertpos_t firstLine = 0; for (int numLines=0; firstLine=horTabMax || horTab[lastLine] != HIDDEN_ROW) { + for (int numLines=0; lastLine= horTabMax || horTab[lastLine] != HIDDEN_ROW) { numLines++; } } @@ -503,9 +510,9 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t s_pgOfs++; } else { - linesCount = s_pgOfs + NUM_BODY_LINES; + linesCount = s_pgOfs + maxBodyLines; for (int i=lastLine; ihorTabMax || horTab[i] != HIDDEN_ROW) { + if (i > horTabMax || horTab[i] != HIDDEN_ROW) { linesCount++; } } @@ -516,10 +523,10 @@ bool check(check_event_t event, uint8_t curr, const MenuFuncP * menuTab, uint8_t } } else { - if (m_posVert>=NUM_BODY_LINES+s_pgOfs) { - s_pgOfs = m_posVert-NUM_BODY_LINES+1; + if (m_posVert >= maxBodyLines + s_pgOfs) { + s_pgOfs = m_posVert-maxBodyLines+1; } - else if (m_posVert NUM_BODY_LINES) { + drawScrollbar(scrollbar_X, DEFAULT_SCROLLBAR_Y, DEFAULT_SCROLLBAR_H, s_pgOfs, linesCount, NUM_BODY_LINES); + } +} + void drawMenuTemplate(const char * name, uint16_t scrollbar_X) { // clear the screen @@ -207,25 +239,23 @@ void drawMenuTemplate(const char * name, uint16_t scrollbar_X) } } -select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char *label, const pm_char *values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, evt_t event) +select_menu_value_t selectMenuItem(coord_t x, coord_t y, const pm_char * values, select_menu_value_t value, select_menu_value_t min, select_menu_value_t max, LcdFlags attr, evt_t event) { - lcd_putsColumnLeft(x, y, label); if (attr) value = checkIncDec(event, value, min, max, (g_menuPos[0] == 0) ? EE_MODEL : EE_GENERAL); if (values) lcdDrawTextAtIndex(x, y, values, value-min, attr); return value; } -uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, const pm_char *label, LcdFlags attr, evt_t event ) +uint8_t onoffMenuItem(uint8_t value, coord_t x, coord_t y, LcdFlags attr, evt_t event ) { drawCheckBox(x, y, value, attr); - return selectMenuItem(x, y, label, NULL, value, 0, 1, attr, event); + return selectMenuItem(x, y, NULL, value, 0, 1, attr, event); } int8_t switchMenuItem(coord_t x, coord_t y, int8_t value, LcdFlags attr, evt_t event) { - lcd_putsColumnLeft(x, y, STR_SWITCH); if (attr) CHECK_INCDEC_MODELSWITCH(event, value, SWSRC_FIRST_IN_MIXES, SWSRC_LAST_IN_MIXES, isSwitchAvailableInMixes); - putsSwitches(x, y, value, attr); + putsSwitches(x, y, value, attr); return value; } diff --git a/radio/src/gui/Taranis/lcd.cpp b/radio/src/gui/Taranis/lcd.cpp index 4c7b9eb6a..438e78bfc 100644 --- a/radio/src/gui/Taranis/lcd.cpp +++ b/radio/src/gui/Taranis/lcd.cpp @@ -639,7 +639,7 @@ void putsStickName(coord_t x, coord_t y, uint8_t idx, LcdFlags att) void putsMixerSource(coord_t x, coord_t y, uint32_t idx, LcdFlags att) { - if (idx == 0) { + if (idx == MIXSRC_NONE) { lcdDrawTextAtIndex(x, y, STR_VSRCRAW, 0, att); // TODO macro } else if (idx <= MIXSRC_LAST_INPUT) { diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index ed69b86af..053ec2132 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -118,7 +118,7 @@ #define MAX_SCRIPTS 7 #define MAX_INPUTS 32 #define NUM_TRAINER 16 - #define NUM_POTS 3 + #define NUM_POTS 7 #define NUM_XPOTS 3 #define MAX_SENSORS 32 #elif defined(PCBFLAMENCO) @@ -1943,7 +1943,9 @@ enum MixSources { MIXSRC_FIRST_SLIDER, MIXSRC_SLIDER1 = MIXSRC_FIRST_SLIDER, LUA_EXPORT("ls", "Left slider") MIXSRC_SLIDER2, LUA_EXPORT("rs", "Right slider") - MIXSRC_LAST_POT = MIXSRC_SLIDER2, + MIXSRC_SLIDER3, LUA_EXPORT("ls2", "Left center slider") + MIXSRC_SLIDER4, LUA_EXPORT("rs2", "Right center slider") + MIXSRC_LAST_POT = MIXSRC_SLIDER4, #elif defined(PCBFLAMENCO) MIXSRC_POT1 = MIXSRC_FIRST_POT, LUA_EXPORT("sd", "Potentiometer D") MIXSRC_SLIDER1, LUA_EXPORT("ls", "Left slider") @@ -1971,7 +1973,7 @@ enum MixSources { MIXSRC_P2, MIXSRC_P3, MIXSRC_LAST_POT = MIXSRC_P3, - #endif +#endif #if defined(PCBSKY9X) MIXSRC_REa, diff --git a/radio/src/strhelpers.cpp b/radio/src/strhelpers.cpp index 74b7fdfe4..58924b413 100644 --- a/radio/src/strhelpers.cpp +++ b/radio/src/strhelpers.cpp @@ -97,7 +97,7 @@ int zchar2str(char *dest, const char *src, int size) #endif #if defined(CPUARM) -unsigned int effectiveLen(const char *str, unsigned int size) +unsigned int effectiveLen(const char * str, unsigned int size) { while (size > 0) { if (str[size-1] != ' ') @@ -107,7 +107,7 @@ unsigned int effectiveLen(const char *str, unsigned int size) return 0; } -bool zexist(const char *str, uint8_t size) +bool zexist(const char * str, uint8_t size) { for (int i=0; i 0) { if (str[size-1] != 0) diff --git a/radio/src/translations/en.h.txt b/radio/src/translations/en.h.txt index ef07841c0..b928e15c4 100644 --- a/radio/src/translations/en.h.txt +++ b/radio/src/translations/en.h.txt @@ -464,8 +464,11 @@ #define TR_STICKS_VSRCRAW TR("Rud\0""Ele\0""Thr\0""Ail\0", "\307Rud""\307Ele""\307Thr""\307Ail") #define TR_TRIMS_VSRCRAW TR("TrmR""TrmE""TrmT""TrmA", "\313Rud""\313Ele""\313Thr""\313Ail") - -#if defined(PCBFLAMENCO) + +#if defined(PCBHORUS) + #define TR_POTS_VSRCRAW "\310F1\0""\310F2\0""\310F3\0""\311S1\0""\311S2\0""\311LS\0""\311RS\0" + #define TR_SW_VSRCRAW "\312SA\0""\312SB\0""\312SC\0""\312SD\0""\312SE\0""\312SF\0""\312SG\0""\312SH\0" +#elif defined(PCBFLAMENCO) #define TR_POTS_VSRCRAW "SD\0 ""LS\0 ""RS\0 " #define TR_SW_VSRCRAW "SA\0 ""SB\0 ""SC\0 ""SE\0 ""SF\0 " #elif defined(PCBTARANIS) && defined(REV9E) diff --git a/radio/util/font2png.py b/radio/util/font2png.py index 6faf9f43a..ef24ac825 100755 --- a/radio/util/font2png.py +++ b/radio/util/font2png.py @@ -49,12 +49,12 @@ def createFontBitmap(filename, fontname, fontsize, fontbold, foreground, backgro width = getFontWidth(fontsize, metrics) top, bottom = getFontTopBottom(fontsize, metrics) - extraImage = QtGui.QImage("fonts/extra_%dpx.png" % (fontsize+1)) + extraImage = QtGui.QImage("fonts/extra_%dpx.png" % fontsize) if extraImage.isNull(): extraWidth = 0 else: extraWidth = extraImage.size().width() - image = QtGui.QImage(width + extraWidth, fontsize+1, QtGui.QImage.Format_RGB32) + image = QtGui.QImage(width + extraWidth, fontsize+2, QtGui.QImage.Format_RGB32) image.fill(background) painter = QtGui.QPainter() painter.begin(image) @@ -73,7 +73,7 @@ def createFontBitmap(filename, fontname, fontsize, fontbold, foreground, backgro if fontsize >= 24: painter.drawText(width-rect.left()+1, fontsize-2, c) # fontsize-bottom+1 -17 / 7 else: - painter.drawText(width-rect.left(), fontsize-2, c) # fontsize-bottom+1 -17 / 7 + painter.drawText(width-rect.left(), fontsize-1, c) # fontsize-bottom+1 -17 / 7 width += getCharWidth(fontsize, metrics, i) if extraWidth: painter.drawImage(QtCore.QPoint(width, 0), extraImage) From 7f4abd7c83ed0cc7ce46ca5e31a8db13aadc21b6 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 14 Nov 2015 17:50:52 +0100 Subject: [PATCH 64/74] [Horus] Windows compilation errors --- .../src/firmwares/opentx/simulator/opentxsimulator.cpp | 6 +++--- radio/src/targets/simu/simpgmspace.cpp | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp index 51f204ecd..0e1f262f1 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp @@ -306,9 +306,9 @@ namespace NAMESPACE { #include "radio/src/targets/Horus/keys_driver.cpp" #include "radio/src/targets/Horus/audio_driver.cpp" #include "radio/src/targets/Horus/telemetry_driver.cpp" -// #include "radio/src/targets/Horus/haptic_driver.cpp" +#include "radio/src/targets/Horus/haptic_driver.cpp" #include "radio/src/targets/Horus/pulses_driver.cpp" -// #include "radio/src/targets/Horus/rtc_driver.cpp" +#include "radio/src/targets/Taranis/rtc_driver.cpp" #include "radio/src/targets/Horus/trainer_driver.cpp" #elif defined(PCBFLAMENCO) #include "radio/src/targets/Flamenco/board_flamenco.cpp" @@ -318,7 +318,7 @@ namespace NAMESPACE { #include "radio/src/targets/Flamenco/eeprom_driver.cpp" // #include "radio/src/targets/Flamenco/haptic_driver.cpp" #include "radio/src/targets/Flamenco/pulses_driver.cpp" -// #include "radio/src/targets/Flamenco/rtc_driver.cpp" +#include "radio/src/targets/Flamenco/rtc_driver.cpp" #include "radio/src/targets/Flamenco/trainer_driver.cpp" #elif defined(PCBTARANIS) #include "radio/src/targets/Taranis/board_taranis.cpp" diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 276da05ac..f3f3499b4 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -45,6 +45,7 @@ #if defined WIN32 || !defined __GNUC__ #include + #define mkdir(s, f) _mkdir(s) #endif #if defined(SIMU_DISKIO) From f0753fe51cde4b34d5195a0a42689779cb14899a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 14 Nov 2015 17:55:08 +0100 Subject: [PATCH 65/74] Next compilation fixes --- radio/src/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/Makefile b/radio/src/Makefile index 84427abe4..79b46b259 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -1287,7 +1287,7 @@ ifeq ($(PCB), HORUS) $(LUADIR)/lobject.c $(LUADIR)/lopcodes.c $(LUADIR)/lparser.c $(LUADIR)/lstate.c $(LUADIR)/lstring.c $(LUADIR)/ltable.c $(LUADIR)/lrotable.c $(LUADIR)/ltm.c $(LUADIR)/lundump.c $(LUADIR)/lvm.c $(LUADIR)/lzio.c \ $(LUADIR)/lbaselib.c $(LUADIR)/linit.c $(LUADIR)/lmathlib.c $(LUADIR)/lbitlib.c $(LUADIR)/loadlib.c $(LUADIR)/lauxlib.c $(LUADIR)/ltablib.c $(LUADIR)/lcorolib.c $(LUADIR)/liolib.c SRC += $(LUASRC) - LUADEP = lua_exports_flamenco.inc + LUADEP = lua_exports_horus.inc endif EXTRABOARDSRC += $(FATFSDIR)/ff.c $(FATFSDIR)/fattime.c $(FATFSDIR)/option/ccsbcs.c targets/Horus/diskio.cpp @@ -1931,7 +1931,7 @@ lua_exports_taranis.inc: myeeprom.h ../util/luaexport.py lua_exports_horus.inc: myeeprom.h ../util/luaexport.py @echo "Generating a list of Lua exported constants in lua_exports_horus.txt" - @$(PARSER) -DPCBFLAMENCO -DCPUARM -DCPUSTM32 -DLUA -DEXPORT myeeprom.h | grep LEXP > lua_exports_horus.txt + @$(PARSER) -DPCBHORUS -DCPUARM -DCPUSTM32 -DLUA -DEXPORT myeeprom.h | grep LEXP > lua_exports_horus.txt @echo "Parsing and generating C Lua exports in lua_exports_horus.inc" @$(PYTHON) ../util/luaexport.py $(VERSION) lua_exports_horus.txt lua_exports_horus.inc lua_fields_horus.txt @rm lua_exports_horus.txt From 7f991ea7f93ec577108f06728874b16137fb7224 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 14 Nov 2015 18:02:54 +0100 Subject: [PATCH 66/74] Next compilation fixes --- companion/src/firmwares/opentx/simulator/opentxsimulator.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp index 0e1f262f1..68b002b1d 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp @@ -133,6 +133,7 @@ namespace NAMESPACE { #include "radio/src/trainer_input.cpp" #if defined(PCBHORUS) +#include "radio/src/bmp.cpp" #include "radio/src/gui/Horus/bitmaps.cpp" #include "radio/src/gui/Horus/fonts.cpp" #include "radio/src/gui/Horus/widgets.cpp" @@ -310,6 +311,7 @@ namespace NAMESPACE { #include "radio/src/targets/Horus/pulses_driver.cpp" #include "radio/src/targets/Taranis/rtc_driver.cpp" #include "radio/src/targets/Horus/trainer_driver.cpp" +#include "radio/src/targets/Horus/serial2_driver.cpp" #elif defined(PCBFLAMENCO) #include "radio/src/targets/Flamenco/board_flamenco.cpp" #include "radio/src/targets/Flamenco/keys_driver.cpp" From edf1dbc0b7898219a5dc1b95e12dd9aeb8b648ab Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 14 Nov 2015 18:24:51 +0100 Subject: [PATCH 67/74] Windows compilation fix --- radio/src/myeeprom.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 053ec2132..7910f5ece 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -1943,8 +1943,8 @@ enum MixSources { MIXSRC_FIRST_SLIDER, MIXSRC_SLIDER1 = MIXSRC_FIRST_SLIDER, LUA_EXPORT("ls", "Left slider") MIXSRC_SLIDER2, LUA_EXPORT("rs", "Right slider") - MIXSRC_SLIDER3, LUA_EXPORT("ls2", "Left center slider") - MIXSRC_SLIDER4, LUA_EXPORT("rs2", "Right center slider") + MIXSRC_SLIDER3, LUA_EXPORT("lcs", "Left center slider") + MIXSRC_SLIDER4, LUA_EXPORT("rcs", "Right center slider") MIXSRC_LAST_POT = MIXSRC_SLIDER4, #elif defined(PCBFLAMENCO) MIXSRC_POT1 = MIXSRC_FIRST_POT, LUA_EXPORT("sd", "Potentiometer D") From 2f54e85421eb77776008b64c1696c78aa1f58699 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 14 Nov 2015 19:17:34 +0100 Subject: [PATCH 68/74] VERSION_FAMILY dynamic string introduced --- companion/src/CMakeLists.txt | 1 + companion/src/companion.cpp | 2 +- companion/src/companion.desktop.in | 2 +- .../src/firmwares/opentx/stamp-opentx.h.in | 2 +- companion/src/mainwindow.cpp | 8 ++-- companion/src/simulator.desktop.in | 2 +- companion/src/version.h.in | 2 +- .../targets/windows/companion-msys.nsi.in | 2 +- companion/targets/windows/companion-vs.nsi.in | 42 +++++++++---------- companion/targets/windows/simulator-vs.nsi.in | 2 +- 10 files changed, 33 insertions(+), 32 deletions(-) diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index 77105bb42..d090613a7 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -5,6 +5,7 @@ SET(VERSION_MINOR "2") SET(VERSION_REVISION "0") SET(VERSION_SUFFIX $ENV{OPENTX_VERSION_SUFFIX}) +SET(VERSION_FAMILY ${VERSION_MAJOR}.${VERSION_MINOR}) SET(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}${VERSION_SUFFIX}) MESSAGE(STATUS "OpenTX Companion ${VERSION}") diff --git a/companion/src/companion.cpp b/companion/src/companion.cpp index 13dfaa5ef..a60b19fea 100644 --- a/companion/src/companion.cpp +++ b/companion/src/companion.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QStringList strl = QApplication::arguments(); if (strl.contains("--version")) { - printf("%s\n", C9X_VERSION); + printf("%s\n", VERSION); fflush(stdout); exit(0); } diff --git a/companion/src/companion.desktop.in b/companion/src/companion.desktop.in index 007244755..58614f40f 100644 --- a/companion/src/companion.desktop.in +++ b/companion/src/companion.desktop.in @@ -1,7 +1,7 @@ [Desktop Entry] Type=Application Encoding=UTF-8 -Name=Companion @C9X_VERSION@ +Name=Companion @VERSION@ GenericName=Transmitter Companion App Comment=The Ultimate Transmitter Companion Icon=@COMPANION_NAME@ diff --git a/companion/src/firmwares/opentx/stamp-opentx.h.in b/companion/src/firmwares/opentx/stamp-opentx.h.in index 64cb64097..31e946aa4 100644 --- a/companion/src/firmwares/opentx/stamp-opentx.h.in +++ b/companion/src/firmwares/opentx/stamp-opentx.h.in @@ -1,4 +1,4 @@ // Automatically generated file (CMake) - do not edit #define DATE_STR "@DATE@" #define TIME_STR "@TIME@" -#define VERS_STR "@C9X_VERSION@" +#define VERS_STR "@VERSION@" diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp index e30143ba5..619e61dab 100644 --- a/companion/src/mainwindow.cpp +++ b/companion/src/mainwindow.cpp @@ -278,7 +278,7 @@ void MainWindow::closeUpdatesWaitDialog() void MainWindow::checkForCompanionUpdateFinished(QNetworkReply * reply) { QByteArray qba = reply->readAll(); - int i = qba.indexOf("C9X_VERSION"); + int i = qba.indexOf("VERSION"); if (i>0) { QString version = qba.mid(i+14, qba.indexOf("\"", i+14)-i-14); @@ -288,7 +288,7 @@ void MainWindow::checkForCompanionUpdateFinished(QNetworkReply * reply) int vnum = version2index(version); - QString c9xversion = QString(C9X_VERSION); + QString c9xversion = QString(VERSION); int c9xver = version2index(c9xversion); if (c9xver < vnum) { @@ -899,7 +899,7 @@ void MainWindow::about() aboutStr.append("

"); aboutStr.append(tr("If you've found this program useful, please support by
donating").arg(DONATE_STR)); aboutStr.append("

"); - aboutStr.append(QString("Version %1, %2").arg(C9X_VERSION).arg(__DATE__)); + aboutStr.append(QString("Version %1, %2").arg(VERSION).arg(__DATE__)); aboutStr.append("

"); aboutStr.append(tr("Copyright OpenTX Team") + "
© 2011-2015
"); QMessageBox msgBox(this); @@ -930,7 +930,7 @@ void MainWindow::updateMenus() updateLanguageActions(); updateIconSizeActions(); updateIconThemeActions(); - setWindowTitle(tr("OpenTX Companion %1 - Radio: %2 - Profile: %3").arg(C9X_VERSION).arg(GetCurrentFirmware()->getName()).arg(g.profile[g.id()].name())); + setWindowTitle(tr("OpenTX Companion %1 - Radio: %2 - Profile: %3").arg(VERSION).arg(GetCurrentFirmware()->getName()).arg(g.profile[g.id()].name())); } MdiChild *MainWindow::createMdiChild() diff --git a/companion/src/simulator.desktop.in b/companion/src/simulator.desktop.in index d87e3c404..c5715ac24 100644 --- a/companion/src/simulator.desktop.in +++ b/companion/src/simulator.desktop.in @@ -1,7 +1,7 @@ [Desktop Entry] Type=Application Encoding=UTF-8 -Name=Simulator @C9X_VERSION@ +Name=Simulator @VERSION@ GenericName=Transmitter Simulator App Comment=The Ultimate Transmitter Simulator Icon=@COMPANION_NAME@ diff --git a/companion/src/version.h.in b/companion/src/version.h.in index b7bbfbd2e..145b881d4 100644 --- a/companion/src/version.h.in +++ b/companion/src/version.h.in @@ -1,2 +1,2 @@ -#define C9X_VERSION "@C9X_VERSION@" +#define VERSION "@VERSION@" #define SIMULATOR_LIB_SEARCH_PATH "@SIMULATOR_LIB_PATH@" diff --git a/companion/targets/windows/companion-msys.nsi.in b/companion/targets/windows/companion-msys.nsi.in index 6d0975dc7..1d1a31139 100644 --- a/companion/targets/windows/companion-msys.nsi.in +++ b/companion/targets/windows/companion-msys.nsi.in @@ -13,7 +13,7 @@ ;Name and file Name "OpenTX companion" - OutFile "companion-windows-v@C9X_VERSION@.exe" + OutFile "companion-windows-v@VERSION@.exe" ;Default installation folder InstallDir "$PROGRAMFILES\OpenTX companion" diff --git a/companion/targets/windows/companion-vs.nsi.in b/companion/targets/windows/companion-vs.nsi.in index 698a3e617..f6150ae91 100644 --- a/companion/targets/windows/companion-vs.nsi.in +++ b/companion/targets/windows/companion-vs.nsi.in @@ -12,14 +12,14 @@ ;General ;Name and file - Name "OpenTX Companion 2.1" - OutFile "companion-windows-@C9X_VERSION@.exe" + Name "OpenTX Companion @VERSION_FAMILY@" + OutFile "companion-windows-@VERSION@.exe" ;Default installation folder - InstallDir "$PROGRAMFILES\OpenTX\Companion 2.1" + InstallDir "$PROGRAMFILES\OpenTX\Companion @VERSION_FAMILY@" ;Get installation folder from registry if available - InstallDirRegKey HKCU "Software\OpenTX\Companion 2.1" "" + InstallDirRegKey HKCU "Software\OpenTX\Companion @VERSION_FAMILY@" "" ;Compressor options SetCompressor /FINAL /SOLID lzma @@ -66,7 +66,7 @@ ;Start Menu Folder Page Configuration !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" - !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\OpenTX\Companion 2.1" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\OpenTX\Companion @VERSION_FAMILY@" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder @@ -77,7 +77,7 @@ !define MUI_FINISHPAGE_NOAUTOCLOSE !define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_RUN_CHECKED - !define MUI_FINISHPAGE_RUN_TEXT "Launch OpenTX Companion 2.1" + !define MUI_FINISHPAGE_RUN_TEXT "Launch OpenTX Companion @VERSION_FAMILY@" !define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink" # !define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED # !define MUI_FINISHPAGE_SHOWREADME $INSTDIR\readme.txt @@ -134,7 +134,7 @@ Section "OpenTX Companion" SecDummy SetOutPath "$INSTDIR" ;Store installation folder - WriteRegStr HKCU "Software\OpenTX\Companion 2.1" "" $INSTDIR + WriteRegStr HKCU "Software\OpenTX\Companion @VERSION_FAMILY@" "" $INSTDIR ;Associate with extentions ,bin and .hex ${registerExtension} "$INSTDIR\companion.exe" ".bin" "BIN_File" @@ -147,20 +147,20 @@ Section "OpenTX Companion" SecDummy WriteUninstaller "$INSTDIR\Uninstall.exe" ;Registry information for add/remove programs - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion 2.1" "DisplayName" "OpenTX Companion 2.1" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion 2.1" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\"" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion 2.1" "DisplayIcon" "$\"$INSTDIR\companion.exe$\"" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion 2.1" "Publisher" "OpenTX" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion 2.1" "URLInfoAbout" "http://www.open-tx.org" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion 2.1" "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion @VERSION_FAMILY@" "DisplayName" "OpenTX Companion @VERSION_FAMILY@" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion @VERSION_FAMILY@" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion @VERSION_FAMILY@" "DisplayIcon" "$\"$INSTDIR\companion.exe$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion @VERSION_FAMILY@" "Publisher" "OpenTX" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion @VERSION_FAMILY@" "URLInfoAbout" "http://www.open-tx.org" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion @VERSION_FAMILY@" "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S" !insertmacro MUI_STARTMENU_WRITE_BEGIN Application ;Create shortcuts CreateDirectory "$SMPROGRAMS\$StartMenuFolder" - CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Companion 2.1.lnk" "$INSTDIR\companion.exe" - CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Firmware Simulator 2.1.lnk" "$INSTDIR\simulator.exe" - CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall Companion 2.1.lnk" "$INSTDIR\Uninstall.exe" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Companion @VERSION_FAMILY@.lnk" "$INSTDIR\companion.exe" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Firmware Simulator @VERSION_FAMILY@.lnk" "$INSTDIR\simulator.exe" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall Companion @VERSION_FAMILY@.lnk" "$INSTDIR\Uninstall.exe" !insertmacro MUI_STARTMENU_WRITE_END @@ -220,13 +220,13 @@ Section "Uninstall" !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder - Delete "$SMPROGRAMS\$StartMenuFolder\Companion 2.1.lnk" - Delete "$SMPROGRAMS\$StartMenuFolder\Firmware Simulator 2.1.lnk" - Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall Companion 2.1.lnk" + Delete "$SMPROGRAMS\$StartMenuFolder\Companion @VERSION_FAMILY@.lnk" + Delete "$SMPROGRAMS\$StartMenuFolder\Firmware Simulator @VERSION_FAMILY@.lnk" + Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall Companion @VERSION_FAMILY@.lnk" RMDir "$SMPROGRAMS\$StartMenuFolder" - DeleteRegKey /ifempty HKCU "Software\OpenTX Companion 2.1" - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion 2.1" + DeleteRegKey /ifempty HKCU "Software\OpenTX Companion @VERSION_FAMILY@" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenTX Companion @VERSION_FAMILY@" SectionEnd diff --git a/companion/targets/windows/simulator-vs.nsi.in b/companion/targets/windows/simulator-vs.nsi.in index 7111ecd83..3b603b283 100644 --- a/companion/targets/windows/simulator-vs.nsi.in +++ b/companion/targets/windows/simulator-vs.nsi.in @@ -12,7 +12,7 @@ ;Name and file Name "OpenTX Simulator" - OutFile "simulator-windows-@C9X_VERSION@.exe" + OutFile "simulator-windows-@VERSION@.exe" ;Default installation folder InstallDir "$PROGRAMFILES\OpenTX" From 07ca8319ade40b2ce846650b4bc4a4248a08d94e Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 14 Nov 2015 21:15:28 +0100 Subject: [PATCH 69/74] [Horus] Simulator buttons modelized --- companion/src/companion.qrc | 8 ++++++ .../src/simulation/simulatordialog-horus.ui | 2 +- .../src/simulation/simulatordialoghorus.cpp | 27 ++++++++++--------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index ba6baa82e..ced3841ec 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -12,6 +12,14 @@ images/simulator/Horus/bottom.png images/simulator/Horus/led.png images/simulator/Horus/border-right.png + images/simulator/Horus/left_btn1.png + images/simulator/Horus/left_btn2.png + images/simulator/Horus/left_scrnsht.png + images/simulator/Horus/right_btnU.png + images/simulator/Horus/right_btnL.png + images/simulator/Horus/right_btnD.png + images/simulator/Horus/right_btnR.png + images/simulator/Horus/right_ent.png images/simulator/9X/9xdb.png images/simulator/9X/9xdl.png images/simulator/9X/9xdr.png diff --git a/companion/src/simulation/simulatordialog-horus.ui b/companion/src/simulation/simulatordialog-horus.ui index dad0695bf..09db52e34 100644 --- a/companion/src/simulation/simulatordialog-horus.ui +++ b/companion/src/simulation/simulatordialog-horus.ui @@ -178,7 +178,7 @@ 480 - 275 + 272 diff --git a/companion/src/simulation/simulatordialoghorus.cpp b/companion/src/simulation/simulatordialoghorus.cpp index cee428f69..c6b44c3ee 100644 --- a/companion/src/simulation/simulatordialoghorus.cpp +++ b/companion/src/simulation/simulatordialoghorus.cpp @@ -16,18 +16,21 @@ SimulatorDialogHorus::SimulatorDialogHorus(QWidget * parent, SimulatorInterface initUi(ui); - polygon.setPoints(6, 68, 83, 28, 45, 51, 32, 83, 32, 105, 45, 68, 83); - ui->rightbuttons->addArea(polygon, Qt::Key_Up, NULL); - polygon.setPoints(6, 74, 90, 114, 51, 127, 80, 127, 106, 114, 130, 74, 90); - ui->rightbuttons->addArea(polygon, Qt::Key_Right, NULL); - polygon.setPoints(6, 68, 98, 28, 137, 51, 151, 83, 151, 105, 137, 68, 98); - ui->rightbuttons->addArea(polygon, Qt::Key_Down, NULL); - polygon.setPoints(6, 80, 90, 20, 51, 7, 80, 7, 106, 20, 130, 80, 90); - ui->rightbuttons->addArea(polygon, Qt::Key_Left, NULL); - ui->rightbuttons->addArea(5, 148, 39, 182, Qt::Key_Print, NULL); - - ui->leftbuttons->addArea(25, 60, 71, 81, Qt::Key_PageUp, NULL); - ui->leftbuttons->addArea(25, 117, 71, 139, Qt::Key_Escape, NULL); + polygon.setPoints(23, 72,125, 85,126, 92,129, 98,133, 100,136, 100,141, 99,145, 96,148, 92,149, 87,147, 82,146, 76,145, 69,146, 63,147, 58,148, 54,148, 52,147, 49,144, 48,139, 47,135, 53,130, 63,127, 72,125); + ui->rightbuttons->addArea(polygon, Qt::Key_Up, "Horus/right_btnU.png"); + polygon.setPoints(25, 125,163, 129,164, 132,167, 135,171, 136,177, 137,184, 138,189, 138,195, 137,202, 135,207, 134,211, 131,214, 130,216, 125,217, 121,216, 118,214, 115,211, 115,204, 118,195, 118,187, 118,181, 116,176, 116,171, 118,165, 125,163); + ui->rightbuttons->addArea(polygon, Qt::Key_Right, "Horus/right_btnR.png"); + polygon.setPoints(20, 64,234, 72,235, 84,234, 91,232, 97,234, 99,237, 101,242, 100,248, 96,250, 92,252, 83,254, 72,255, 64,254, 57,253, 50,249, 48,245, 47,240, 50,235, 55,233, 64,234); + ui->rightbuttons->addArea(polygon, Qt::Key_Down, "Horus/right_btnD.png"); + polygon.setPoints(22, 24,163, 29,166, 30,168, 31,171, 31,175, 30,179, 29,187, 29,192, 30,198, 32,204, 32,209, 30,214, 27,216, 22,217, 17,216, 13,208, 10,197, 9,188, 9,178, 12,169, 15,165, 24,162); + ui->rightbuttons->addArea(polygon, Qt::Key_Left, "Horus/right_btnL.png"); + polygon.setPoints(21, 72,148, 83,149, 97,155, 106,163, 112,174, 114,187, 114,198, 110,210, 101,221, 89,228, 78,230, 66,230, 53,226, 43,218, 36,208, 32,196, 32,181, 37,168, 47,157, 57,151, 73,148); + ui->rightbuttons->addArea(polygon, Qt::Key_Enter, "Horus/right_ent.png"); + polygon.setPoints(26, 25,164, 31,165, 33,167, 35,169, 35,172, 35,174, 34,179, 34,183, 34,190, 34,195, 36,200, 37,204, 38,208, 37,212, 34,216, 29,218, 25,217, 21,215, 18,210, 16,203, 14,194, 14,184, 15,175, 17,169, 21,165, 25,164); + ui->leftbuttons->addArea(polygon, Qt::Key_PageUp, "Horus/left_btn1.png"); + polygon.setPoints(27, 60,227, 66,228, 71,229, 75,230, 81,229, 86,228, 89,227, 94,227, 97,229, 100,232, 100, 234, 100,238, 100,241, 99,243, 94,246, 88,248, 81,249, 73,249, 63,248, 56,246, 50,243, 48,240, 47,235, 48,232, 51,228, 56,227, 60,227); + ui->leftbuttons->addArea(polygon, Qt::Key_Escape, "Horus/left_btn2.png"); + ui->leftbuttons->addArea(9, 259, 34, 282, Qt::Key_Print, "Horus/left_scrnsht.png"); // install simulator TRACE hook simulator->installTraceHook(traceCb); From 11cff7a63b3fa972df2883aca03bcc4c282a10a5 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 15 Nov 2015 00:28:22 +0100 Subject: [PATCH 70/74] [Horus] Mouse middle button used for [Enter] --- companion/src/simulation/simulatordialoghorus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion/src/simulation/simulatordialoghorus.cpp b/companion/src/simulation/simulatordialoghorus.cpp index c6b44c3ee..c18b6755a 100644 --- a/companion/src/simulation/simulatordialoghorus.cpp +++ b/companion/src/simulation/simulatordialoghorus.cpp @@ -120,7 +120,7 @@ void SimulatorDialogHorus::getValues() { buttonPressed == Qt::Key_PageUp, buttonPressed == Qt::Key_Escape, - buttonPressed == Qt::Key_Enter, + buttonPressed == Qt::Key_Enter || middleButtonPressed, buttonPressed == Qt::Key_Up, buttonPressed == Qt::Key_Down, buttonPressed == Qt::Key_Right, From e71b53964b34ba0f0b43d7a3d50fcf2f55fd35f9 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 15 Nov 2015 11:56:17 +0100 Subject: [PATCH 71/74] [Horus] For the moment no better solution for Windows :( --- radio/src/Makefile | 36 +++++++++++------------ radio/src/fonts/Horus/font_dblsize.png | Bin 0 -> 27740 bytes radio/src/fonts/Horus/font_dblsize.specs | 1 + radio/src/fonts/Horus/font_midsize.png | Bin 0 -> 20959 bytes radio/src/fonts/Horus/font_midsize.specs | 1 + radio/src/fonts/Horus/font_smlsize.png | Bin 0 -> 11541 bytes radio/src/fonts/Horus/font_smlsize.specs | 1 + radio/src/fonts/Horus/font_stdsize.png | Bin 0 -> 17377 bytes radio/src/fonts/Horus/font_stdsize.specs | 1 + radio/src/fonts/Horus/font_tinsize.png | Bin 0 -> 7451 bytes radio/src/fonts/Horus/font_tinsize.specs | 1 + radio/src/fonts/Horus/font_xxlsize.png | Bin 0 -> 42906 bytes radio/src/fonts/Horus/font_xxlsize.specs | 1 + radio/src/gui/Horus/fonts.cpp | 24 +++++++-------- 14 files changed, 35 insertions(+), 31 deletions(-) create mode 100644 radio/src/fonts/Horus/font_dblsize.png create mode 100644 radio/src/fonts/Horus/font_dblsize.specs create mode 100644 radio/src/fonts/Horus/font_midsize.png create mode 100644 radio/src/fonts/Horus/font_midsize.specs create mode 100644 radio/src/fonts/Horus/font_smlsize.png create mode 100644 radio/src/fonts/Horus/font_smlsize.specs create mode 100644 radio/src/fonts/Horus/font_stdsize.png create mode 100644 radio/src/fonts/Horus/font_stdsize.specs create mode 100644 radio/src/fonts/Horus/font_tinsize.png create mode 100644 radio/src/fonts/Horus/font_tinsize.specs create mode 100644 radio/src/fonts/Horus/font_xxlsize.png create mode 100644 radio/src/fonts/Horus/font_xxlsize.specs diff --git a/radio/src/Makefile b/radio/src/Makefile index 79b46b259..289049eb9 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -1080,7 +1080,7 @@ ifeq ($(PCB), FLAMENCO) OPT = 2 CPPDEFS += -DHSE_VALUE=12000000 -DCOLORLCD -DSTM32F40_41xxx BITMAPS += $(patsubst %.png,%.lbm,$(wildcard bitmaps/Flamenco/*.png)) - FONTS = fonts/font_stdsize.lbm fonts/font_tinsize.lbm fonts/font_smlsize.lbm fonts/font_midsize.lbm fonts/font_dblsize.lbm fonts/font_xxlsize.lbm + FONTS = fonts/Flamenco/font_stdsize.lbm fonts/Flamenco/font_tinsize.lbm fonts/Flamenco/font_smlsize.lbm fonts/Flamenco/font_midsize.lbm fonts/Flamenco/font_dblsize.lbm fonts/Flamenco/font_xxlsize.lbm SDCARD = YES THR_TRACE = YES EXT = FRSKY_SPORT @@ -1202,7 +1202,7 @@ ifeq ($(PCB), HORUS) OPT = 2 CPPDEFS += -DHSE_VALUE=12000000 -DCOLORLCD -DSTM32F429_439xx BITMAPS += $(patsubst %.png,%.lbm,$(wildcard bitmaps/Horus/*.png)) - FONTS = fonts/font_stdsize.lbm fonts/font_tinsize.lbm fonts/font_smlsize.lbm fonts/font_midsize.lbm fonts/font_dblsize.lbm fonts/font_xxlsize.lbm + FONTS = fonts/Horus/font_stdsize.lbm fonts/Horus/font_tinsize.lbm fonts/Horus/font_smlsize.lbm fonts/Horus/font_midsize.lbm fonts/Horus/font_dblsize.lbm fonts/Horus/font_xxlsize.lbm SDCARD = YES THR_TRACE = YES EXT = FRSKY_SPORT @@ -1844,6 +1844,7 @@ translations/se.h: translations/se.h.txt $(TRANSLATE) translations/se.h.txt translations/se.h se ifeq ($(PCB), $(filter $(PCB), HORUS)) + FONTDIR = Horus FONTNAME = DejaVu\ Sans TINSIZE = 9 SMLSIZE = 13 @@ -1854,6 +1855,7 @@ ifeq ($(PCB), $(filter $(PCB), HORUS)) XXLSIZE = 48 MASKFORMAT = 8bits else + FONTDIR = Flamenco FONTNAME = Roboto TINSIZE = 5 SMLSIZE = 7 @@ -1868,26 +1870,26 @@ endif .PHONY: fonts fonts: $(FONTS) -fonts/font_%size.lbm: fonts/font_%size.png +fonts/$(FONTDIR)/font_%size.lbm: fonts/$(FONTDIR)/font_%size.png $(IMG2LBM) $^ $@ $(LCDSIZE) $(MASKFORMAT) -fonts/font_tinsize.png: - $(FONT2PNG) $(FONTNAME) $(TINSIZE) False fonts/font_tinsize +fonts/$(FONTDIR)/font_tinsize.png: + $(FONT2PNG) $(FONTNAME) $(TINSIZE) False fonts/$(FONTDIR)/font_tinsize -fonts/font_smlsize.png: - $(FONT2PNG) $(FONTNAME) $(SMLSIZE) False fonts/font_smlsize +fonts/$(FONTDIR)/font_smlsize.png: + $(FONT2PNG) $(FONTNAME) $(SMLSIZE) False fonts/$(FONTDIR)/font_smlsize -fonts/font_stdsize.png: - $(FONT2PNG) $(FONTNAME) $(STDSIZE) False fonts/font_stdsize +fonts/$(FONTDIR)/font_stdsize.png: + $(FONT2PNG) $(FONTNAME) $(STDSIZE) False fonts/$(FONTDIR)/font_stdsize -fonts/font_midsize.png: - $(FONT2PNG) $(FONTNAME) $(MIDSIZE) False fonts/font_midsize +fonts/$(FONTDIR)/font_midsize.png: + $(FONT2PNG) $(FONTNAME) $(MIDSIZE) False fonts/$(FONTDIR)/font_midsize -fonts/font_dblsize.png: - $(FONT2PNG) $(FONTNAME) $(DBLSIZE) $(DBLBOLD) fonts/font_dblsize +fonts/$(FONTDIR)/font_dblsize.png: + $(FONT2PNG) $(FONTNAME) $(DBLSIZE) $(DBLBOLD) fonts/$(FONTDIR)/font_dblsize -fonts/font_xxlsize.png: - $(FONT2PNG) $(FONTNAME) $(XXLSIZE) False fonts/font_xxlsize +fonts/$(FONTDIR)/font_xxlsize.png: + $(FONT2PNG) $(FONTNAME) $(XXLSIZE) False fonts/$(FONTDIR)/font_xxlsize fonts/%.lbm: fonts/%.png $(IMG2LBM) $^ $@ $(LCDSIZE) @@ -2096,12 +2098,8 @@ clean_list : $(REMOVE) allsrc.cpp $(REMOVE) allsimusrc.cpp $(REMOVE) stamp-opentx.h - $(REMOVE) *.lbm $(REMOVE) bitmaps/*.lbm $(REMOVE) bitmaps/*/*.lbm - $(REMOVE) bitmaps/*/*/*.lbm - $(REMOVE) fonts/*.lbm - $(REMOVE) fonts/font_* $(REMOVE) fonts/*/*.lbm $(REMOVE) lua_exports* lua_fields.txt $(MAKE) -C targets/Taranis/bootloader clean diff --git a/radio/src/fonts/Horus/font_dblsize.png b/radio/src/fonts/Horus/font_dblsize.png new file mode 100644 index 0000000000000000000000000000000000000000..d617ab223b5a6e479dc575710c4d4f1f8e5b091b GIT binary patch literal 27740 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�)WCH&mEG-sx#;TAp3$?;2n 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 literal 0 HcmV?d00001 diff --git a/radio/src/fonts/Horus/font_dblsize.specs b/radio/src/fonts/Horus/font_dblsize.specs new file mode 100644 index 000000000..8129cf4ed --- /dev/null +++ b/radio/src/fonts/Horus/font_dblsize.specs @@ -0,0 +1 @@ +{ 0,4,14,28,54,76,110,139,146,160,173,193,217,228,242,251,266,289,310,331,352,375,397,420,441,464,487,497,508,532,556,580,598,613,641,665,689,715,736,757,783,808,818,832,859,880,910,935,963,987,1015,1040,1062,1087,1112,1140,1178,1206,1234,1258,1272,1287,1300,1324,1343,1356,1378,1401,1420,1443,1466,1484,1507,1529,1539,1553,1576,1586,1618,1640,1663,1686,1709,1726,1746,1764,1786,1810,1841,1865,1889,1909,1933,1936,1960,1988,2015,2033,2051,2074,2097,2120,2143,2171 } \ No newline at end of file diff --git a/radio/src/fonts/Horus/font_midsize.png b/radio/src/fonts/Horus/font_midsize.png new file mode 100644 index 0000000000000000000000000000000000000000..f1bc88d935d21d241a0e5ad3ecc316ae3e7a7f25 GIT binary patch literal 20959 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 literal 0 HcmV?d00001 diff --git a/radio/src/fonts/Horus/font_midsize.specs b/radio/src/fonts/Horus/font_midsize.specs new file mode 100644 index 000000000..7244eaedd --- /dev/null +++ b/radio/src/fonts/Horus/font_midsize.specs @@ -0,0 +1 @@ +{ 0,4,10,20,41,56,80,100,106,115,125,140,159,167,177,184,196,212,227,242,258,274,290,306,322,338,354,361,369,388,407,426,440,451,471,487,505,524,539,553,572,589,595,605,623,638,658,675,695,710,730,747,763,782,799,819,845,864,883,901,910,922,931,950,967,976,991,1006,1020,1036,1052,1064,1080,1095,1101,1110,1125,1131,1154,1169,1185,1200,1216,1227,1241,1253,1268,1285,1306,1323,1340,1354,1373,1376,1395,1416,1437,1452,1467,1484,1501,1518,1535,1557 } \ No newline at end of file diff --git a/radio/src/fonts/Horus/font_smlsize.png b/radio/src/fonts/Horus/font_smlsize.png new file mode 100644 index 0000000000000000000000000000000000000000..338e064bb5e48cb147f480a74f591cd2df68568e GIT binary patch literal 11541 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 z439O�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 EKikl4>Hq)$ literal 0 HcmV?d00001 diff --git a/radio/src/fonts/Horus/font_smlsize.specs b/radio/src/fonts/Horus/font_smlsize.specs new file mode 100644 index 000000000..3e3184a10 --- /dev/null +++ b/radio/src/fonts/Horus/font_smlsize.specs @@ -0,0 +1 @@ +{ 0,4,8,13,23,31,44,55,58,63,67,75,85,88,94,97,103,112,120,128,137,146,154,163,171,180,189,192,195,205,215,225,232,238,248,256,266,276,284,291,301,310,313,318,327,335,345,354,365,373,384,393,402,412,421,431,445,455,465,475,479,485,489,499,508,513,521,529,537,546,555,561,570,578,581,586,594,597,609,617,626,634,643,649,657,663,671,680,692,701,710,718,728,729,739,751,763,771,779,789,799,809,819,830 } \ No newline at end of file diff --git a/radio/src/fonts/Horus/font_stdsize.png b/radio/src/fonts/Horus/font_stdsize.png new file mode 100644 index 0000000000000000000000000000000000000000..02cdc828424535f9d34eda0afecfe5089c12f5cf GIT binary patch literal 17377 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 literal 0 HcmV?d00001 diff --git a/radio/src/fonts/Horus/font_stdsize.specs b/radio/src/fonts/Horus/font_stdsize.specs new file mode 100644 index 000000000..fe043dfda --- /dev/null +++ b/radio/src/fonts/Horus/font_stdsize.specs @@ -0,0 +1 @@ +{ 0,4,7,13,26,35,51,63,66,71,76,85,97,101,107,111,118,128,137,146,155,166,175,185,194,204,214,218,222,234,246,258,266,273,285,295,307,319,329,338,351,362,366,372,383,392,405,416,429,439,452,463,473,485,496,508,525,537,549,561,566,573,578,590,601,607,617,627,636,646,656,663,673,682,685,690,700,703,718,727,737,747,757,764,773,780,789,799,813,823,833,842,854,855,867,881,895,904,913,924,935,946,957,971, 986, 1001, 1016, 1031, 1046, 1061, 1076, 1091, 1106, 1121, 1136, 1151, 1166 } \ No newline at end of file diff --git a/radio/src/fonts/Horus/font_tinsize.png b/radio/src/fonts/Horus/font_tinsize.png new file mode 100644 index 0000000000000000000000000000000000000000..be586e83560e631c2460ee355c707de05b99d79d GIT binary patch literal 7451 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 literal 0 HcmV?d00001 diff --git a/radio/src/fonts/Horus/font_tinsize.specs b/radio/src/fonts/Horus/font_tinsize.specs new file mode 100644 index 000000000..2fbef2d86 --- /dev/null +++ b/radio/src/fonts/Horus/font_tinsize.specs @@ -0,0 +1 @@ +{ 0,4,7,12,20,26,36,44,47,51,55,61,69,72,76,79,84,91,97,103,109,116,122,129,135,142,149,151,154,162,170,178,184,189,197,204,211,219,226,232,240,247,250,254,262,268,276,283,291,298,306,313,320,328,335,343,353,360,368,375,379,384,388,396,403,407,413,420,426,432,439,444,450,456,459,463,470,473,482,488,495,502,508,513,519,524,530,537,545,552,559,565,573,574,582,591,600,606,612,619,626,633,640,648 } \ No newline at end of file diff --git a/radio/src/fonts/Horus/font_xxlsize.png b/radio/src/fonts/Horus/font_xxlsize.png new file mode 100644 index 0000000000000000000000000000000000000000..f9752d0a8c200094c00ecf4a35a81fc4b9234f51 GIT binary patch literal 42906 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 tfNAdz0-K5q48YPFn7M(y Date: Sun, 15 Nov 2015 12:54:29 +0100 Subject: [PATCH 72/74] Wrong address for updates --- companion/src/firmwares/opentx/opentxinterface.cpp | 4 ++-- companion/src/mainwindow.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/companion/src/firmwares/opentx/opentxinterface.cpp b/companion/src/firmwares/opentx/opentxinterface.cpp index c598d8d63..b6fcfc331 100644 --- a/companion/src/firmwares/opentx/opentxinterface.cpp +++ b/companion/src/firmwares/opentx/opentxinterface.cpp @@ -23,8 +23,8 @@ #include "file.h" #include "appdata.h" -#define OPENTX_FIRMWARE_DOWNLOADS "http://downloads-21.open-tx.org/firmware" -#define OPENTX_NIGHT_FIRMWARE_DOWNLOADS "http://downloads-21.open-tx.org/nightly/firmware" +#define OPENTX_FIRMWARE_DOWNLOADS "http://downloads-22.open-tx.org/firmware" +#define OPENTX_NIGHT_FIRMWARE_DOWNLOADS "http://downloads-22.open-tx.org/nightly/firmware" #define FILE_TYP_GENERAL 1 #define FILE_TYP_MODEL 2 diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp index 619e61dab..52e63ea80 100644 --- a/companion/src/mainwindow.cpp +++ b/companion/src/mainwindow.cpp @@ -70,8 +70,8 @@ #include "radiointerface.h" #include "progressdialog.h" -#define OPENTX_COMPANION_DOWNLOADS "http://downloads-21.open-tx.org/companion" -#define OPENTX_NIGHT_COMPANION_DOWNLOADS "http://downloads-21.open-tx.org/nightly/companion" +#define OPENTX_COMPANION_DOWNLOADS "http://downloads-22.open-tx.org/companion" +#define OPENTX_NIGHT_COMPANION_DOWNLOADS "http://downloads-22.open-tx.org/nightly/companion" #define DONATE_STR "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QUZ48K4SEXDP2" #ifdef __APPLE__ From 404a1934c557d4687e1aa4465800834c214f6f88 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sun, 15 Nov 2015 14:18:43 +0100 Subject: [PATCH 73/74] =?UTF-8?q?[Horus]=C2=A0GVARS=20Menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- radio/src/gui/Horus/menu_model_gvars.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/gui/Horus/menu_model_gvars.cpp b/radio/src/gui/Horus/menu_model_gvars.cpp index 5afeeecb8..d3e9c9c67 100644 --- a/radio/src/gui/Horus/menu_model_gvars.cpp +++ b/radio/src/gui/Horus/menu_model_gvars.cpp @@ -55,7 +55,7 @@ void onGVARSMenu(const char *result) } } -#define GVARS_FM_COLUMN(p) (123 + (p)*24) +#define GVARS_FM_COLUMN(p) (160 + (p)*38) bool menuModelGVars(evt_t event) { @@ -75,7 +75,7 @@ bool menuModelGVars(evt_t event) switch(j) { case 0: - editName(MENUS_MARGIN_LEFT+30, y, g_model.gvars[i].name, LEN_GVAR_NAME, event, attr); + editName(MENUS_MARGIN_LEFT+50, y, g_model.gvars[i].name, LEN_GVAR_NAME, event, attr); break; default: From e76478f27aefc96a4a14275a30c00b449b79fdfd Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 16 Nov 2015 18:08:58 +0100 Subject: [PATCH 74/74] [Horus] Merge of Telemetry menu --- radio/src/gui/Horus/menu_model_telemetry.cpp | 274 +++++++++++++----- .../src/gui/Taranis/menu_model_telemetry.cpp | 7 +- radio/src/myeeprom.h | 1 + 3 files changed, 200 insertions(+), 82 deletions(-) diff --git a/radio/src/gui/Horus/menu_model_telemetry.cpp b/radio/src/gui/Horus/menu_model_telemetry.cpp index 1b6a22618..8d5897e8e 100644 --- a/radio/src/gui/Horus/menu_model_telemetry.cpp +++ b/radio/src/gui/Horus/menu_model_telemetry.cpp @@ -73,12 +73,16 @@ enum menuModelTelemetryItems { ITEM_TELEMETRY_SENSOR30, ITEM_TELEMETRY_SENSOR31, ITEM_TELEMETRY_SENSOR32, - ITEM_TELEMETRY_NEWSENSOR, - CASE_VARIO(ITEM_TELEMETRY_VARIO_LABEL) + ITEM_TELEMETRY_DISCOVER_SENSORS, + ITEM_TELEMETRY_NEW_SENSOR, + ITEM_TELEMETRY_DELETE_ALL_SENSORS, + ITEM_TELEMETRY_IGNORE_SENSOR_INSTANCE, #if defined(VARIO) + ITEM_TELEMETRY_VARIO_LABEL, ITEM_TELEMETRY_VARIO_SOURCE, + ITEM_TELEMETRY_VARIO_RANGE, + ITEM_TELEMETRY_VARIO_CENTER, #endif - CASE_VARIO(ITEM_TELEMETRY_VARIO_RANGE) ITEM_TELEMETRY_TOP_BAR_LABEL, ITEM_TELEMETRY_TOP_BAR_VOLTAGE, ITEM_TELEMETRY_TOP_BAR_ALTITUDE, @@ -105,34 +109,39 @@ enum menuModelTelemetryItems { ITEM_TELEMETRY_MAX }; -#define TELEM_COL1 10 -#define TELEM_COL2 150 -#define TELEM_COL3 200 -#define TELEM_SCRTYPE_COL TELEM_COL2 +#define TELEM_COL1 10 +#define TELEM_COL2 150 +#define TELEM_COL3 300 +#define TELEM_BARS_COLMIN TELEM_COL2 +#define TELEM_BARS_COLMAX TELEM_COL3 +#define TELEM_SCRTYPE_COL TELEM_COL2 -#define IF_FAS_OFFSET(x) x, -#define IS_RANGE_DEFINED(k) (g_model.frsky.channels[k].ratio > 0) +#define IF_FAS_OFFSET(x) x, +#define IS_RANGE_DEFINED(k) (g_model.frsky.channels[k].ratio > 0) -#define SENSOR_ROWS(x) (isTelemetryFieldAvailable(x) ? (uint8_t)0 : HIDDEN_ROW) -#define SENSORS_ROWS LABEL(Sensors), SENSOR_ROWS(0), SENSOR_ROWS(1), SENSOR_ROWS(2), SENSOR_ROWS(3), SENSOR_ROWS(4), SENSOR_ROWS(5), SENSOR_ROWS(6), SENSOR_ROWS(7), SENSOR_ROWS(8), SENSOR_ROWS(9), SENSOR_ROWS(10), SENSOR_ROWS(11), SENSOR_ROWS(12), SENSOR_ROWS(13), SENSOR_ROWS(14), SENSOR_ROWS(15), SENSOR_ROWS(16), SENSOR_ROWS(17), SENSOR_ROWS(18), SENSOR_ROWS(19), SENSOR_ROWS(20), SENSOR_ROWS(21), SENSOR_ROWS(22), SENSOR_ROWS(23), SENSOR_ROWS(24), SENSOR_ROWS(25), SENSOR_ROWS(26), SENSOR_ROWS(27), SENSOR_ROWS(28), SENSOR_ROWS(29), SENSOR_ROWS(30), SENSOR_ROWS(31), 0, -#define USRDATA_ROWS -#define RSSI_ROWS LABEL(RSSI), 0, 0, -#if defined(LUA) - #define SCREEN_TYPE_ROWS 1 +#define SENSOR_ROWS(x) (isTelemetryFieldAvailable(x) ? (uint8_t)0 : HIDDEN_ROW) +#define SENSORS_ROWS LABEL(Sensors), SENSOR_ROWS(0), SENSOR_ROWS(1), SENSOR_ROWS(2), SENSOR_ROWS(3), SENSOR_ROWS(4), SENSOR_ROWS(5), SENSOR_ROWS(6), SENSOR_ROWS(7), SENSOR_ROWS(8), SENSOR_ROWS(9), SENSOR_ROWS(10), SENSOR_ROWS(11), SENSOR_ROWS(12), SENSOR_ROWS(13), SENSOR_ROWS(14), SENSOR_ROWS(15), SENSOR_ROWS(16), SENSOR_ROWS(17), SENSOR_ROWS(18), SENSOR_ROWS(19), SENSOR_ROWS(20), SENSOR_ROWS(21), SENSOR_ROWS(22), SENSOR_ROWS(23), SENSOR_ROWS(24), SENSOR_ROWS(25), SENSOR_ROWS(26), SENSOR_ROWS(27), SENSOR_ROWS(28), SENSOR_ROWS(29), SENSOR_ROWS(30), SENSOR_ROWS(31), 0, 0, 0, 0, +#if defined(VARIO) + #define VARIO_ROWS LABEL(Vario), 0, 1, 2, #else - #define SCREEN_TYPE_ROWS 0 + #define VARIO_ROWS #endif -#define VARIO_RANGE_ROWS 3 - -#define TELEMETRY_TYPE_ROWS (g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_PPM ? (uint8_t)0 : HIDDEN_ROW), +#define RSSI_ROWS LABEL(RSSI), 0, 0, +#if defined(LUA) + #define SCREEN_TYPE_ROWS 1 +#else + #define SCREEN_TYPE_ROWS 0 +#endif +#define VARIO_RANGE_ROWS 3 +#define TELEMETRY_TYPE_ROWS (g_model.moduleData[INTERNAL_MODULE].rfProtocol == RF_PROTO_OFF && g_model.moduleData[EXTERNAL_MODULE].type == MODULE_TYPE_PPM) ? (uint8_t)0 : HIDDEN_ROW, #if defined(LUA) - #define TELEMETRY_SCREEN_LINE(x) ((TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_NONE || TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_SCRIPT) ? HIDDEN_ROW : (uint8_t)2) + #define TELEMETRY_SCREEN_LINE(x) ((TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_NONE || TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_SCRIPT) ? HIDDEN_ROW : (uint8_t)2) #else - #define TELEMETRY_SCREEN_LINE(x) (TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_NONE ? HIDDEN_ROW : (uint8_t)2) + #define TELEMETRY_SCREEN_LINE(x) (TELEMETRY_SCREEN_TYPE(x) == TELEMETRY_SCREEN_TYPE_NONE ? HIDDEN_ROW : (uint8_t)2) #endif -#define TELEMETRY_SCREEN_ROWS(x) SCREEN_TYPE_ROWS, TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x) -#define TELEMETRY_CURRENT_EDIT_SCREEN(k) (k < ITEM_TELEMETRY_SCREEN_LABEL2 ? 0 : (k < ITEM_TELEMETRY_SCREEN_LABEL3 ? 1 : (k < ITEM_TELEMETRY_SCREEN_LABEL4 ? 2 : 3))) +#define TELEMETRY_SCREEN_ROWS(x) SCREEN_TYPE_ROWS, TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x), TELEMETRY_SCREEN_LINE(x) +#define TELEMETRY_CURRENT_SCREEN(k) (k < ITEM_TELEMETRY_SCREEN_LABEL2 ? 0 : (k < ITEM_TELEMETRY_SCREEN_LABEL3 ? 1 : (k < ITEM_TELEMETRY_SCREEN_LABEL4 ? 2 : 3))) enum SensorFields { SENSOR_FIELD_NAME, @@ -146,6 +155,7 @@ enum SensorFields { SENSOR_FIELD_PARAM3, SENSOR_FIELD_PARAM4, SENSOR_FIELD_AUTOOFFSET, + SENSOR_FIELD_ONLYPOSITIVE, SENSOR_FIELD_FILTER, SENSOR_FIELD_PERSISTENT, SENSOR_FIELD_LOGS, @@ -154,12 +164,8 @@ enum SensorFields { bool isSensorUnit(int sensor, uint8_t unit) { - if (sensor == 0) - return true; - - sensor -= 1; - - return g_model.telemetrySensors[sensor].unit == unit; + if (sensor <= 0 || sensor > MAX_SENSORS ) return true; + return g_model.telemetrySensors[--sensor].unit == unit; } bool isCellsSensor(int sensor) @@ -174,7 +180,7 @@ bool isGPSSensor(int sensor) bool isAltSensor(int sensor) { - return isSensorUnit(sensor, UNIT_DIST); + return isSensorUnit(sensor, UNIT_DIST) || isSensorUnit(sensor, UNIT_FEET); } bool isVoltsSensor(int sensor) @@ -195,37 +201,41 @@ bool isSensorAvailable(int sensor) return isTelemetryFieldAvailable(abs(sensor) - 1); } -#define SENSOR_2ND_COLUMN 80 -#define SENSOR_3RD_COLUMN 120 +#define SENSOR_2ND_COLUMN 140 +#define SENSOR_3RD_COLUMN 180 -#define SENSOR_UNIT_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) -#define SENSOR_PREC_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_UNIT_ROWS ((sensor->type == TELEM_TYPE_CALCULATED && (sensor->formula == TELEM_FORMULA_DIST)) || sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_PREC_ROWS (sensor->isPrecConfigurable() && sensor->unit != UNIT_FAHRENHEIT ? (uint8_t)0 : HIDDEN_ROW) #define SENSOR_PARAM1_ROWS (sensor->unit >= UNIT_FIRST_VIRTUAL ? HIDDEN_ROW : (uint8_t)0) -#define SENSOR_PARAM2_ROWS (sensor->unit == UNIT_RPMS || sensor->unit == UNIT_GPS || sensor->unit == UNIT_DATETIME || sensor->unit == UNIT_CELLS || (sensor->type==TELEM_TYPE_CALCULATED && (sensor->formula==TELEM_FORMULA_CONSUMPTION || sensor->formula==TELEM_FORMULA_TOTALIZE)) ? HIDDEN_ROW : (uint8_t)0) +#define SENSOR_PARAM2_ROWS (sensor->unit == UNIT_GPS || sensor->unit == UNIT_DATETIME || sensor->unit == UNIT_CELLS || (sensor->type==TELEM_TYPE_CALCULATED && (sensor->formula==TELEM_FORMULA_CONSUMPTION || sensor->formula==TELEM_FORMULA_TOTALIZE)) ? HIDDEN_ROW : (uint8_t)0) #define SENSOR_PARAM3_ROWS (sensor->type == TELEM_TYPE_CALCULATED && sensor->formula < TELEM_FORMULA_MULTIPLY) ? (uint8_t)0 : HIDDEN_ROW #define SENSOR_PARAM4_ROWS (sensor->type == TELEM_TYPE_CALCULATED && sensor->formula < TELEM_FORMULA_MULTIPLY) ? (uint8_t)0 : HIDDEN_ROW -#define SENSOR_AUTOOFFSET_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_AUTOOFFSET_ROWS (sensor->unit != UNIT_RPMS && sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_ONLYPOS_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) #define SENSOR_FILTER_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) -#define SENSOR_PERSISTENT_ROWS (sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) +#define SENSOR_PERSISTENT_ROWS ((sensor->type == TELEM_TYPE_CALCULATED && sensor->formula == TELEM_FORMULA_CONSUMPTION) || sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) bool menuModelSensor(evt_t event) { - TelemetrySensor *sensor = &g_model.telemetrySensors[s_currIdx]; + TelemetrySensor * sensor = &g_model.telemetrySensors[s_currIdx]; SUBMENU(STR_MENUSENSOR, SENSOR_FIELD_MAX, DEFAULT_SCROLLBAR_X, { 0, 0, sensor->type == TELEM_TYPE_CALCULATED ? (uint8_t)0 : (uint8_t)1, SENSOR_UNIT_ROWS, SENSOR_PREC_ROWS, SENSOR_PARAM1_ROWS, SENSOR_PARAM2_ROWS, SENSOR_PARAM3_ROWS, SENSOR_PARAM4_ROWS, 0 }); - lcdDrawNumber(MENUS_MARGIN_LEFT+getTextWidth(TR_MENUSENSOR)+5, MENU_FOOTER_TOP, s_currIdx+1, HEADER_COLOR|LEFT); - putsTelemetryChannelValue(SENSOR_2ND_COLUMN, MENU_FOOTER_TOP, s_currIdx, getValue(MIXSRC_FIRST_TELEM+3*s_currIdx), HEADER_COLOR|LEFT); + // lcdDrawNumber(MENUS_MARGIN_LEFT+getTextWidth(TR_MENUSENSOR)+5, MENU_FOOTER_TOP, s_currIdx+1, HEADER_COLOR|LEFT); + // putsTelemetryChannelValue(SENSOR_2ND_COLUMN, MENU_FOOTER_TOP, s_currIdx, getValue(MIXSRC_FIRST_TELEM+3*s_currIdx), HEADER_COLOR|LEFT); int sub = m_posVert; - for (unsigned int i=0; i= (int)DIM(mstate_tab)) { + return true; + } + } } LcdFlags attr = (sub==k ? (s_editMode>0 ? BLINK|INVERS : INVERS) : 0); @@ -258,7 +268,7 @@ bool menuModelSensor(evt_t event) if (attr) { switch (m_posHorz) { case 0: - CHECK_INCDEC_MODELVAR_ZERO(event, sensor->id, 0xffff); + sensor->id = checkIncDec(event, sensor->id, 0x0000, 0xffff, INCDEC_REP10|NO_INCDEC_MARKS); break; case 1: @@ -295,6 +305,9 @@ bool menuModelSensor(evt_t event) if (attr) { CHECK_INCDEC_MODELVAR_ZERO(event, sensor->unit, UNIT_MAX); if (checkIncDec_Ret) { + if (sensor->unit == UNIT_FAHRENHEIT) { + sensor->prec = 0; + } telemetryItems[s_currIdx].clear(); } } @@ -319,7 +332,7 @@ bool menuModelSensor(evt_t event) break; } else if (sensor->formula == TELEM_FORMULA_DIST) { - lcdDrawText(MENUS_MARGIN_LEFT, y, "GPS sensor"); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_GPSSENSOR); putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->dist.gps ? MIXSRC_FIRST_TELEM+3*(sensor->dist.gps-1) : 0, attr); if (attr) { sensor->dist.gps = checkIncDec(event, sensor->dist.gps, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isGPSSensor); @@ -327,34 +340,50 @@ bool menuModelSensor(evt_t event) break; } else if (sensor->formula == TELEM_FORMULA_CONSUMPTION) { - lcdDrawText(MENUS_MARGIN_LEFT, y, "Amps sensor"); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CURRENTSENSOR); putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->consumption.source ? MIXSRC_FIRST_TELEM+3*(sensor->consumption.source-1) : 0, attr); if (attr) { - sensor->consumption.source = checkIncDec(event, sensor->consumption.source, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isCurrentSensor); + sensor->consumption.source = checkIncDec(event, sensor->consumption.source, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isTelemetryFieldAvailable); + } + break; + } + else if (sensor->formula == TELEM_FORMULA_TOTALIZE) { + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_SOURCE)); + putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->consumption.source ? MIXSRC_FIRST_TELEM+3*(sensor->consumption.source-1) : 0, attr); + if (attr) { + sensor->consumption.source = checkIncDec(event, sensor->consumption.source, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isTelemetryFieldComparisonAvailable); } break; } } else { - lcdDrawText(MENUS_MARGIN_LEFT, y, "Ratio"); - if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.ratio, 0, 30000); - if (sensor->custom.ratio == 0) - lcdDrawText(SENSOR_2ND_COLUMN, y, "-", attr); - else - lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); - break; + if (sensor->unit == UNIT_RPMS) { + lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_BLADES)); + if (attr) sensor->custom.ratio = checkIncDec(event, sensor->custom.ratio, 1, 30000, EE_MODEL|NO_INCDEC_MARKS|INCDEC_REP10); + lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr); + break; + } + else { + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_RATIO); + if (attr) sensor->custom.ratio = checkIncDec(event, sensor->custom.ratio, 0, 30000, EE_MODEL|NO_INCDEC_MARKS|INCDEC_REP10); + if (sensor->custom.ratio == 0) + lcdDrawText(SENSOR_2ND_COLUMN, y, "-", attr); + else + lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.ratio, LEFT|attr|PREC1); + break; + } } // no break case SENSOR_FIELD_PARAM2: if (sensor->type == TELEM_TYPE_CALCULATED) { if (sensor->formula == TELEM_FORMULA_CELL) { - lcdDrawText(MENUS_MARGIN_LEFT, y, "Cell index"); - sensor->cell.index = selectMenuItem(SENSOR_2ND_COLUMN, y, "\007Lowest\0001\0 2\0 3\0 4\0 5\0 6\0 HighestDelta\0", sensor->cell.index, 0, 8, attr, event); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CELLINDEX); + sensor->cell.index = selectMenuItem(SENSOR_2ND_COLUMN, y, STR_VCELLINDEX, sensor->cell.index, 0, 8, attr, event); break; } else if (sensor->formula == TELEM_FORMULA_DIST) { - lcdDrawText(MENUS_MARGIN_LEFT, y, "Alt sensor"); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ALTSENSOR); putsMixerSource(SENSOR_2ND_COLUMN, y, sensor->dist.alt ? MIXSRC_FIRST_TELEM+3*(sensor->dist.alt-1) : 0, attr); if (attr) { sensor->dist.alt = checkIncDec(event, sensor->dist.alt, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isAltSensor); @@ -362,9 +391,15 @@ bool menuModelSensor(evt_t event) break; } } + else if (sensor->unit == UNIT_RPMS) { + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_MULTIPLIER); + if (attr) sensor->custom.offset = checkIncDec(event, sensor->custom.offset, 1, 30000, EE_MODEL|NO_INCDEC_MARKS|INCDEC_REP10); + lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.offset, LEFT|attr); + break; + } else { lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_OFFSET)); - if (attr) CHECK_INCDEC_MODELVAR(event, sensor->custom.offset, -30000, +30000); + if (attr) sensor->custom.offset = checkIncDec(event, sensor->custom.offset, -30000, +30000, EE_MODEL|NO_INCDEC_MARKS|INCDEC_REP10); if (sensor->prec > 0) attr |= (sensor->prec == 2 ? PREC2 : PREC1); lcdDrawNumber(SENSOR_2ND_COLUMN, y, sensor->custom.offset, LEFT|attr); break; @@ -396,6 +431,11 @@ bool menuModelSensor(evt_t event) onoffMenuItem(sensor->autoOffset, SENSOR_2ND_COLUMN, y, attr, event); break; + case SENSOR_FIELD_ONLYPOSITIVE: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ONLYPOSITIVE); + onoffMenuItem(sensor->onlyPositive, SENSOR_2ND_COLUMN, y, attr, event); + break; + case SENSOR_FIELD_FILTER: lcdDrawText(MENUS_MARGIN_LEFT, y, STR_FILTER); onoffMenuItem(sensor->filter, SENSOR_2ND_COLUMN, y, attr, event); @@ -406,6 +446,14 @@ bool menuModelSensor(evt_t event) onoffMenuItem(sensor->persistent, SENSOR_2ND_COLUMN, y, attr, event); break; + case SENSOR_FIELD_LOGS: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_LOGS); + onoffMenuItem(sensor->logs, SENSOR_2ND_COLUMN, y, attr, event); + if (attr && checkIncDec_Ret) { + closeLogs(); + } + break; + } } @@ -414,7 +462,7 @@ bool menuModelSensor(evt_t event) void onSensorMenu(const char *result) { - uint8_t index = m_posVert - ITEM_TELEMETRY_SENSOR1; + int index = m_posVert - ITEM_TELEMETRY_SENSOR1; if (index < MAX_SENSORS) { if (result == STR_EDIT) { @@ -426,7 +474,23 @@ void onSensorMenu(const char *result) if (index= 0) { + TelemetrySensor & sourceSensor = g_model.telemetrySensors[index]; + TelemetrySensor & newSensor = g_model.telemetrySensors[newIndex]; + newSensor = sourceSensor; + TelemetryItem & sourceItem = telemetryItems[index]; + TelemetryItem & newItem = telemetryItems[newIndex]; + newItem = sourceItem; + storageDirty(EE_MODEL); + } + else { + POPUP_WARNING(STR_TELEMETRYFULL); + } } } } @@ -434,8 +498,8 @@ void onSensorMenu(const char *result) #if defined(LUA) void onTelemetryScriptFileSelectionMenu(const char *result) { - int8_t sub = m_posVert; - uint8_t screenIndex = TELEMETRY_CURRENT_EDIT_SCREEN(sub); + int sub = m_posVert; + int screenIndex = TELEMETRY_CURRENT_SCREEN(sub); if (result == STR_UPDATE_LIST) { if (!sdListFiles(SCRIPTS_TELEM_PATH, SCRIPTS_EXT, sizeof(g_model.frsky.screens[screenIndex].script.file), NULL)) { @@ -454,7 +518,14 @@ void onTelemetryScriptFileSelectionMenu(const char *result) bool menuModelTelemetry(evt_t event) { - MENU(STR_MENUTELEMETRY, menuTabModel, e_Telemetry, ITEM_TELEMETRY_MAX, DEFAULT_SCROLLBAR_X, { TELEMETRY_TYPE_ROWS RSSI_ROWS SENSORS_ROWS USRDATA_ROWS CASE_VARIO(LABEL(Vario)) CASE_VARIO(0) CASE_VARIO(VARIO_RANGE_ROWS) LABEL(TopBar), 0, 0, TELEMETRY_SCREEN_ROWS(0), TELEMETRY_SCREEN_ROWS(1), TELEMETRY_SCREEN_ROWS(2), TELEMETRY_SCREEN_ROWS(3) }); + if (s_warning_result) { + s_warning_result = 0; + for (int i=0; itype == TELEM_TYPE_CUSTOM && !g_model.ignoreSensorIds) { + lcdDrawNumber(TELEM_COL3, y, sensor->instance, LEFT); + } if (attr) { s_editMode = 0; s_currIdx = index; if (event == EVT_KEY_LONG(KEY_ENTER)) { killEvents(event); MENU_ADD_ITEM(STR_EDIT); + MENU_ADD_ITEM(STR_COPY); MENU_ADD_ITEM(STR_DELETE); menuHandler = onSensorMenu; } @@ -508,10 +584,22 @@ bool menuModelTelemetry(evt_t event) case ITEM_TELEMETRY_SENSORS_LABEL: lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TELEMETRY_SENSORS); + lcdDrawText(TELEM_COL2, y, STR_VALUE, 0); + if (!g_model.ignoreSensorIds) { + lcdDrawText(TELEM_COL3, y, STR_ID, 0); + } break; - case ITEM_TELEMETRY_NEWSENSOR: - lcdDrawText(MENUS_MARGIN_LEFT+INDENT_WIDTH, y, NO_INDENT(STR_TELEMETRY_NEWSENSOR), attr); + case ITEM_TELEMETRY_DISCOVER_SENSORS: + lcdDrawText(MENUS_MARGIN_LEFT, y, allowNewSensors ? STR_STOP_DISCOVER_SENSORS : STR_DISCOVER_SENSORS, attr); + if (attr && event==EVT_KEY_BREAK(KEY_ENTER)) { + s_editMode = 0; + allowNewSensors = !allowNewSensors; + } + break; + + case ITEM_TELEMETRY_NEW_SENSOR: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TELEMETRY_NEWSENSOR, attr); if (attr && event==EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; int res = availableTelemetryIndex(); @@ -519,8 +607,26 @@ bool menuModelTelemetry(evt_t event) s_currIdx = res; pushMenu(menuModelSensor); } + else { + POPUP_WARNING(STR_TELEMETRYFULL); + } } break; + + case ITEM_TELEMETRY_DELETE_ALL_SENSORS: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_DELETE_ALL_SENSORS, attr); + if (attr) + s_editMode = 0; + if (attr && event==EVT_KEY_LONG(KEY_ENTER)) { + killEvents(KEY_ENTER); + POPUP_CONFIRMATION(STR_CONFIRMDELETE); + } + break; + + case ITEM_TELEMETRY_IGNORE_SENSOR_INSTANCE: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_IGNORE_INSTANCE); + onoffMenuItem(g_model.ignoreSensorIds, TELEM_COL2, y, attr, event); + break; case ITEM_TELEMETRY_RSSI_LABEL: lcdDrawText(MENUS_MARGIN_LEFT, y, PSTR("RSSI")); @@ -551,24 +657,36 @@ bool menuModelTelemetry(evt_t event) break; case ITEM_TELEMETRY_VARIO_RANGE: - lcdDrawText(MENUS_MARGIN_LEFT, y, STR_LIMIT); - lcdDrawNumber(TELEM_COL2, y, -10+g_model.frsky.varioMin, (m_posHorz<=0 ? attr : 0)|LEFT); - lcdDrawNumber(TELEM_COL2+70, y, -5+g_model.frsky.varioCenterMin, ((CURSOR_ON_LINE() || m_posHorz==1) ? attr : 0)|PREC1); - lcdDrawNumber(TELEM_COL2+115, y, 5+g_model.frsky.varioCenterMax, ((CURSOR_ON_LINE() || m_posHorz==2) ? attr : 0)|PREC1); - lcdDrawNumber(TELEM_COL2+160, y, 10+g_model.frsky.varioMax, ((CURSOR_ON_LINE() || m_posHorz==3) ? attr : 0)); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_RANGE); + lcdDrawNumber(TELEM_COL2, y, -10+g_model.frsky.varioMin, (m_posHorz==0 ? attr : 0)|LEFT); + lcdDrawNumber(TELEM_COL2+100, y, 10+g_model.frsky.varioMax, (m_posHorz==1 ? attr : 0)|LEFT); if (attr && s_editMode>0) { switch (m_posHorz) { case 0: CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioMin, -7, 7); break; case 1: + CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioMax, -7, 7); + break; + } + } + break; + + case ITEM_TELEMETRY_VARIO_CENTER: + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_CENTER); + lcdDrawNumber(TELEM_COL2, y, -5+g_model.frsky.varioCenterMin, (m_posHorz==0 ? attr : 0)|PREC1|LEFT); + lcdDrawNumber(TELEM_COL2+100, y, 5+g_model.frsky.varioCenterMax, (m_posHorz==1 ? attr : 0)|PREC1|LEFT); + lcdDrawTextAtIndex(TELEM_COL3, y, STR_VVARIOCENTER, g_model.frsky.varioCenterSilent, (m_posHorz==2 ? attr : 0)); + if (attr && s_editMode>0) { + switch (m_posHorz) { + case 0: CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioCenterMin, -16, 5+min(10, g_model.frsky.varioCenterMax+5)); break; - case 2: + case 1: CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioCenterMax, -5+max(-10, g_model.frsky.varioCenterMin-5), +15); break; - case 3: - CHECK_INCDEC_MODELVAR(event, g_model.frsky.varioMax, -7, 7); + case 2: + CHECK_INCDEC_MODELVAR_ZERO(event, g_model.frsky.varioCenterSilent, 1); break; } } @@ -576,7 +694,7 @@ bool menuModelTelemetry(evt_t event) #endif case ITEM_TELEMETRY_TOP_BAR_LABEL: - lcdDrawText(MENUS_MARGIN_LEFT, y, "Main Telemetry Fields"); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_TOP_BAR); break; case ITEM_TELEMETRY_TOP_BAR_VOLTAGE: @@ -588,7 +706,7 @@ bool menuModelTelemetry(evt_t event) break; case ITEM_TELEMETRY_TOP_BAR_ALTITUDE: - lcdDrawText(MENUS_MARGIN_LEFT, y, INDENT "Altitude Source"); + lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ALTITUDE); putsMixerSource(TELEM_COL2, y, g_model.frsky.altitudeSource ? MIXSRC_FIRST_TELEM+3*(g_model.frsky.altitudeSource-1) : 0, attr); if (attr) { g_model.frsky.altitudeSource = checkIncDec(event, g_model.frsky.altitudeSource, 0, MAX_SENSORS, EE_MODEL|NO_INCDEC_MARKS, isAltSensor); @@ -600,7 +718,7 @@ bool menuModelTelemetry(evt_t event) case ITEM_TELEMETRY_SCREEN_LABEL3: case ITEM_TELEMETRY_SCREEN_LABEL4: { - uint8_t screenIndex = TELEMETRY_CURRENT_EDIT_SCREEN(k); + uint8_t screenIndex = TELEMETRY_CURRENT_SCREEN(k); putsStrIdx(MENUS_MARGIN_LEFT, y, STR_SCREEN, screenIndex+1); TelemetryScreenType oldScreenType = TELEMETRY_SCREEN_TYPE(screenIndex); TelemetryScreenType newScreenType = (TelemetryScreenType)selectMenuItem(TELEM_SCRTYPE_COL, y, STR_VTELEMSCREENTYPE, oldScreenType, 0, TELEMETRY_SCREEN_TYPE_MAX, (m_posHorz==0 ? attr : 0), event); @@ -695,7 +813,7 @@ bool menuModelTelemetry(evt_t event) if (attr && s_editMode>0) { switch (m_posHorz) { case 0: - bar.source = CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, barSource, MIXSRC_LAST_TELEM, isSourceAvailable); + bar.source = checkIncDec(event, barSource, 0, MIXSRC_LAST_TELEM, EE_MODEL|INCDEC_SOURCE|NO_INCDEC_MARKS, isSourceAvailable); if (checkIncDec_Ret) { if (barSource <= MIXSRC_LAST_CH) { bar.barMin = -100; @@ -725,7 +843,7 @@ bool menuModelTelemetry(evt_t event) const coord_t pos[] = {MENUS_MARGIN_LEFT+INDENT_WIDTH, MENUS_MARGIN_LEFT+INDENT_WIDTH+100, MENUS_MARGIN_LEFT+INDENT_WIDTH+200}; putsMixerSource(pos[c], y, value, cellAttr); if (cellAttr && s_editMode>0) { - CHECK_INCDEC_MODELVAR_ZERO_CHECK(event, value, MIXSRC_LAST_TELEM, isSourceAvailable); + value = checkIncDec(event, value, 0, MIXSRC_LAST_TELEM, EE_MODEL|INCDEC_SOURCE|NO_INCDEC_MARKS, isSourceAvailable); } } if (attr && m_posHorz == NUM_LINE_ITEMS) { diff --git a/radio/src/gui/Taranis/menu_model_telemetry.cpp b/radio/src/gui/Taranis/menu_model_telemetry.cpp index 5370cbc2f..0c48ef463 100644 --- a/radio/src/gui/Taranis/menu_model_telemetry.cpp +++ b/radio/src/gui/Taranis/menu_model_telemetry.cpp @@ -201,8 +201,8 @@ bool isSensorAvailable(int sensor) return isTelemetryFieldAvailable(abs(sensor) - 1); } -#define SENSOR_2ND_COLUMN (12*FW) -#define SENSOR_3RD_COLUMN (18*FW) +#define SENSOR_2ND_COLUMN (12*FW) +#define SENSOR_3RD_COLUMN (18*FW) #define SENSOR_UNIT_ROWS ((sensor->type == TELEM_TYPE_CALCULATED && (sensor->formula == TELEM_FORMULA_DIST)) || sensor->isConfigurable() ? (uint8_t)0 : HIDDEN_ROW) #define SENSOR_PREC_ROWS (sensor->isPrecConfigurable() && sensor->unit != UNIT_FAHRENHEIT ? (uint8_t)0 : HIDDEN_ROW) @@ -217,7 +217,7 @@ bool isSensorAvailable(int sensor) void menuModelSensor(uint8_t event) { - TelemetrySensor * sensor = & g_model.telemetrySensors[s_currIdx]; + TelemetrySensor * sensor = &g_model.telemetrySensors[s_currIdx]; SUBMENU(STR_MENUSENSOR, SENSOR_FIELD_MAX, { 0, 0, sensor->type == TELEM_TYPE_CALCULATED ? (uint8_t)0 : (uint8_t)1, SENSOR_UNIT_ROWS, SENSOR_PREC_ROWS, SENSOR_PARAM1_ROWS, SENSOR_PARAM2_ROWS, SENSOR_PARAM3_ROWS, SENSOR_PARAM4_ROWS, SENSOR_AUTOOFFSET_ROWS, SENSOR_ONLYPOS_ROWS, SENSOR_FILTER_ROWS, SENSOR_PERSISTENT_ROWS, 0 }); lcd_outdezAtt(PSIZE(TR_MENUSENSOR)*FW+1, 0, s_currIdx+1, INVERS|LEFT); @@ -681,7 +681,6 @@ void menuModelTelemetry(uint8_t event) } } break; - #endif case ITEM_TELEMETRY_TOP_BAR_LABEL: diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index 74db54f83..93c8bb722 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -533,6 +533,7 @@ enum PotsWarnMode { #if defined(PCBHORUS) enum ModuleIndex { + INTERNAL_MODULE, EXTERNAL_MODULE, TRAINER_MODULE, };