diff --git a/radio/src/bitmaps/480x272/default_theme/mask_menu_model_select.png b/radio/src/bitmaps/480x272/default_theme/mask_menu_model_select.png new file mode 100644 index 000000000..e4d326ce3 Binary files /dev/null and b/radio/src/bitmaps/480x272/default_theme/mask_menu_model_select.png differ diff --git a/radio/src/bitmaps/480x272/default_theme/mask_model_select_category.png b/radio/src/bitmaps/480x272/default_theme/mask_model_select_category.png new file mode 100644 index 000000000..5663b18df Binary files /dev/null and b/radio/src/bitmaps/480x272/default_theme/mask_model_select_category.png differ diff --git a/radio/src/dump.cpp b/radio/src/dump.cpp index 12a6b3459..c8964ce10 100644 --- a/radio/src/dump.cpp +++ b/radio/src/dump.cpp @@ -26,31 +26,31 @@ #define dumpPrintf(...) serialPrintf(__VA_ARGS__) #endif +#if defined(DEBUG) || defined(CLI) uint8_t dumpPosition; -void dumpStart(unsigned int size) +static void dumpStart(unsigned int size) { dumpPrintf("DUMP %d bytes ...\n\r", size); dumpPosition = 0; } -void dumpBody(const void *data, unsigned int size) +static void dumpBody(const void *data, unsigned int size) { for (unsigned int i=0; i textHandler, + const char* message, const int lineHeight, + const LcdFlags textFlags) : Dialog(parent, title, {50, 73, LCD_W - 100, LCD_H - 146}) { setWindowFlags(windowFlags); @@ -34,21 +62,22 @@ MessageDialog::MessageDialog(Window* parent, const char* title, {0, coord_t(height() - PAGE_LINE_HEIGHT) / 2, width(), PAGE_LINE_HEIGHT}, message, 0, CENTERED); - infoWidget = new StaticText( + infoWidget = new DynamicText( this, {0, 30 + coord_t(height() - PAGE_LINE_HEIGHT) / 2, width(), lineHeight}, - info, 0, textFlags); + textHandler, textFlags); setCloseWhenClickOutside(true); setFocus(); } #if defined(HARDWARE_KEYS) -void MessageDialog::onEvent(event_t event) +void DynamicMessageDialog::onEvent(event_t event) { - TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), event); + TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), + event); if (event == EVT_KEY_BREAK(KEY_EXIT) || event == EVT_KEY_BREAK(KEY_ENTER)) { deleteLater(); } } -#endif +#endif \ No newline at end of file diff --git a/radio/src/gui/colorlcd/message_dialog.h b/radio/src/gui/colorlcd/message_dialog.h index bf0d570ba..a439568e1 100644 --- a/radio/src/gui/colorlcd/message_dialog.h +++ b/radio/src/gui/colorlcd/message_dialog.h @@ -23,23 +23,46 @@ #include "dialog.h" #include "static.h" -class MessageDialog : public Dialog +class MessageDialog: public Dialog { + public: + MessageDialog(Window * parent, const char * title, const char * message, const char * info = ""); + + void setInfoText(std::string text) + { + infoWidget->setText(std::move(text)); + } + + protected: + StaticText * messageWidget; + StaticText * infoWidget; + +#if defined(DEBUG_WINDOWS) + std::string getName() const override + { + return "MessageDialog"; + } +#endif + +#if defined(HARDWARE_KEYS) + void onEvent(event_t event) override; +#endif +}; + +class DynamicMessageDialog : public Dialog { public: - MessageDialog(Window* parent, const char* title, const char* message, - const char* info = "", const int lineHeight = PAGE_LINE_HEIGHT, - const WindowFlags windowFlags = 0, + DynamicMessageDialog(Window* parent, const char* title, + std::function textHandler, const char* message = "", const int lineHeight = PAGE_LINE_HEIGHT, const LcdFlags textFlags = CENTERED); - // Attn.: FONT(XXL) is not supported by MessageDialog + // Attn.: FONT(XXL) is not supported by DynamicMessageDialog - void setInfoText(std::string text) { infoWidget->setText(std::move(text)); } protected: StaticText* messageWidget; - StaticText* infoWidget; - LcdFlags textFlags; + DynamicText* infoWidget; + #if defined(DEBUG_WINDOWS) - std::string getName() const override { return "MessageDialog"; } + std::string getName() const override { return "DynamicMessageDialog"; } #endif #if defined(HARDWARE_KEYS) @@ -47,4 +70,5 @@ class MessageDialog : public Dialog #endif }; + #endif // _MESSAGE_DIALOG_H_ diff --git a/radio/src/gui/colorlcd/model_inputs.cpp b/radio/src/gui/colorlcd/model_inputs.cpp index 62a24d2a8..a28b1e0dd 100644 --- a/radio/src/gui/colorlcd/model_inputs.cpp +++ b/radio/src/gui/colorlcd/model_inputs.cpp @@ -284,12 +284,12 @@ class InputEditWindow : public Page // Flight modes new StaticText(window, grid.getLabelSlot(), STR_FLMODE); - for (uint8_t i=0; i 0 && (i % 4) == 0) grid.nextLine(); new TextButton(window, grid.getFieldSlot(4, i % 4), fm, - [=]() -> uint8_t { + [=]() -> uint32_t { BFBIT_FLIP(line->flightModes, bfBit(i)); SET_DIRTY(); return !(bfSingleBitGet(line->flightModes, i)); diff --git a/radio/src/gui/colorlcd/model_mixes.cpp b/radio/src/gui/colorlcd/model_mixes.cpp index 3d534b32a..a6fae37a9 100644 --- a/radio/src/gui/colorlcd/model_mixes.cpp +++ b/radio/src/gui/colorlcd/model_mixes.cpp @@ -136,13 +136,13 @@ class MixEditWindow : public Page // Flight modes new StaticText(window, grid.getLabelSlot(), STR_FLMODE); - for (uint8_t i = 0; i < MAX_FLIGHT_MODES; i++) { + for (uint32_t i = 0; i < MAX_FLIGHT_MODES; i++) { char fm[2] = {char('0' + i), '\0'}; if (i > 0 && (i % 4) == 0) grid.nextLine(); new TextButton( window, grid.getFieldSlot(4, i % 4), fm, - [=]() -> uint8_t { - BFBIT_FLIP(mix->flightModes, bfBit(i)); + [=]() -> uint32_t { + BFBIT_FLIP(mix->flightModes, bfBit(i)); SET_DIRTY(); return !(bfSingleBitGet(mix->flightModes, i)); }, diff --git a/radio/src/gui/colorlcd/model_select.cpp b/radio/src/gui/colorlcd/model_select.cpp index 909b2cc5c..8ecb3ffa7 100644 --- a/radio/src/gui/colorlcd/model_select.cpp +++ b/radio/src/gui/colorlcd/model_select.cpp @@ -244,6 +244,7 @@ class ModelCategoryPageBody : public FormWindow storageCheck(true); modelslist.setCurrentModel(model); + modelslist.setCurrentCategory(category); update(); }); } @@ -271,7 +272,7 @@ class ModelCategoryPageBody : public FormWindow .c_str(), [=] { modelslist.removeModel(category, model); - update(index < category->size() - 1 ? index : index - 1); + update(index < (int)category->size() - 1 ? index : index - 1); }); }); } @@ -353,7 +354,7 @@ class ModelCategoryPage : public PageTab { public: explicit ModelCategoryPage(ModelsCategory *category) : - PageTab(category->name, ICON_MODEL), category(category) + PageTab(category->name, ICON_MODEL_SELECT_CATEGORY), category(category) { } @@ -369,7 +370,7 @@ class ModelCategoryPage : public PageTab }; ModelSelectMenu::ModelSelectMenu(): - TabsGroup(ICON_MODEL) + TabsGroup(ICON_MODEL_SELECT) { modelslist.load(); diff --git a/radio/src/gui/colorlcd/model_setup.cpp b/radio/src/gui/colorlcd/model_setup.cpp index cbd353bf6..75033cc05 100644 --- a/radio/src/gui/colorlcd/model_setup.cpp +++ b/radio/src/gui/colorlcd/model_setup.cpp @@ -41,26 +41,6 @@ std::string switchWarninglabel(swsrc_t index) 1); } -class RssiDialog : public MessageDialog -{ - public: - RssiDialog(Window* parent, const char* title, const char* message, - const char* info = "", const int lineHeight = PAGE_LINE_HEIGHT, - const WindowFlags windowFlags = 0, - const LcdFlags textFlags = CENTERED) : - MessageDialog(parent, title, message, info, lineHeight, windowFlags, - textFlags) - { - } - - virtual void checkEvents() - { - char buf[10]; - sprintf(buf, "%d", (int)TELEMETRY_RSSI()); - setInfoText(buf); - MessageDialog::checkEvents(); - } -}; class ChannelFailsafeBargraph: public Window { public: @@ -644,6 +624,7 @@ class ModuleWindow : public FormGroup { } #if defined(MULTIMODULE) else if (isModuleMultimodule(moduleIdx)) { + Choice * mmSubProtocol = nullptr; grid.nextLine(); new StaticText(this, grid.getLabelSlot(true), STR_RF_PROTOCOL); @@ -653,6 +634,8 @@ class ModuleWindow : public FormGroup { GET_DEFAULT(multiRfProto), [=](int32_t newValue) { g_model.moduleData[moduleIdx].setMultiProtocol(newValue); + g_model.moduleData[moduleIdx].subType = 0; + if (mmSubProtocol != nullptr) mmSubProtocol->invalidate(); resetMultiProtocolsOptions(moduleIdx); SET_DIRTY(); update(); @@ -662,7 +645,7 @@ class ModuleWindow : public FormGroup { // Subtype (D16, DSMX,...) const mm_protocol_definition * pdef = getMultiProtocolDefinition(g_model.moduleData[moduleIdx].getMultiProtocol()); if (pdef->maxSubtype > 0) - new Choice(this, grid.getFieldSlot(2, 1), pdef->subTypeString, 0, pdef->maxSubtype,GET_SET_DEFAULT(g_model.moduleData[moduleIdx].subType)); + mmSubProtocol = new Choice(this, grid.getFieldSlot(2, 1), pdef->subTypeString, 0, pdef->maxSubtype,GET_SET_DEFAULT(g_model.moduleData[moduleIdx].subType)); grid.nextLine(); // Multimodule status @@ -868,8 +851,14 @@ class ModuleWindow : public FormGroup { } else { moduleState[moduleIdx].mode = MODULE_MODE_RANGECHECK; - auto rssiDialog = new RssiDialog( - this, "Range Test", "RSSI:", "", 50, REFRESH_ALWAYS, + auto rssiDialog = new DynamicMessageDialog( + this, "Range Test", + [=]() { + char buf[16]; + sprintf(buf, "%d db", (int)TELEMETRY_RSSI()); + return std::string(buf); + }, + "RSSI:", 50, DEFAULT_COLOR | CENTERED | FONT(BOLD) | FONT(XL)); rssiDialog->setCloseHandler([this]() { rangeButton->check(false); @@ -1023,6 +1012,8 @@ void onBindMenu(const char * result) const char * STR_TIMER_MODES[] = {"OFF", "ON", "Start", "Throttle", "Throttle %", "Throttle Start"}; +const char MODEL_NAME_EXTRA_CHARS[] = "_-.,:;<=>"; + void ModelSetupPage::build(FormWindow * window) { FormGridLayout grid; @@ -1030,7 +1021,8 @@ void ModelSetupPage::build(FormWindow * window) // Model name new StaticText(window, grid.getLabelSlot(), STR_MODELNAME); - auto text = new RadioTextEdit(window, grid.getFieldSlot(), g_model.header.name, sizeof(g_model.header.name)); + auto text = new RadioTextEdit(window, grid.getFieldSlot(), g_model.header.name, sizeof(g_model.header.name), + 0, MODEL_NAME_EXTRA_CHARS); text->setChangeHandler([=] { modelslist.load(); auto model = modelslist.getCurrentModel(); diff --git a/radio/src/gui/colorlcd/model_telemetry.cpp b/radio/src/gui/colorlcd/model_telemetry.cpp index d945293bc..3ae56b644 100644 --- a/radio/src/gui/colorlcd/model_telemetry.cpp +++ b/radio/src/gui/colorlcd/model_telemetry.cpp @@ -552,13 +552,34 @@ void ModelTelemetryPage::build(FormWindow * window, int8_t focusSensorIndex) }); grid.nextLine(); new StaticText(window, grid.getLabelSlot(true), STR_RANGE); - new NumberEdit(window, grid.getFieldSlot(2, 0), -7, 7, GET_SET_WITH_OFFSET(g_model.varioData.min, -10)); - new NumberEdit(window, grid.getFieldSlot(2, 1), -7, 7, GET_SET_WITH_OFFSET(g_model.varioData.max, 10)); + + auto vMin = new NumberEdit(window, grid.getFieldSlot(2, 0), -17, 17, + GET_SET_WITH_OFFSET(g_model.varioData.min, -10)); + vMin->setAvailableHandler( + [](int val) { return val < g_model.varioData.max + 10; }); + + auto vMax = new NumberEdit(window, grid.getFieldSlot(2, 1), -17, 17, + GET_SET_WITH_OFFSET(g_model.varioData.max, 10)); + vMax->setAvailableHandler( + [](int val) { return g_model.varioData.min - 10 < val; }); + grid.nextLine(); new StaticText(window, grid.getLabelSlot(true), STR_CENTER); - new NumberEdit(window, grid.getFieldSlot(3, 0), -7, 7, GET_SET_WITH_OFFSET(g_model.varioData.min, -5), 0, PREC1); - new NumberEdit(window, grid.getFieldSlot(3, 1), -7, 7, GET_SET_WITH_OFFSET(g_model.varioData.max, 5), 0, PREC1); - new Choice(window, grid.getFieldSlot(3, 2), STR_VVARIOCENTER, 0, 1, GET_SET_DEFAULT(g_model.varioData.centerSilent)); + + auto cMin = new NumberEdit( + window, grid.getFieldSlot(3, 0), -15, 15, + GET_SET_WITH_OFFSET(g_model.varioData.centerMin, -5), 0, PREC1); + cMin->setAvailableHandler( + [](int val) { return val < g_model.varioData.centerMax + 5; }); + + auto cMax = new NumberEdit( + window, grid.getFieldSlot(3, 1), -15, 15, + GET_SET_WITH_OFFSET(g_model.varioData.centerMax, 5), 0, PREC1); + cMax->setAvailableHandler( + [](int val) { return g_model.varioData.centerMin - 5 < val; }); + + new Choice(window, grid.getFieldSlot(3, 2), STR_VVARIOCENTER, 0, 1, + GET_SET_DEFAULT(g_model.varioData.centerSilent)); grid.nextLine(); window->setInnerHeight(grid.getWindowHeight()); diff --git a/radio/src/gui/colorlcd/radio_diaganas.cpp b/radio/src/gui/colorlcd/radio_diaganas.cpp index 3b24c3cd6..939b113b2 100644 --- a/radio/src/gui/colorlcd/radio_diaganas.cpp +++ b/radio/src/gui/colorlcd/radio_diaganas.cpp @@ -49,7 +49,7 @@ class RadioAnalogsDiagsWindow: public Window { } #if defined(HARDWARE_TOUCH) - constexpr coord_t y = MENU_CONTENT_TOP + 6 * FH; + constexpr coord_t y = MENU_CONTENT_TOP + 5 * FH; if (touchState.event != TE_NONE && touchState.event != TE_SLIDE_END) { coord_t x = dc->drawText(MENUS_MARGIN_LEFT, y, STR_TOUCH_PANEL); @@ -59,6 +59,14 @@ class RadioAnalogsDiagsWindow: public Window { dc->drawLine(touchState.x - 10, touchState.y - 8 - parent->top(), touchState.x + 10, touchState.y + 8 - parent->top(), SOLID, 0); dc->drawLine(touchState.x - 10, touchState.y + 8 - parent->top(), touchState.x + 10, touchState.y - 8- parent->top(), SOLID, 0); } +#if !defined(SIMU) + constexpr coord_t y1 = MENU_CONTENT_TOP + 6 * FH; + coord_t x1 = MENUS_MARGIN_LEFT; + x1 = dc->drawText(x1, y1, "Touch GT911 FW ver:") + 8; + x1 = dc->drawNumber(x1, y1, touchGT911fwver, LEFT, 4) + 16; + x1 = dc->drawText(x1, y1, "TSI2CEvents:") + 4; + dc->drawNumber(x1, y1, touchGT911hiccups, LEFT, 5); +#endif #endif }; diff --git a/radio/src/gui/colorlcd/radio_sdmanager.cpp b/radio/src/gui/colorlcd/radio_sdmanager.cpp index 2eef2e5fa..217d44612 100644 --- a/radio/src/gui/colorlcd/radio_sdmanager.cpp +++ b/radio/src/gui/colorlcd/radio_sdmanager.cpp @@ -26,6 +26,7 @@ #include "io/bootloader_flash.h" #include "standalone_lua.h" #include "sdcard.h" +#include "view_text.h" class FileNameEditWindow : public Page { @@ -232,15 +233,15 @@ void RadioSdManagerPage::build(FormWindow * window) if (res != FR_OK || fno.fname[0] == 0) break; // Break on error or end of dir - if (strlen(fno.fname) > SD_SCREEN_FILE_LENGTH) + if (strlen((const char*)fno.fname) > SD_SCREEN_FILE_LENGTH) continue; if (fno.fname[0] == '.' && fno.fname[1] != '.') continue; // Ignore hidden files under UNIX, but not .. if (fno.fattrib & AM_DIR) { - directories.push_back(fno.fname); + directories.push_back((char*)fno.fname); } else { - files.push_back(fno.fname); + files.push_back((char*)fno.fname); } } @@ -251,7 +252,7 @@ void RadioSdManagerPage::build(FormWindow * window) for (auto name: directories) { auto b = new TextButton(window, grid.getLabelSlot(), name, [=]() -> uint8_t { std::string fullpath = currentPath + "/" + name; - f_chdir(fullpath.c_str()); + f_chdir((TCHAR*)fullpath.c_str()); window->clear(); build(window); return 0; @@ -303,11 +304,18 @@ void RadioSdManagerPage::build(FormWindow * window) // else if (isExtensionMatching(ext, BITMAPS_EXT)) { // // TODO // } - // else if (!strcasecmp(ext, TEXT_EXT)) { - // menu->addLine(STR_VIEW_TEXT, [=]() { - // // TODO - // }); - // } + else if (!strcasecmp(ext, TEXT_EXT)) { + menu->addLine(STR_VIEW_TEXT, [=]() { + static char lfn[FF_MAX_LFN + 1]; // TODO optimize that! + f_getcwd((TCHAR *)lfn, FF_MAX_LFN); + + auto textView = new ViewTextWindow(lfn, name); + textView->setCloseHandler([=]() { + //window->clear(); + rebuild(window); + }); + }); + } if (!READ_ONLY() && !strcasecmp(ext, FIRMWARE_EXT)) { if (isBootloader(name.data())) { menu->addLine(STR_FLASH_BOOTLOADER, [=]() { @@ -378,7 +386,6 @@ void RadioSdManagerPage::build(FormWindow * window) //window->clear(); rebuild(window); }); - // rebuild(window); }); }); menu->addLine(STR_DELETE_FILE, [=]() { f_unlink((const TCHAR*)getFullPath(name)); diff --git a/radio/src/gui/colorlcd/textedits.h b/radio/src/gui/colorlcd/textedits.h index 15e3e2e79..b8dddc97e 100644 --- a/radio/src/gui/colorlcd/textedits.h +++ b/radio/src/gui/colorlcd/textedits.h @@ -27,8 +27,8 @@ class ModelTextEdit: public TextEdit { public: - ModelTextEdit(Window * parent, const rect_t & rect, char * value, uint8_t length, LcdFlags windowFlags = 0): - TextEdit(parent, rect, value, length, windowFlags) + ModelTextEdit(Window * parent, const rect_t & rect, char * value, uint8_t length, LcdFlags windowFlags = 0, const char * extra_chars = nullptr): + TextEdit(parent, rect, value, length, windowFlags, extra_chars) { setChangeHandler([](){ storageDirty(EE_MODEL); @@ -39,11 +39,11 @@ class ModelTextEdit: public TextEdit class RadioTextEdit: public TextEdit { public: - RadioTextEdit(Window * parent, const rect_t & rect, char * value, uint8_t length, LcdFlags windowFlags = 0): - TextEdit(parent, rect, value, length, windowFlags) + RadioTextEdit(Window * parent, const rect_t & rect, char * value, uint8_t length, LcdFlags windowFlags = 0, const char * extra_chars = nullptr): + TextEdit(parent, rect, value, length, windowFlags, extra_chars) { setChangeHandler([](){ storageDirty(EE_GENERAL); }); } -}; \ No newline at end of file +}; diff --git a/radio/src/gui/colorlcd/themes/480_bitmaps.h b/radio/src/gui/colorlcd/themes/480_bitmaps.h index 2b1df160b..ea278241a 100644 --- a/radio/src/gui/colorlcd/themes/480_bitmaps.h +++ b/radio/src/gui/colorlcd/themes/480_bitmaps.h @@ -61,6 +61,12 @@ const uint8_t mask_model_special_functions[] = { const uint8_t mask_model_telemetry[] = { #include "mask_model_telemetry.lbm" }; +const uint8_t mask_menu_model_select[] = { +#include "mask_menu_model_select.lbm" //TODO: someone may want to make proper icon +}; +const uint8_t mask_model_select_category[] = { +#include "mask_model_select_category.lbm" //TODO: someone should make proper icon +}; const uint8_t mask_monitor[] = { #include "mask_monitor.lbm" }; diff --git a/radio/src/gui/colorlcd/themes/480_default.cpp b/radio/src/gui/colorlcd/themes/480_default.cpp index 77b5ac252..331c8b7b7 100644 --- a/radio/src/gui/colorlcd/themes/480_default.cpp +++ b/radio/src/gui/colorlcd/themes/480_default.cpp @@ -150,6 +150,8 @@ class Theme480: public OpenTxTheme loadMenuIcon(ICON_MODEL_SPECIAL_FUNCTIONS, mask_model_special_functions); loadMenuIcon(ICON_MODEL_LUA_SCRIPTS, mask_model_lua_scripts); loadMenuIcon(ICON_MODEL_TELEMETRY, mask_model_telemetry); + loadMenuIcon(ICON_MODEL_SELECT, mask_menu_model_select); + loadMenuIcon(ICON_MODEL_SELECT_CATEGORY, mask_model_select_category); loadMenuIcon(ICON_STATS, mask_menu_stats); loadMenuIcon(ICON_STATS_THROTTLE_GRAPH, mask_stats_throttle_graph); loadMenuIcon(ICON_STATS_TIMERS, mask_stats_timers); diff --git a/radio/src/gui/colorlcd/view_main_menu.cpp b/radio/src/gui/colorlcd/view_main_menu.cpp index 6c26e2f4f..dd3e37c65 100644 --- a/radio/src/gui/colorlcd/view_main_menu.cpp +++ b/radio/src/gui/colorlcd/view_main_menu.cpp @@ -52,7 +52,7 @@ ViewMainMenu::ViewMainMenu(Window* parent) : // "Tasks", 0, FOCUS_COLOR | FONT(XL) | CENTERED); // pos += title->height() + PAGE_LINE_SPACING; - carousel->addButton(ICON_MODEL, "Select\nModel", [=]() -> uint8_t { + carousel->addButton(ICON_MODEL_SELECT, "Select\nModel", [=]() -> uint8_t { deleteLater(); new ModelSelectMenu(); return 0; @@ -64,7 +64,7 @@ ViewMainMenu::ViewMainMenu(Window* parent) : return 0; }); - carousel->addButton(ICON_RADIO_TOOLS, "Model\nSettings", [=]() -> uint8_t { + carousel->addButton(ICON_MODEL, "Model\nSettings", [=]() -> uint8_t { deleteLater(); new ModelMenu(); return 0; diff --git a/radio/src/gui/colorlcd/view_text.cpp b/radio/src/gui/colorlcd/view_text.cpp new file mode 100644 index 000000000..c3381508d --- /dev/null +++ b/radio/src/gui/colorlcd/view_text.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * 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 "view_text.h" + +#include "gridlayout.h" +#include "menu.h" +#include "opentx.h" +#include "sdcard.h" + +#define CASE_EVT_KEY_NEXT_LINE \ + case EVT_ROTARY_RIGHT: \ + case EVT_KEY_BREAK(KEY_PGDN) +// case EVT_KEY_BREAK(KEY_DOWN) + +#define CASE_EVT_KEY_PREVIOUS_LINE \ + case EVT_ROTARY_LEFT: \ + case EVT_KEY_BREAK(KEY_PGUP): \ + case EVT_KEY_BREAK(KEY_UP) + +#define CASE_EVT_START \ + case EVT_ENTRY: \ + case EVT_KEY_BREAK(KEY_ENTER): \ + case EVT_KEY_BREAK(KEY_TELEM) + +void ViewTextWindow::extractNameSansExt() +{ + uint8_t nameLength; + uint8_t extLength; + + const char *ext = + getFileExtension(name.data(), 0, 0, &nameLength, &extLength); + extension = std::string(ext); + if (nameLength > TEXT_FILENAME_MAXLEN) nameLength = TEXT_FILENAME_MAXLEN; + + nameLength -= extLength; + name.substr(nameLength); +} + +#if READ_FILE_BY_LINE +void ViewTextWindow::buildBody(Window *window) +{ + GridLayout grid(window); + grid.spacer(); + int i; + + // assume average characte is 10 pixels wide, round the string length to tens. + // Font is not fixed width, so this is for the worst case... + const int maxLineLength = int(floor(window->width() / 10 / 10)) * 10 - 2; + window->setFocus(); + + FIL file; + if (FR_OK != + f_open(&file, (TCHAR *)fullPath.c_str(), FA_OPEN_EXISTING | FA_READ)) { + return; + } + readCount = 0; + longestLine = 0; + lastLine = false; + for (i = 0; i < TEXT_FILE_MAXSIZE && !lastLine; i++) { + lastLine = + sdReadTextLine(&file, reusableBuffer.viewText.lines[0], maxLineLength); + + new StaticText(window, grid.getSlot(), reusableBuffer.viewText.lines[0]); + grid.nextLine(); + } + + f_close(&file); + + window->setInnerWidth((longestLine + 4) * 10); + window->setInnerHeight(grid.getWindowHeight()); +} + +void ViewTextWindow::checkEvents() +{ + if (&body == Window::focusWindow) { + const int step = PAGE_LINE_HEIGHT + PAGE_LINE_SPACING; + coord_t currentPos = body.getScrollPositionY(); + coord_t deltaY; + event_t event = getWindowEvent(); + + if (event == EVT_ROTARY_LEFT || event == EVT_ROTARY_RIGHT) { + deltaY = ROTARY_ENCODER_SPEED() * step; + } else { + deltaY = step; + } + + switch (event) { + CASE_EVT_KEY_NEXT_LINE: + currentPos += deltaY; + break; + + CASE_EVT_KEY_PREVIOUS_LINE: + currentPos -= deltaY; + break; + + default: + Page::onEvent(event); + return; + } + body.setScrollPositionY(currentPos); + } + Page::checkEvents(); +} + +bool ViewTextWindow::sdReadTextLine(FIL *file, char line[], + const uint8_t maxLineLength) +{ + char c; + unsigned int sz; + uint8_t line_length = 0; + uint8_t escape = 0; + char escape_chars[4] = {0}; + int current_line = 0; + + memclear(line, maxLineLength + 1); + line[line_length++] = 0x20; + + for (uint8_t i = 0; i < maxLineLength && readCount < (int)TEXT_FILE_MAXSIZE; + ++i) { + if ((f_read(file, &c, 1, &sz) != FR_OK || !sz) && + line_length < maxLineLength) { + return true; + } + readCount++; + + if (c == '\n') { + ++current_line; + escape = 0; + return false; + } else if (c != '\r') { + if (c == '\\' && escape == 0) { + escape = 1; + continue; + } else if (c != '\\' && escape > 0 && escape < sizeof(escape_chars)) { + escape_chars[escape - 1] = c; + if (escape == 2 && !strncmp(escape_chars, "up", 2)) { + c = CHAR_UP; + } else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) { + c = CHAR_DOWN; + } else if (escape == 3) { + int val = atoi(escape_chars); + if (val >= 200 && val < 225) { + c = '\200' + val - 200; + } + } else { + escape++; + continue; + } + } else if (c == '~') { + c = 'z' + 1; + } else if (c == '\t') { + c = 0x1D; // tab + } + escape = 0; + line[line_length++] = c; + if (longestLine < line_length) longestLine = line_length; + } + } + if (c != '\n') { + current_line += 1; + return false; + } + + return false; +} +#else + +void ViewTextWindow::buildBody(Window *window) +{ + GridLayout grid(window); + grid.spacer(); + int i; + + // assume average characte is 10 pixels wide, round the string length to tens. + // Font is not fixed width, so this is for the worst case... + maxLineLength = int(floor(window->width() / 10 / 10)) * 10 - 2; + maxScreenLines = window->height() / (PAGE_LINE_HEIGHT + PAGE_LINE_SPACING); + window->setFocus(); + readLinesCount = 0; + lastLoadedLine = 0; + + lines = new char*[maxScreenLines]; + for(i = 0 ; i < maxScreenLines; i++) + { + lines[i] = new char[maxLineLength + 1]; + memclear(lines[i], maxLineLength + 1); + } + + longestLine = 0; + loadFirstScreen(); + + if(isInSetup == true) + { + textBottom = false; + while(!textBottom) + { + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + textVerticalOffset += 10; + } + maxPos = (maxLines - maxScreenLines) * (PAGE_LINE_HEIGHT + PAGE_LINE_SPACING); + if(maxPos < body.getRect().h) maxPos = body.getRect().h; + } + + isInSetup = false; + loadFirstScreen(); + + for (i = 0; i < maxScreenLines; i++) { + new DynamicText(window, grid.getSlot(), [=]() { + std::string str = (lines[i][0]) ? std::string(lines[i]) : std::string(" "); + return std::string(str); + }); + grid.nextLine(); + } +} + +#if defined(HARDWARE_TOUCH) +bool ViewTextWindow::onTouchSlide(coord_t x, coord_t y, coord_t startX, + coord_t startY, coord_t slideX, + coord_t slideY) +{ + if (&body == Window::focusWindow) { + const int step = PAGE_LINE_HEIGHT + PAGE_LINE_SPACING; + int deltaY = -slideY; + int lineStep = deltaY / step; + + textVerticalOffset += lineStep; + if (textVerticalOffset < 0) textVerticalOffset = 0; + + // if (textBottom && lineStep > 0) textVerticalOffset -= lineStep; + if(textVerticalOffset > maxLines) textVerticalOffset = maxLines; + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + } + return Page::onTouchSlide(x, y, startX, startY, slideX, slideY); +} +#endif + +void ViewTextWindow::checkEvents() +{ + if (&body == Window::focusWindow) { + const int step = PAGE_LINE_HEIGHT + PAGE_LINE_SPACING; + coord_t deltaY; + event_t event = getWindowEvent(); + + if (event == EVT_ROTARY_LEFT || event == EVT_ROTARY_RIGHT) { + deltaY = ROTARY_ENCODER_SPEED() * step; + } else { + deltaY = step; + } + + int lineStep = deltaY / step; + if(lineStep > (maxScreenLines >> 1)) lineStep = maxScreenLines >> 1; +/* + if(event) { + sprintf(lines[0], "Received event %d", event); + if(!readLinesCount) + readLinesCount = 1; + Page::onEvent(event); + return; + } +*/ + switch (event) { + CASE_EVT_START: + textVerticalOffset = 0; + readLinesCount = 0; + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + break; + + CASE_EVT_KEY_NEXT_LINE: + if(textBottom && textVerticalOffset) //(textVerticalOffset + maxScreenLines - 1 >= readLinesCount) + break; + else { + textVerticalOffset += lineStep; + if(textVerticalOffset > maxLines) textVerticalOffset = maxLines; + } + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + break; + + CASE_EVT_KEY_PREVIOUS_LINE: + if (textVerticalOffset == 0) + break; + else { + textVerticalOffset -= lineStep; + if(textVerticalOffset < 0) textVerticalOffset = 0; + } + + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + break; + + default: + Page::onEvent(event); + break; + } + } + Page::checkEvents(); +} + +void ViewTextWindow::loadFirstScreen() +{ + textVerticalOffset = 0; + readLinesCount = 0; + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); +} + +void ViewTextWindow::sdReadTextFileBlock(const char *filename, int &lines_count) +{ + FIL file; + int result; + char c; + unsigned int sz = 0; + int line_length = 1; + uint8_t escape = 0; + char escape_chars[4] = {0}; + int current_line = 0; + textBottom = false; + + for(int i = 0; i < maxScreenLines; i++) { + memclear(lines[i], maxLineLength + 1); + lines[i][0] = ' '; + } + + result = f_open(&file, (TCHAR *)filename, FA_OPEN_EXISTING | FA_READ); + if (result == FR_OK) { + for (int i = 0; i < TEXT_FILE_MAXSIZE && + f_read(&file, &c, 1, &sz) == FR_OK && sz == 1 && + (lines_count == 0 || + current_line - textVerticalOffset < maxScreenLines); + i++) { + if (c == '\n' || line_length >= maxLineLength) { + ++current_line; + line_length = 1; + escape = 0; + } + if (c != '\r' && c != '\n' && current_line >= textVerticalOffset && + current_line - textVerticalOffset < maxScreenLines && + line_length < maxLineLength) { + if (c == '\\' && escape == 0) { + escape = 1; + continue; + } else if (c != '\\' && escape > 0 && escape < sizeof(escape_chars)) { + escape_chars[escape - 1] = c; + if (escape == 2 && !strncmp(escape_chars, "up", 2)) { + c = CHAR_UP; + } else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) { + c = CHAR_DOWN; + } else if (escape == 3) { + int val = atoi(escape_chars); + if (val >= 200 && val < 225) { + c = '\200' + val - 200; + } + } else { + escape++; + continue; + } + } + else if (c == '~') { + c = 'z' + 1; + } else if (c == '\t') { + c = 0x1D; // tab + } + escape = 0; + + lines[current_line - textVerticalOffset][line_length++] = c; + if (longestLine < line_length) longestLine = line_length; + } else if(current_line < textVerticalOffset) { + ++line_length; + } + } + if (c != '\n') { + ++current_line; + } + + if (f_eof(&file)) { + textBottom = true; + if (isInSetup) maxLines = current_line; + } + + f_close(&file); + } + + if(lastLoadedLine < textVerticalOffset) + lastLoadedLine = textVerticalOffset; + + if (lines_count == 0) { + lines_count = current_line; + } +} + +void ViewTextWindow::drawVerticalScrollbar(BitmapBuffer *dc) +{ + int readPos = textVerticalOffset * (PAGE_LINE_HEIGHT + PAGE_LINE_SPACING); + + if(readPos < header.getRect().h << 1) readPos = header.getRect().h << 1; + + coord_t yofs = divRoundClosest(body.getRect().h * readPos, maxPos); + coord_t yhgt = divRoundClosest(body.getRect().h * body.getRect().h, maxPos); + if (yhgt < 15) yhgt = 15; + if (yhgt + yofs > maxPos) yhgt = maxPos - yofs; + dc->drawSolidFilledRect(body.getRect().w - SCROLLBAR_WIDTH, yofs, + SCROLLBAR_WIDTH, yhgt, SCROLLBAR_COLOR); +} + +#endif + +#include "datastructs.h" + +void readModelNotes() +{ + LED_ERROR_BEGIN(); + + std::string modelNotesName(g_model.header.name); + modelNotesName.append(TEXT_EXT); + const char buf[] = {MODELS_PATH}; + f_chdir((TCHAR*)buf); + new ViewTextWindow(std::string(buf), modelNotesName, ICON_MODEL); + + LED_ERROR_END(); +} \ No newline at end of file diff --git a/radio/src/gui/colorlcd/view_text.h b/radio/src/gui/colorlcd/view_text.h new file mode 100644 index 000000000..e73938ef1 --- /dev/null +++ b/radio/src/gui/colorlcd/view_text.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * 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. + */ + +#pragma once + +#include "page.h" +#include "static.h" +#include "lcd.h" +#include "menus.h" +#include "ff.h" + +#define READ_FILE_BY_LINE 0 + +constexpr uint16_t TEXT_FILE_MAXSIZE = 20480; + +class ViewTextWindow : public Page +{ + public: + ViewTextWindow(const std::string iPath, const std::string iName, unsigned int icon = ICON_RADIO_SD_MANAGER) : + Page(icon), + path(std::move(iPath)), + name(std::move(iName)), + icon(icon) + { + fullPath = path + std::string("/") + name; + extractNameSansExt(); + lines = nullptr; + lastLoadedLine = 0; + maxPos = 0; + maxLines = 0; + isInSetup = true; + //body.setWindowFlags(FORWARD_SCROLL); + header.setWindowFlags(NO_SCROLLBAR); + + buildHeader(&header); + buildBody(&body); + }; + +#if READ_FILE_BY_LINE + bool sdReadTextLine(FIL* file, char lines[], + const uint8_t lineLength = LCD_COLS); +#else + void sdReadTextFileBlock(const char * filename, int& lines_count); + void loadFirstScreen(void); + #if defined(HARDWARE_TOUCH) + bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) override; +#endif + void drawVerticalScrollbar(BitmapBuffer * dc); + + ~ViewTextWindow() + { + if(lines != nullptr) { + for(int i = 0; i < maxScreenLines; i++) + { + delete[] lines[i]; + } + delete[] lines; + } + } + + void paint(BitmapBuffer* dc) + { + Page::paint(dc); + drawVerticalScrollbar(dc); + } +#endif + virtual void checkEvents(); + +#if defined(DEBUG_WINDOWS) + std::string getName() const override { return "ViewTextWindow"; }; +#endif + + protected: + std::string path; + std::string name; + std::string fullPath; + std::string extension; + unsigned int icon; + + bool lastLine; + uint16_t readCount; + int longestLine; + +#if !READ_FILE_BY_LINE +char** lines = nullptr; +//char lines[15][80]; +int maxScreenLines; +int maxLineLength; +int textVerticalOffset; +int readLinesCount; +int lastLoadedLine; +int maxPos; +int maxLines; +bool textBottom; +bool isInSetup; +#endif + + void extractNameSansExt(void); + void buildBody(Window *window); + void buildHeader(Window *window) + { + new StaticText(window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + 10, + LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT}, + name, 0, MENU_HIGHLIGHT_COLOR); + }; +}; + +void readModelNotes(); \ No newline at end of file diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index a4e1a6176..77332df11 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -26,6 +26,7 @@ // #include "shutdown_animation.h" #include "radio_calibration.h" #include "view_main.h" + #include "view_text.h" #endif #if defined(PCBSKY9X) @@ -749,13 +750,9 @@ void checkAll() disableVBatBridge(); #endif -#if defined(COLORLCD) -#warning "Model notes missing" -#else if (g_model.displayChecklist && modelHasNotes()) { readModelNotes(); } -#endif #if defined(MULTIMODULE) checkMultiLowPower(); diff --git a/radio/src/pulses/pulses.cpp b/radio/src/pulses/pulses.cpp index 7ac4a69e8..779771c04 100755 --- a/radio/src/pulses/pulses.cpp +++ b/radio/src/pulses/pulses.cpp @@ -347,7 +347,7 @@ bool setupPulsesExternalModule(uint8_t protocol) case PROTOCOL_CHANNELS_PXX2_LOWSPEED: extmodulePulsesData.pxx2.setupFrame(EXTERNAL_MODULE); #if defined(PCBSKY9X) - sheduleNextMixerCalculation(EXTERNAL_MODULE, PXX2_NO_HEARTBEAT_PERIOD); + scheduleNextMixerCalculation(EXTERNAL_MODULE, PXX2_NO_HEARTBEAT_PERIOD); #endif return true; #endif @@ -457,12 +457,9 @@ static void enablePulsesInternalModule(uint8_t protocol) case PROTOCOL_CHANNELS_PXX1_PULSES: intmodulePxx1PulsesStart(); #if defined(INTMODULE_HEARTBEAT) - // use backup trigger (1 ms later) init_intmodule_heartbeat(); - mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD + 1000/*us*/); -#else - mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD); #endif + mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD); break; #endif @@ -470,12 +467,9 @@ static void enablePulsesInternalModule(uint8_t protocol) case PROTOCOL_CHANNELS_PXX1_SERIAL: intmodulePxx1SerialStart(); #if defined(INTMODULE_HEARTBEAT) - // use backup trigger (1 ms later) init_intmodule_heartbeat(); - mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD + 1000/*us*/); -#else - mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD); #endif + mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD); break; #endif @@ -487,10 +481,8 @@ static void enablePulsesInternalModule(uint8_t protocol) #if defined(INTMODULE_HEARTBEAT) // use backup trigger (1 ms later) init_intmodule_heartbeat(); - mixerSchedulerSetPeriod(INTERNAL_MODULE, PXX2_MAX_HEARTBEAT_PERIOD); -#else - mixerSchedulerSetPeriod(INTERNAL_MODULE, PXX2_NO_HEARTBEAT_PERIOD); #endif + mixerSchedulerSetPeriod(INTERNAL_MODULE, PXX2_PERIOD); break; #endif @@ -522,12 +514,6 @@ bool setupPulsesInternalModule(uint8_t protocol) #if defined(HARDWARE_INTERNAL_MODULE) && defined(PXX1) && !defined(INTMODULE_USART) case PROTOCOL_CHANNELS_PXX1_PULSES: intmodulePulsesData.pxx.setupFrame(INTERNAL_MODULE); -#if defined(INTMODULE_HEARTBEAT) - mixerSchedulerResetTimer(); - mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD + 1000 /* backup */); -#else - mixerSchedulerSetPeriod(INTERNAL_MODULE, INTMODULE_PXX1_SERIAL_PERIOD); -#endif return true; #endif @@ -545,12 +531,7 @@ bool setupPulsesInternalModule(uint8_t protocol) mixerSchedulerSetPeriod(INTERNAL_MODULE, PXX2_TOOLS_PERIOD); } else { -#if defined(INTMODULE_HEARTBEAT) - mixerSchedulerResetTimer(); - mixerSchedulerSetPeriod(INTERNAL_MODULE, PXX2_MAX_HEARTBEAT_PERIOD); -#else - mixerSchedulerSetPeriod(INTERNAL_MODULE, PXX2_NO_HEARTBEAT_PERIOD); -#endif + mixerSchedulerSetPeriod(INTERNAL_MODULE, PXX2_PERIOD); } return result; } @@ -559,8 +540,6 @@ bool setupPulsesInternalModule(uint8_t protocol) #if defined(PCBTARANIS) && defined(INTERNAL_MODULE_PPM) case PROTOCOL_CHANNELS_PPM: setupPulsesPPMInternalModule(); - // probably useless, as the interval did not change since "enable" function - mixerSchedulerSetPeriod(INTERNAL_MODULE, PPM_PERIOD(INTERNAL_MODULE)); return true; #endif @@ -572,7 +551,6 @@ bool setupPulsesInternalModule(uint8_t protocol) #endif default: - //mixerSchedulerSetPeriod(INTERNAL_MODULE, 10000 /*us*/); // used for USB sim for example return false; } } diff --git a/radio/src/pulses/pxx.h b/radio/src/pulses/pxx.h index 90956fe35..42e7c6c28 100644 --- a/radio/src/pulses/pxx.h +++ b/radio/src/pulses/pxx.h @@ -30,7 +30,12 @@ #define PXX2_LOWSPEED_BAUDRATE 230400 #define PXX2_HIGHSPEED_BAUDRATE 450000 #define PXX2_NO_HEARTBEAT_PERIOD 4000/*us*/ -#define PXX2_MAX_HEARTBEAT_PERIOD (7000 + 1000)/*us 7ms frame rate + 1ms heartbeat backup*/ +#define PXX2_MAX_HEARTBEAT_PERIOD (9000 + 1000)/*us longest period (isrm/accst) + 1ms heartbeat backup*/ +#if defined(INTMODULE_HEARTBEAT) + #define PXX2_PERIOD PXX2_MAX_HEARTBEAT_PERIOD +#else + #define PXX2_PERIOD PXX2_NO_HEARTBEAT_PERIOD +#endif #define PXX2_TOOLS_PERIOD 1000/*us*/ #define PXX2_FRAME_MAXLENGTH 64 @@ -40,10 +45,20 @@ #if defined(PXX_FREQUENCY_HIGH) #define INTMODULE_PXX1_SERIAL_BAUDRATE 450000 - #define INTMODULE_PXX1_SERIAL_PERIOD 4000/*us*/ + #if defined(INTMODULE_HEARTBEAT) + // use backup trigger (1 ms later) + #define INTMODULE_PXX1_SERIAL_PERIOD (4000 + 1000)/*us*/ + #else + #define INTMODULE_PXX1_SERIAL_PERIOD 4000/*us*/ + #endif #else #define INTMODULE_PXX1_SERIAL_BAUDRATE 115200 - #define INTMODULE_PXX1_SERIAL_PERIOD 9000/*us*/ + #if defined(INTMODULE_HEARTBEAT) + // use backup trigger (1 ms later) + #define INTMODULE_PXX1_SERIAL_PERIOD (9000 + 1000)/*us*/ + #else + #define INTMODULE_PXX1_SERIAL_PERIOD 9000/*us*/ + #endif #endif // Used by the Sky9x family boards diff --git a/radio/src/targets/common/arm/stm32/heartbeat_driver.cpp b/radio/src/targets/common/arm/stm32/heartbeat_driver.cpp index 269b40854..ee00a6db3 100644 --- a/radio/src/targets/common/arm/stm32/heartbeat_driver.cpp +++ b/radio/src/targets/common/arm/stm32/heartbeat_driver.cpp @@ -79,6 +79,7 @@ void check_intmodule_heartbeat() #endif EXTI_ClearITPendingBit(INTMODULE_HEARTBEAT_EXTI_LINE); + mixerSchedulerResetTimer(); mixerSchedulerISRTrigger(); } } diff --git a/radio/src/targets/horus/CMakeLists.txt b/radio/src/targets/horus/CMakeLists.txt index bfbb3153e..c01057e9c 100644 --- a/radio/src/targets/horus/CMakeLists.txt +++ b/radio/src/targets/horus/CMakeLists.txt @@ -183,6 +183,7 @@ endif() if(FLYSKY_HALL_STICKS) add_definitions(-DFLYSKY_HALL_STICKS) + add_definitions(-DFLYSKY_HALL_STICKS_REVERSE) set(AUX_SERIAL OFF) set(FLYSKY_HALL_STICKS_DRIVER flyskyHallStick_driver.cpp) endif() diff --git a/radio/src/targets/horus/hal.h b/radio/src/targets/horus/hal.h index 752ed29d4..8d7be0da5 100644 --- a/radio/src/targets/horus/hal.h +++ b/radio/src/targets/horus/hal.h @@ -758,10 +758,6 @@ // Flysky Hall Stick #if defined(FLYSKY_HALL_STICKS) - #if defined (RADIO_TX16S) - #define FLYSKY_HALL_STICKS_REVERSE - #endif - #define FLYSKY_HALL_SERIAL_USART UART4 #define FLYSKY_HALL_SERIAL_GPIO GPIOA #define FLYSKY_HALL_DMA_Channel DMA_Channel_4 @@ -784,7 +780,6 @@ #define FLYSKY_HALL_RX_DMA_Stream_IRQHandler DMA1_Stream2_IRQHandler #define FLYSKY_HALL_TX_DMA_Stream_IRQHandler DMA1_Stream4_IRQHandler - #endif // Internal Module diff --git a/radio/src/targets/horus/tp_gt911.cpp b/radio/src/targets/horus/tp_gt911.cpp index d176b5b71..8dd01b49b 100644 --- a/radio/src/targets/horus/tp_gt911.cpp +++ b/radio/src/targets/horus/tp_gt911.cpp @@ -39,9 +39,9 @@ const uint8_t TOUCH_GT911_Cfg[] = { 0x50, // 0x8053 Screen touch level 0x3C, // 0x8054 Screen touch leave 0x03, // 0x8055 Low power control - 0x05, // 0x8056 Refresh rate - 0x00, // 0x8057 X threshold - 0x00, // 0x8058 Y threshold + 0x0F, // 0x8056 Refresh rate + 0x01, // 0x8057 X threshold + 0x01, // 0x8058 Y threshold 0x00, // 0x8059 Reserved 0x00, // 0x805A Reserved 0x00, // 0x805B Space (top, bottom) @@ -230,9 +230,9 @@ const uint8_t TOUCH_GT911_Cfg[] = 0x5A, // 0x8053 Screen touch level 0x3C, // 0x8054 Screen touch leave 0x03, // 0x8055 Low power control - 0x05, // 0x8056 Refresh rate - 0x00, // 0x8057 X threshold - 0x00, // 0x8058 Y threshold + 0x0F, // 0x8056 Refresh rate + 0x01, // 0x8057 X threshold + 0x01, // 0x8058 Y threshold 0x00, // 0x8059 Reserved 0x00, // 0x805A Reserved 0x11, // 0x805B Space (top, bottom) @@ -403,9 +403,11 @@ const uint8_t TOUCH_GT911_Cfg[] = #endif -uint8_t touchGT911Flag = 0; -uint8_t touchEventOccured = 0; +bool touchGT911Flag = false; +volatile static bool touchEventOccured = false; struct TouchData touchData; +uint16_t touchGT911fwver = 0; +uint32_t touchGT911hiccups = 0; static void TOUCH_AF_ExtiStop(void) { @@ -415,7 +417,7 @@ static void TOUCH_AF_ExtiStop(void) EXTI_StructInit(&EXTI_InitStructure); EXTI_InitStructure.EXTI_Line = TOUCH_INT_EXTI_LINE1; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; - EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; + EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = DISABLE; EXTI_Init(&EXTI_InitStructure); @@ -436,7 +438,7 @@ static void TOUCH_AF_ExtiConfig(void) EXTI_StructInit(&EXTI_InitStructure); EXTI_InitStructure.EXTI_Line = TOUCH_INT_EXTI_LINE1; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; - EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; + EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); @@ -484,6 +486,8 @@ void TOUCH_AF_INT_Change(void) void I2C_Init() { + TRACE("I2C Init"); + I2C_DeInit(I2C); GPIO_InitTypeDef GPIO_InitStructure; @@ -508,6 +512,15 @@ void I2C_Init() GPIO_Init(I2C_GPIO, &GPIO_InitStructure); } +bool I2C_WaitFlag(uint32_t flag) +{ + uint32_t timeout = I2C_TIMEOUT_MAX; + while (!I2C_GetFlagStatus(I2C, flag)) { + if ((timeout--) == 0) return false; + } + return true; +} + bool I2C_WaitEvent(uint32_t event) { uint32_t timeout = I2C_TIMEOUT_MAX; @@ -529,33 +542,55 @@ bool I2C_WaitEventCleared(uint32_t event) uint8_t I2C_GT911_WriteRegister(uint16_t reg, uint8_t * buf, uint8_t len) { if (!I2C_WaitEventCleared(I2C_FLAG_BUSY)) + { + TRACE("I2C WRITE ERROR: busy timeout"); return false; + } I2C_GenerateSTART(I2C, ENABLE); if (!I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) + { + TRACE("I2C WRITE ERROR: could not issue start"); return false; + } I2C_Send7bitAddress(I2C, GT_CMD_WR, I2C_Direction_Transmitter); if (!I2C_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) + { + TRACE("I2C WRITE ERROR: could not send device address"); return false; + } I2C_SendData(I2C, (uint8_t)((reg & 0xFF00) >> 8)); if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTING)) + { + TRACE("I2C WRITE ERROR: could not send register high address"); return false; + } + I2C_SendData(I2C, (uint8_t)(reg & 0x00FF)); if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTING)) + { + TRACE("I2C WRITE ERROR: could not send register low address"); return false; + } /* While there is data to be written */ while (len--) { I2C_SendData(I2C, *buf); if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTING)) + { + TRACE("I2C WRITE ERROR: could not send data with %u bytes remaining", len+1 ); return false; + } buf++; } if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) + { + TRACE("I2C WRITE ERROR: waiting for BTF failed."); return false; + } I2C_GenerateSTOP(I2C, ENABLE); return true; @@ -564,67 +599,184 @@ uint8_t I2C_GT911_WriteRegister(uint16_t reg, uint8_t * buf, uint8_t len) bool I2C_GT911_ReadRegister(u16 reg, uint8_t * buf, uint8_t len) { if (!I2C_WaitEventCleared(I2C_FLAG_BUSY)) + { + TRACE("I2C READ ERROR: busy timeout"); return false; + } I2C_GenerateSTART(I2C, ENABLE); if (!I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) + { + TRACE("I2C READ ERROR: could not issue first step start"); return false; + } I2C_Send7bitAddress(I2C, GT_CMD_WR, I2C_Direction_Transmitter); if (!I2C_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) + { + TRACE("I2C READ ERROR: could not send first step device address"); return false; + } I2C_SendData(I2C, (uint8_t)((reg & 0xFF00) >> 8)); if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTING)) + { + TRACE("I2C READ ERROR: could not send register high address"); return false; + } + I2C_SendData(I2C, (uint8_t)(reg & 0x00FF)); if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) + { + TRACE("I2C READ ERROR: could not send register low address"); return false; + } + + // According to GP911 data sheet, repeated start is not listed, so we do full stop. + // See chapter 6.1.c, page 12, diagram for reading data + I2C_GenerateSTOP(I2C, ENABLE); + if (!I2C_WaitEventCleared(I2C_FLAG_BUSY)) + { + TRACE("I2C READ ERROR: timeout waiting for first stop"); + return false; + } + + // Enable Acknowledgement, clear POS flag + I2C_AcknowledgeConfig(I2C, ENABLE); + I2C_NACKPositionConfig(I2C, I2C_NACKPosition_Current); I2C_GenerateSTART(I2C, ENABLE); if (!I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT)) + { + TRACE("I2C READ ERROR: could not issue second step start"); return false; + } I2C_Send7bitAddress(I2C, GT_CMD_RD, I2C_Direction_Receiver); if (!I2C_WaitEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) + { + TRACE("I2C READ ERROR: could not send second step device address"); return false; - - if (len > 1) { - I2C_AcknowledgeConfig(I2C, ENABLE); } - while (len) { - if (len == 1) { - I2C_AcknowledgeConfig(I2C, DISABLE); - } - if (!I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_RECEIVED)) + if (len == 1) { + // Clear Ack bit + I2C_AcknowledgeConfig(I2C, DISABLE); + + // EV6_1 -- must be atomic -- Clear ADDR, generate STOP + __disable_irq(); + (void) I2C->SR2; + I2C_GenerateSTOP(I2C,ENABLE); + __enable_irq(); + + // Receive data EV7 + if(!I2C_WaitFlag(I2C_FLAG_RXNE)) { + TRACE("I2C READ ERROR: timeout waiting for RxNE"); + I2C_Init(); return false; + } + *buf++ = I2C_ReceiveData(I2C); - len--; + } + else if (len == 2) { + + // Set POS flag + I2C_NACKPositionConfig(I2C, I2C_NACKPosition_Next); + + // EV6_1 -- must be atomic and in this order + __disable_irq(); + (void) I2C->SR2; // Clear ADDR flag + I2C_AcknowledgeConfig(I2C, DISABLE); // Clear Ack bit + __enable_irq(); + + // EV7_3 -- Wait for BTF, program stop, read data twice + if (!I2C_WaitFlag(I2C_FLAG_BTF)) { + TRACE("I2C READ ERROR: timeout waiting for BTF"); + I2C_Init(); + return false; + } + + __disable_irq(); + I2C_GenerateSTOP(I2C,ENABLE); + *buf++ = I2C->DR; + __enable_irq(); + + *buf++ = I2C->DR; + } + else { + + (void) I2C->SR2; // Clear ADDR flag + while (len-- != 3) { + // EV7 -- cannot guarantee 1 transfer completion time, wait for BTF + // instead of RXNE + if (!I2C_WaitFlag(I2C_FLAG_BTF)) { + TRACE("I2C READ ERROR: timeout waiting for BTF"); + I2C_Init(); + return false; + } + + *buf++ = I2C_ReceiveData(I2C); + } + + // Data N-2 in DR, data N-1 in shift register, + // SCL stretched low until data N-2 is read + if (!I2C_WaitFlag(I2C_FLAG_BTF)) { + TRACE("I2C READ ERROR: timeout waiting for BTF"); + I2C_Init(); + return false; + } + + I2C_AcknowledgeConfig(I2C, DISABLE); // clear ack bit + *buf++ = I2C_ReceiveData(I2C); // receive byte N-2 + + // Wait for BTF, program stop, read data twice + if (!I2C_WaitFlag(I2C_FLAG_BTF)) { + TRACE("I2C READ ERROR: timeout waiting for BTF"); + I2C_Init(); + return false; + } + + __disable_irq(); + I2C_GenerateSTOP(I2C,ENABLE); + *buf++ = I2C->DR; + __enable_irq(); + + *buf++ = I2C->DR; } - I2C_GenerateSTOP(I2C, ENABLE); return true; } -uint8_t I2C_GT911_SendConfig(uint8_t mode) +bool I2C_GT911_SendConfig(void) { uint8_t buf[2]; uint8_t i = 0; buf[0] = 0; - buf[1] = mode; + buf[1] = 1; + bool bResult = true; + for (i = 0; i < sizeof(TOUCH_GT911_Cfg); i++) buf[0] += TOUCH_GT911_Cfg[i];//check sum buf[0] = (~buf[0]) + 1; - I2C_GT911_WriteRegister(GT_CFGS_REG, (uint8_t *) TOUCH_GT911_Cfg, sizeof(TOUCH_GT911_Cfg));// - I2C_GT911_WriteRegister(GT_CHECK_REG, buf, 2);//write checksum - return 0; + if (!I2C_GT911_WriteRegister(GT_CFGS_REG, (uint8_t *) TOUCH_GT911_Cfg, sizeof(TOUCH_GT911_Cfg))) + { + TRACE("GT911 ERROR: write config failed"); + bResult = false; + } + + if (!I2C_GT911_WriteRegister(GT_CHECK_REG, buf, 2)) //write checksum + { + TRACE("GT911 ERROR: write config checksum failed"); + bResult = false; + } + return bResult; } void touchPanelDeInit(void) { TOUCH_AF_ExtiStop(); + touchGT911Flag = false; } bool touchPanelInit(void) @@ -656,29 +808,55 @@ bool touchPanelInit(void) delay_ms(50); TRACE("Reading Touch registry"); - I2C_GT911_ReadRegister(GT_PID_REG, tmp, 4); + if (!I2C_GT911_ReadRegister(GT_PID_REG, tmp, 4)) + { + TRACE("GT911 ERROR: Product ID read failed"); + } if (strcmp((char *) tmp, "911") == 0) //ID==9147 { TRACE("GT911 chip detected"); tmp[0] = 0X02; - I2C_GT911_WriteRegister(GT_CTRL_REG, tmp, 1); - I2C_GT911_ReadRegister(GT_CFGS_REG, tmp, 1); + if (!I2C_GT911_WriteRegister(GT_CTRL_REG, tmp, 1)) + { + TRACE("GT911 ERROR: write to control register failed"); + } + if (!I2C_GT911_ReadRegister(GT_CFGS_REG, tmp, 1)) + { + TRACE("GT911 ERROR: configuration register read failed"); + } - TRACE("Chip config Ver:%x\r\n", tmp[0]); - if (tmp[0] < GT911_CFG_NUMER) //Config ver + TRACE("Chip config Ver:%x", tmp[0]); + if (tmp[0] <= GT911_CFG_NUMER) //Config ver { TRACE("Sending new config %d", GT911_CFG_NUMER); - I2C_GT911_SendConfig(1); + if (!I2C_GT911_SendConfig()) + { + TRACE("GT911 ERROR: sending configration failed"); + } + } + + if (!I2C_GT911_ReadRegister(GT911_FIRMWARE_VERSION_REG, tmp, 2)) + { + TRACE("GT911 ERROR: reading firmware version failed"); + } + else + { + touchGT911fwver = (tmp[1] << 8) + tmp[0]; + TRACE("GT911 FW version: %u", touchGT911fwver); } delay_ms(10); tmp[0] = 0X00; - I2C_GT911_WriteRegister(GT_CTRL_REG, tmp, 1); //end reset + if (!I2C_GT911_WriteRegister(GT_CTRL_REG, tmp, 1)) //end reset + { + TRACE("GT911 ERROR: write to control register failed"); + } touchGT911Flag = true; TOUCH_AF_ExtiConfig(); + return true; } TRACE("GT911 chip NOT FOUND"); @@ -686,6 +864,7 @@ bool touchPanelInit(void) } } +#if defined(SIMU) || defined(SEMIHOSTING) || defined(DEBUG) static const char* event2str(uint8_t ev) { switch(ev){ @@ -703,20 +882,25 @@ static const char* event2str(uint8_t ev) return "UNKNOWN"; } } +#endif void touchPanelRead() { uint8_t state = 0; - if (!touchEventOccured) - return; + // if (!touchEventOccured) + // return; - touchEventOccured = 0; + // touchEventOccured = false; uint32_t startReadStatus = RTOS_GET_MS(); do { if (!I2C_GT911_ReadRegister(GT911_READ_XY_REG, &state, 1)) { - TRACE("GT911 I2C read error"); + //ledRed(); + touchGT911hiccups++; + TRACE("GT911 I2C read XY error"); + touchPanelDeInit(); + touchPanelInit(); return; } @@ -734,7 +918,11 @@ void touchPanelRead() if (pointsCount > 0 && pointsCount <= GT911_MAX_TP) { if (!I2C_GT911_ReadRegister(GT911_READ_XY_REG + 1, touchData.data, pointsCount * sizeof(TouchPoint))) { - TRACE("GT911 I2C read error"); + //ledRed(); + touchGT911hiccups++; + TRACE("GT911 I2C data read error"); + touchPanelDeInit(); + touchPanelInit(); return; } if (touchState.event == TE_NONE || touchState.event == TE_UP || @@ -765,7 +953,10 @@ void touchPanelRead() } uint8_t zero = 0; - I2C_GT911_WriteRegister(GT911_READ_XY_REG, &zero, 1); + if (!I2C_GT911_WriteRegister(GT911_READ_XY_REG, &zero, 1)) + { + TRACE("GT911 ERROR: clearing XY register failed"); + } TRACE("touch event = %s", event2str(touchState.event)); } @@ -777,7 +968,7 @@ extern "C" void TOUCH_INT_EXTI_IRQHandler1(void) // on touch turn the light on resetBacklightTimeout(); } - touchEventOccured = 1; + touchEventOccured = true; EXTI_ClearITPendingBit(TOUCH_INT_EXTI_LINE1); } } diff --git a/radio/src/targets/horus/tp_gt911.h b/radio/src/targets/horus/tp_gt911.h index 1a3371d31..0651dc1b6 100644 --- a/radio/src/targets/horus/tp_gt911.h +++ b/radio/src/targets/horus/tp_gt911.h @@ -22,13 +22,15 @@ #define HAS_TOUCH_PANEL() touchGT911Flag == true -extern uint8_t touchGT911Flag; +extern bool touchGT911Flag; +extern uint16_t touchGT911fwver; +extern uint32_t touchGT911hiccups; extern bool touchPanelInit(); void touchPanelRead(); bool touchPanelEventOccured(); -#define GT911_TIMEOUT 20 // 20ms +#define GT911_TIMEOUT 3 // 3ms #define GT911_MAX_TP 5 #define GT911_CFG_NUMER 0x6C diff --git a/radio/src/targets/horus/x12s_adc_driver.cpp b/radio/src/targets/horus/x12s_adc_driver.cpp index 2833c4620..b8127bddc 100644 --- a/radio/src/targets/horus/x12s_adc_driver.cpp +++ b/radio/src/targets/horus/x12s_adc_driver.cpp @@ -128,6 +128,8 @@ void adcInit() ADC_DMA_Stream->NDTR = 2; ADC_DMA_Stream->FCR = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH_0; + ADC->CCR = ADC_CCR_VBATE; // Enable RTC coin cell sampling bridge + ADC1->CR1 = ADC_CR1_SCAN; ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_DMA | ADC_CR2_DDS; ADC1->SQR1 = (1 - 1) << 20; diff --git a/radio/src/targets/nv14/touch_driver.cpp b/radio/src/targets/nv14/touch_driver.cpp index b11767c20..f92d4c52a 100644 --- a/radio/src/targets/nv14/touch_driver.cpp +++ b/radio/src/targets/nv14/touch_driver.cpp @@ -21,7 +21,7 @@ #include "opentx.h" #include "touch_driver.h" -static bool touchEventOccured; +volatile static bool touchEventOccured; #define FT6x06_MAX_INSTANCE 1 diff --git a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.2.0/Libraries/STM32_USB_OTG_Driver/src/usb_core.c b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.2.0/Libraries/STM32_USB_OTG_Driver/src/usb_core.c index 8c96a04fc..f76c587c6 100644 --- a/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.2.0/Libraries/STM32_USB_OTG_Driver/src/usb_core.c +++ b/radio/src/thirdparty/STM32_USB-Host-Device_Lib_V2.2.0/Libraries/STM32_USB_OTG_Driver/src/usb_core.c @@ -29,6 +29,19 @@ #include "usb_core.h" #include "usb_bsp.h" +// taken from https://github.com/ARMmbed/mbed-os/blob/master/cmsis/TARGET_CORTEX_M/cmsis_compiler.h +#ifndef __PACKED_STRUCT +#define __PACKED_STRUCT struct __attribute__((packed)) +#endif +#ifndef __UNALIGNED_UINT32_WRITE +__PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; +#define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT32_READ +__PACKED_STRUCT T_UINT32_READ { uint32_t v; }; +#define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) +#endif + /** @addtogroup USB_OTG_DRIVER * @{ @@ -180,7 +193,7 @@ USB_OTG_STS USB_OTG_WritePacket(USB_OTG_CORE_HANDLE *pdev, fifo = pdev->regs.DFIFO[ch_ep_num]; for (i = 0; i < count32b; i++) { - USB_OTG_WRITE_REG32( fifo, *((uint32_t *)src) ); + USB_OTG_WRITE_REG32( fifo, __UNALIGNED_UINT32_READ(src) ); src+=4; } } @@ -206,7 +219,7 @@ void *USB_OTG_ReadPacket(USB_OTG_CORE_HANDLE *pdev, for( i = 0; i < count32b; i++) { - *(uint32_t *)dest = USB_OTG_READ_REG32(fifo); + __UNALIGNED_UINT32_WRITE(dest, USB_OTG_READ_REG32(fifo)); dest += 4 ; } return ((void *)dest); diff --git a/radio/src/thirdparty/libopenui b/radio/src/thirdparty/libopenui index 05067daa6..014e87b9f 160000 --- a/radio/src/thirdparty/libopenui +++ b/radio/src/thirdparty/libopenui @@ -1 +1 @@ -Subproject commit 05067daa62127ae1bfaa0a2f518d8249b606a4d3 +Subproject commit 014e87b9fc5dd345a3619f375b84c5e9747a398f