diff --git a/CMakeLists.txt b/CMakeLists.txt index fa100b9a94f..01ce84d2e8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(EdgeTX) set(VERSION_MAJOR "2") set(VERSION_MINOR "11") -set(VERSION_REVISION "7") +set(VERSION_REVISION "8") set(CODENAME "Jolly Mon") string(TIMESTAMP BUILD_YEAR "%Y") diff --git a/radio/src/audio.h b/radio/src/audio.h index 9f2be4b45a2..8a750d58b09 100644 --- a/radio/src/audio.h +++ b/radio/src/audio.h @@ -98,6 +98,11 @@ constexpr uint8_t AUDIO_FILENAME_MAXLEN = (AUDIO_LUA_FILENAME_MAXLEN > AUDIO_MOD #define AUDIO_BUFFER_COUNT (3) #endif +static_assert((AUDIO_QUEUE_LENGTH & (AUDIO_QUEUE_LENGTH - 1)) == 0, + "AUDIO_QUEUE_LENGTH must be a power of 2"); +static_assert(2 * AUDIO_BUFFER_COUNT <= 255, + "AUDIO_BUFFER_COUNT free-running counter must fit in uint8_t"); + #define BEEP_MIN_FREQ (150) #define BEEP_MAX_FREQ (15000) #define BEEP_DEFAULT_FREQ (2250) @@ -300,23 +305,31 @@ class AudioBufferFifo { #endif private: + // free-running counters in [0, 2*AUDIO_BUFFER_COUNT); each written by only one side volatile uint8_t readIdx; volatile uint8_t writeIdx; - volatile bool bufferFull; - inline uint8_t nextBufferIdx(uint8_t idx) const + inline uint8_t nextIndex(uint8_t idx) const { - return (idx >= AUDIO_BUFFER_COUNT - 1 ? 0 : idx + 1); + return (idx >= 2 * AUDIO_BUFFER_COUNT - 1 ? 0 : idx + 1); } - bool full() const { return bufferFull; } - bool empty() const { return readIdx == writeIdx && !bufferFull; } - uint8_t used() const { return bufferFull ? AUDIO_BUFFER_COUNT - : (writeIdx < readIdx) ? writeIdx + AUDIO_BUFFER_COUNT - readIdx - : writeIdx - readIdx; } + inline uint8_t slot(uint8_t idx) const + { + return idx >= AUDIO_BUFFER_COUNT ? idx - AUDIO_BUFFER_COUNT : idx; + } + + uint8_t used() const + { + uint8_t w = writeIdx, r = readIdx; + return w >= r ? w - r : w + 2 * AUDIO_BUFFER_COUNT - r; + } + + bool full() const { return used() == AUDIO_BUFFER_COUNT; } + bool empty() const { return readIdx == writeIdx; } public: - AudioBufferFifo() : readIdx(0), writeIdx(0), bufferFull(false) + AudioBufferFifo() : readIdx(0), writeIdx(0) { memset(audioBuffers, 0, sizeof(audioBuffers)); } @@ -325,26 +338,19 @@ class AudioBufferFifo { // with audioPushBuffer() AudioBuffer *getEmptyBuffer() const { - return full() ? nullptr : &audioBuffers[writeIdx]; + return full() ? nullptr : &audioBuffers[slot(writeIdx)]; } // puts filled buffer into FIFO - void audioPushBuffer() - { - writeIdx = nextBufferIdx(writeIdx); - if (writeIdx == readIdx) bufferFull = true; - } + void audioPushBuffer() { writeIdx = nextIndex(writeIdx); } - void freeNextFilledBuffer() - { - readIdx = nextBufferIdx(readIdx); - bufferFull = false; - } + // frees the last played buffer + void freeNextFilledBuffer() { readIdx = nextIndex(readIdx); } // returns a pointer to the audio buffer to be played const AudioBuffer *getNextFilledBuffer() { - return empty() ? nullptr : &audioBuffers[readIdx]; + return empty() ? nullptr : &audioBuffers[slot(readIdx)]; } bool filledAtleast(int noBuffers) const { return used() >= noBuffers; } @@ -356,13 +362,25 @@ class AudioFragmentFifo friend void printAudioVars(); #endif private: + // free-running counters in [0, 2*AUDIO_QUEUE_LENGTH); each written by only one side volatile uint8_t ridx; volatile uint8_t widx; AudioFragment fragments[AUDIO_QUEUE_LENGTH]; uint8_t nextIdx(uint8_t idx) const { - return (idx + 1) & (AUDIO_QUEUE_LENGTH - 1); + return (idx + 1) & (2 * AUDIO_QUEUE_LENGTH - 1); + } + + uint8_t slot(uint8_t idx) const + { + return idx & (AUDIO_QUEUE_LENGTH - 1); + } + + uint8_t used() const + { + uint8_t w = widx, r = ridx; + return w >= r ? w - r : w + 2 * AUDIO_QUEUE_LENGTH - r; } public: @@ -372,7 +390,7 @@ class AudioFragmentFifo { uint8_t i = ridx; while (i != widx) { - AudioFragment & fragment = fragments[i]; + AudioFragment & fragment = fragments[slot(i)]; if (fragment.id == id) return true; i = nextIdx(i); } @@ -383,7 +401,7 @@ class AudioFragmentFifo { uint8_t i = ridx; while (i != widx) { - AudioFragment & fragment = fragments[i]; + AudioFragment & fragment = fragments[slot(i)]; if (fragment.id == id) fragment.clear(); i = nextIdx(i); } @@ -397,7 +415,7 @@ class AudioFragmentFifo bool full() const { - return ridx == nextIdx(widx); + return used() == AUDIO_QUEUE_LENGTH; } void clear() @@ -408,8 +426,8 @@ class AudioFragmentFifo const AudioFragment * get() { if (!empty()) { - const AudioFragment * result = &fragments[ridx]; - if (!fragments[ridx].repeat--) { + const AudioFragment * result = &fragments[slot(ridx)]; + if (!fragments[slot(ridx)].repeat--) { // repeat is done, move to the next fragment ridx = nextIdx(ridx); } @@ -422,7 +440,7 @@ class AudioFragmentFifo { if (!full()) { // TRACE("fragment %d at %d", fragment.type, widx); - fragments[widx] = fragment; + fragments[slot(widx)] = fragment; widx = nextIdx(widx); } } diff --git a/radio/src/bootloader/bin_fw_files.cpp b/radio/src/bootloader/bin_fw_files.cpp index 7580bcab741..35a07416b6a 100644 --- a/radio/src/bootloader/bin_fw_files.cpp +++ b/radio/src/bootloader/bin_fw_files.cpp @@ -53,13 +53,14 @@ static FWFileInfo fwFiles[MAX_FW_FILES]; static uint8_t Block_buffer[BLOCK_LEN]; static UINT BlockCount; -static void flashWriteBlock() +static bool flashWriteBlock() { // TODO: use some board provided driver instead uint32_t blockOffset = 0; #if !defined(SIMU) while (BlockCount) { - flashWrite((uint32_t *)firmwareAddress, (uint32_t *)&Block_buffer[blockOffset]); + if (!flashWrite((uint32_t *)firmwareAddress, (uint32_t *)&Block_buffer[blockOffset])) + return false; blockOffset += FLASH_PAGESIZE; firmwareAddress += FLASH_PAGESIZE; if (BlockCount > FLASH_PAGESIZE) { @@ -70,6 +71,7 @@ static void flashWriteBlock() } } #endif // SIMU + return true; } void sdInit(void) @@ -267,17 +269,24 @@ void firmwareInitWrite(uint32_t index) firmwareWritten = 0; } -bool firmwareWriteBlock(uint32_t* progress) +FlashWriteRes firmwareWriteBlock(uint32_t* progress) { - flashWriteBlock(); + if (!flashWriteBlock()) return FW_ERROR; + firmwareWritten += sizeof(Block_buffer); *progress = (100 * firmwareWritten) / firmwareSize; - readFirmwareFile(); + // a read error or a short file must not be reported as success + if (readFirmwareFile() != FR_OK) { + return FW_ERROR; + } + if (BlockCount == 0 && firmwareWritten < firmwareSize) { + return FW_ERROR; + } if (BlockCount == 0 || firmwareWritten >= FLASHSIZE - BOOTLOADER_SIZE) { - return true; + return FW_DONE; } - return false; + return FW_IN_PROGRESS; } diff --git a/radio/src/bootloader/boot.h b/radio/src/bootloader/boot.h index 4313e0af332..53ed498b499 100644 --- a/radio/src/bootloader/boot.h +++ b/radio/src/bootloader/boot.h @@ -53,6 +53,7 @@ enum BootloaderState { ST_FLASH_CHECK, ST_FLASHING, ST_FLASH_DONE, + ST_FLASH_ERROR, ST_RESTORE_MENU, ST_USB, #if defined(SPI_FLASH) diff --git a/radio/src/bootloader/boot_menu.cpp b/radio/src/bootloader/boot_menu.cpp index 229df8b1350..ac54145b6d8 100644 --- a/radio/src/bootloader/boot_menu.cpp +++ b/radio/src/bootloader/boot_menu.cpp @@ -96,6 +96,7 @@ void bootloaderMenu() uint8_t index = 0; FRESULT fr; uint32_t nameCount = 0; + uint32_t progress = 0; sdInit(); @@ -107,7 +108,8 @@ void bootloaderMenu() next_frame += FRAME_INTERVAL_MS; if (state != ST_USB && state != ST_FLASHING - && state != ST_FLASH_DONE && state != ST_RADIO_MENU) { + && state != ST_FLASH_DONE && state != ST_FLASH_ERROR + && state != ST_RADIO_MENU) { if (usbPlugged()) { state = ST_USB; #if !defined(SIMU) @@ -244,14 +246,16 @@ void bootloaderMenu() } else if (result == 1) { // confirmed firmwareInitWrite(vpos); + progress = 0; state = ST_FLASHING; } } else if (state == ST_FLASHING) { - uint32_t progress = 0; - bool done = firmwareWriteBlock(&progress); + FlashWriteRes res = firmwareWriteBlock(&progress); bootloaderDrawScreen(state, progress); - if(done) { + if (res == FW_DONE) { state = ST_FLASH_DONE; + } else if (res == FW_ERROR) { + state = ST_FLASH_ERROR; } #if defined(SPI_FLASH) } else if (state == ST_CLEAR_FLASH_CHECK) { @@ -282,13 +286,15 @@ void bootloaderMenu() } } - if (state == ST_FLASH_DONE) { + if (state == ST_FLASH_DONE || state == ST_FLASH_ERROR) { + BootloaderState drawn = state; + if (event == EVT_KEY_BREAK(KEY_EXIT) || event == EVT_KEY_BREAK(KEY_ENTER)) { state = ST_START; vpos = 0; } - bootloaderDrawScreen(state, 100); + bootloaderDrawScreen(drawn, drawn == ST_FLASH_DONE ? 100 : progress); } if (event == EVT_KEY_LONG(KEY_EXIT)) { diff --git a/radio/src/bootloader/firmware_files.h b/radio/src/bootloader/firmware_files.h index 4475b13d3d4..560e5135b11 100644 --- a/radio/src/bootloader/firmware_files.h +++ b/radio/src/bootloader/firmware_files.h @@ -69,7 +69,15 @@ enum FlashCheckRes { }; FlashCheckRes checkFirmwareFile(unsigned int index, FlashCheckRes res); +enum FlashWriteRes { + FW_IN_PROGRESS=0, + FW_DONE, + FW_ERROR +}; + void firmwareInitWrite(uint32_t index); bool firmwareEraseBlock(uint32_t* progress); -bool firmwareWriteBlock(uint32_t* progress); + +// Write the block currently in the buffer, then fetch the next one +FlashWriteRes firmwareWriteBlock(uint32_t* progress); diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index 6deced89485..cfa9209a649 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -1400,11 +1400,17 @@ void printAudioVars() } } - cliSerialPrint("FragmentFifo: ridx: %d, widx: %d", - audioQueue.fragmentsFifo.ridx, audioQueue.fragmentsFifo.widx); - cliSerialPrint("audioQueue: readIdx: %d, writeIdx: %d, full: %d", - audioQueue.buffersFifo.readIdx, audioQueue.buffersFifo.writeIdx, - audioQueue.buffersFifo.bufferFull); + cliSerialPrint("FragmentFifo: ridx: %d (slot %d), widx: %d (slot %d)", + audioQueue.fragmentsFifo.ridx, + audioQueue.fragmentsFifo.slot(audioQueue.fragmentsFifo.ridx), + audioQueue.fragmentsFifo.widx, + audioQueue.fragmentsFifo.slot(audioQueue.fragmentsFifo.widx)); + cliSerialPrint("audioQueue: readIdx: %d (slot %d), writeIdx: %d (slot %d), full: %d", + audioQueue.buffersFifo.readIdx, + audioQueue.buffersFifo.slot(audioQueue.buffersFifo.readIdx), + audioQueue.buffersFifo.writeIdx, + audioQueue.buffersFifo.slot(audioQueue.buffersFifo.writeIdx), + audioQueue.buffersFifo.full()); cliSerialPrint("normalContext: %u", (uint32_t)audioQueue.normalContext.fragment.type); @@ -1465,6 +1471,11 @@ int cliDisplay(const char ** argv) gettime(&utm); cliSerialPrint("rtc = %4d-%02d-%02d %02d:%02d:%02d.%02d0", utm.tm_year+TM_YEAR_BASE, utm.tm_mon+1, utm.tm_mday, utm.tm_hour, utm.tm_min, utm.tm_sec, g_ms100); } +#if defined(VOLUME_I2C_ADDRESS) + else if (!strcmp(argv[1], "volume")) { + cliSerialPrint("volume = %d", getVolume()); + } +#endif else if (!strcmp(argv[1], "uid")) { char str[LEN_CPU_UID+1]; getCPUUniqueID(str); diff --git a/radio/src/gui/colorlcd/boot_menu.cpp b/radio/src/gui/colorlcd/boot_menu.cpp index 24bdc1c3033..b9c63b0e0cb 100644 --- a/radio/src/gui/colorlcd/boot_menu.cpp +++ b/radio/src/gui/colorlcd/boot_menu.cpp @@ -344,16 +344,20 @@ void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) lcd->drawText(USB_TXT_X, y + USB_PLG_TXT_YO, TR_BL_USB_CONNECTED, USB_TXT_ALIGN | BL_FOREGROUND); } else if (st == ST_FILE_LIST || st == ST_DIR_CHECK || st == ST_FLASH_CHECK || - st == ST_FLASHING || st == ST_FLASH_DONE) { + st == ST_FLASHING || st == ST_FLASH_DONE || st == ST_FLASH_ERROR) { bootloaderDrawTitle(LV_SYMBOL_SD_CARD " /FIRMWARE"); - if (st == ST_FLASHING || st == ST_FLASH_DONE) { + if (st == ST_FLASHING || st == ST_FLASH_DONE || st == ST_FLASH_ERROR) { LcdFlags color = BL_RED; if (st == ST_FLASH_DONE) { color = BL_GREEN; opt = 100; // Completed > 100% + } else if (st == ST_FLASH_ERROR) { + // the bar stays where the write gave up + lcd->drawText(LCD_W / 2, (LCD_H - PROGRESS_H) / 2 - EdgeTxStyles::STD_FONT_HEIGHT, + LV_SYMBOL_CLOSE " " TR_BL_WRITING_FAILED, CENTERED | BL_FOREGROUND); } lcd->drawRect(PROGRESS_X, (LCD_H - PROGRESS_H) / 2, PROGRESS_W, PROGRESS_H, LINE_H, SOLID, BL_SELECTED); @@ -408,6 +412,9 @@ void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) else if (st == ST_FLASH_DONE) { pos = lcd->drawText(FOOTER_X1, LCD_H - FOOTER_Y1, LV_SYMBOL_CHARGE " " TR_BL_WRITING_COMPL, FOOTER_ALIGN1 | BL_FOREGROUND); } + else if (st == ST_FLASH_ERROR) { + pos = lcd->drawText(FOOTER_X1, LCD_H - FOOTER_Y1, LV_SYMBOL_CHARGE " " TR_BL_RETRY_OR_DFU, FOOTER_ALIGN1 | BL_FOREGROUND); + } } #if LANDSCAPE pos = 0; diff --git a/radio/src/gui/colorlcd/controls/color_editor.cpp b/radio/src/gui/colorlcd/controls/color_editor.cpp index 13c07ce1b60..e62e0f4ea59 100644 --- a/radio/src/gui/colorlcd/controls/color_editor.cpp +++ b/radio/src/gui/colorlcd/controls/color_editor.cpp @@ -36,8 +36,6 @@ class ColorBar : public FormField uint32_t maxValue = 0) : FormField(parent, r) { - lv_obj_add_flag(lvobj, LV_OBJ_FLAG_ENCODER_ACCEL); - lv_group_add_obj((lv_group_t*)lv_group_get_default(), lvobj); lv_obj_add_event_cb(lvobj, ColorBar::pressing, LV_EVENT_PRESSING, nullptr); diff --git a/radio/src/gui/colorlcd/libui/keyboard_number.cpp b/radio/src/gui/colorlcd/libui/keyboard_number.cpp index de9efde635c..4fe6b59a453 100644 --- a/radio/src/gui/colorlcd/libui/keyboard_number.cpp +++ b/radio/src/gui/colorlcd/libui/keyboard_number.cpp @@ -20,7 +20,7 @@ #include "numberedit.h" -constexpr coord_t KEYBOARD_HEIGHT = 90; +LAYOUT_VAL_SCALED(KEYBOARD_HEIGHT, 90); NumberKeyboard* NumberKeyboard::_instance = nullptr; static const char* const number_kb_map[] = {"<<", "-", "+", ">>", "\n", @@ -71,42 +71,42 @@ void NumberKeyboard::handleEvent(const char* btn) void NumberKeyboard::decLarge() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_BACKWARD); + field->onEvent(EVT_VIRTUAL_KEY_BACKWARD); } void NumberKeyboard::decSmall() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MINUS); + field->onEvent(EVT_VIRTUAL_KEY_MINUS); } void NumberKeyboard::incSmall() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_PLUS); + field->onEvent(EVT_VIRTUAL_KEY_PLUS); } void NumberKeyboard::incLarge() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_FORWARD); + field->onEvent(EVT_VIRTUAL_KEY_FORWARD); } void NumberKeyboard::setMIN() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MIN); + field->onEvent(EVT_VIRTUAL_KEY_MIN); } void NumberKeyboard::setMAX() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_MAX); + field->onEvent(EVT_VIRTUAL_KEY_MAX); } void NumberKeyboard::setDEF() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_DEFAULT); + field->onEvent(EVT_VIRTUAL_KEY_DEFAULT); } void NumberKeyboard::changeSign() { - ((NumberEdit*)field)->onEvent(EVT_VIRTUAL_KEY_SIGN); + field->onEvent(EVT_VIRTUAL_KEY_SIGN); } #if defined(HARDWARE_KEYS) diff --git a/radio/src/gui/colorlcd/libui/libopenui_defines.h b/radio/src/gui/colorlcd/libui/libopenui_defines.h index daf6803563d..848a0757740 100644 --- a/radio/src/gui/colorlcd/libui/libopenui_defines.h +++ b/radio/src/gui/colorlcd/libui/libopenui_defines.h @@ -48,5 +48,3 @@ /* telemetry flags */ #define NO_UNIT 0x40u - -#define LV_OBJ_FLAG_ENCODER_ACCEL LV_OBJ_FLAG_USER_1 diff --git a/radio/src/gui/colorlcd/libui/menu.cpp b/radio/src/gui/colorlcd/libui/menu.cpp index fbe79cce8b5..83a18472faa 100644 --- a/radio/src/gui/colorlcd/libui/menu.cpp +++ b/radio/src/gui/colorlcd/libui/menu.cpp @@ -68,9 +68,6 @@ class MenuBody : public TableField MenuBody(Window* parent, const rect_t& rect) : TableField(parent, rect) { - // Allow encoder acceleration - lv_obj_add_flag(lvobj, LV_OBJ_FLAG_ENCODER_ACCEL); - setColumnWidth(0, rect.w); setAutoEdit(); diff --git a/radio/src/gui/colorlcd/libui/numberedit.cpp b/radio/src/gui/colorlcd/libui/numberedit.cpp index ff8233820f3..acc95672161 100644 --- a/radio/src/gui/colorlcd/libui/numberedit.cpp +++ b/radio/src/gui/colorlcd/libui/numberedit.cpp @@ -32,14 +32,6 @@ class NumberArea : public FormField { lv_obj_clear_flag(lvobj, LV_OBJ_FLAG_CLICK_FOCUSABLE); - if (parent->getTextFlags() & CENTERED) - etx_obj_add_style(lvobj, styles->text_align_center, LV_PART_MAIN); - else - etx_obj_add_style(lvobj, styles->text_align_right, LV_PART_MAIN); - - // Allow encoder acceleration - lv_obj_add_flag(lvobj, LV_OBJ_FLAG_ENCODER_ACCEL); - lv_obj_add_event_cb(lvobj, NumberArea::numberedit_cb, LV_EVENT_KEY, this); setFocusHandler([=](bool focus) { diff --git a/radio/src/gui/colorlcd/model/model_select.cpp b/radio/src/gui/colorlcd/model/model_select.cpp index ab16bf893a0..0b7ebcdc115 100644 --- a/radio/src/gui/colorlcd/model/model_select.cpp +++ b/radio/src/gui/colorlcd/model/model_select.cpp @@ -137,7 +137,6 @@ class ModelButton : public Button return true; } } - showNoImgMsg(); } return false; @@ -266,10 +265,12 @@ class ModelsPageBody : public Window // Long Press Handler for Models button->setLongPressHandler([=]() -> uint8_t { - button->setFocused(); - focusedModel = model; + if (model != focusedModel) { + button->setFocused(); + focusedModel = model; + } openMenu(); - return 0; + return model == modelslist.getCurrentModel(); }); } @@ -281,10 +282,15 @@ class ModelsPageBody : public Window } } - void reload() + void clearButtons() { modelButtons.clear(); clear(); + } + + void reload() + { + clearButtons(); update(); } @@ -665,14 +671,8 @@ void ModelLabelsWindow::newLabel() new LabelDialog(tmpLabel, LABEL_LENGTH, STR_ENTER_LABEL, [=](std::string label) { int newlabindex = modelslabels.addLabel(label); if (newlabindex >= 0) { - std::set newset; - newset.insert(newlabindex); auto labels = getLabels(); lblselector->setNames(labels); - lblselector->setSelected(newset); - if (g_eeGeneral.labelSingleSelect) - lblselector->setActiveItem(newlabindex); - updateFilteredLabels(newset); } }); } @@ -824,6 +824,7 @@ void ModelLabelsWindow::buildBody(Window *window) }); auto labels = getLabels(); lblselector->setNames(labels); + mdlselector->clearButtons(); updateFilteredLabels(modelslabels.filteredLabels(), false); } }); @@ -846,8 +847,13 @@ void ModelLabelsWindow::buildBody(Window *window) std::set newset; lblselector->setNames(labels); lblselector->setSelected(newset); - if (g_eeGeneral.labelSingleSelect && selected == lblselector->getActiveItem()) - lblselector->setActiveItem(-1); + if (g_eeGeneral.labelSingleSelect) { + if (selected == lblselector->getActiveItem()) + lblselector->setActiveItem(-1); + else + newset.insert(lblselector->getActiveItem()); + } + mdlselector->clearButtons(); updateFilteredLabels(newset); }); return 0; diff --git a/radio/src/gui/colorlcd/standalone_lua.cpp b/radio/src/gui/colorlcd/standalone_lua.cpp index 9be6aa2faf5..f8b51fdf41c 100644 --- a/radio/src/gui/colorlcd/standalone_lua.cpp +++ b/radio/src/gui/colorlcd/standalone_lua.cpp @@ -33,7 +33,7 @@ LuaMemTracer lsStandaloneTrace; static void luaStandaloneHook(lua_State * L, lua_Debug *ar) { - else if (ar->event == LUA_HOOKLINE) { + if (ar->event == LUA_HOOKLINE) { lua_getinfo(L, "nSl", ar); LuaMemTracer * tracer = &lsStandaloneTrace; if (tracer->alloc || tracer->free) { diff --git a/radio/src/gui/colorlcd/themes/theme_manager.cpp b/radio/src/gui/colorlcd/themes/theme_manager.cpp index 6c176b40933..23eb2603ca6 100644 --- a/radio/src/gui/colorlcd/themes/theme_manager.cpp +++ b/radio/src/gui/colorlcd/themes/theme_manager.cpp @@ -385,7 +385,8 @@ void ThemePersistance::loadDefaultTheme() } for (auto theme : themes) { - if (theme->getName().compare(0, SELECTED_THEME_NAME_LEN, g_eeGeneral.selectedTheme) == 0) { + if (theme->getName().compare(0, SELECTED_THEME_NAME_LEN, + stringFromNtString(g_eeGeneral.selectedTheme)) == 0) { found = true; break; } @@ -458,8 +459,7 @@ bool ThemePersistance::createNewTheme(std::string name, ThemeFile& theme) void ThemePersistance::setDefaultTheme(int index) { if (index >= 0 && index < (int)themes.size()) { - strAppend(g_eeGeneral.selectedTheme, themes[index]->getName().c_str(), - SELECTED_THEME_NAME_LEN); + copyToUnTerminated(g_eeGeneral.selectedTheme, themes[index]->getName()); SET_DIRTY(); currentTheme = index; } diff --git a/radio/src/gui/common/stdlcd/boot_menu.cpp b/radio/src/gui/common/stdlcd/boot_menu.cpp index 7875396ba73..3fb347df180 100644 --- a/radio/src/gui/common/stdlcd/boot_menu.cpp +++ b/radio/src/gui/common/stdlcd/boot_menu.cpp @@ -132,6 +132,10 @@ void bootloaderDrawScreen(BootloaderState st, int opt, const char *str) else if (st == ST_FLASH_DONE) { lcdDrawCenteredText(4 * FH, TR_BL_WRITING_COMPL); } + else if (st == ST_FLASH_ERROR) { + lcdDrawCenteredText(4 * FH, TR_BL_WRITING_FAILED); + lcdDrawCenteredText(6 * FH, TR_BL_RETRY_OR_DFU); + } } uint32_t bootloaderGetMenuItemCount(int baseCount) diff --git a/radio/src/io/bootloader_flash.cpp b/radio/src/io/bootloader_flash.cpp index 76ee72276f2..ce6971cc019 100644 --- a/radio/src/io/bootloader_flash.cpp +++ b/radio/src/io/bootloader_flash.cpp @@ -63,6 +63,7 @@ void BootloaderFirmwareUpdate::flashFirmware(const char * filename, ProgressHand FIL file; uint8_t buffer[1024]; UINT count; + bool success = true; pulsesStop(); @@ -80,22 +81,29 @@ void BootloaderFirmwareUpdate::flashFirmware(const char * filename, ProgressHand if (f_read(&file, buffer, sizeof(buffer), &count) != FR_OK) { POPUP_WARNING(STR_SDCARD_ERROR); + success = false; break; } if (count != sizeof(buffer) && !f_eof(&file)) { POPUP_WARNING(STR_SDCARD_ERROR); + success = false; break; } if (i == 0 && !isBootloaderStart(buffer)) { POPUP_WARNING(STR_INCOMPATIBLE); + success = false; break; } - for (UINT j = 0; j < count; j += FLASH_PAGESIZE) { + for (UINT j = 0; j < count && success; j += FLASH_PAGESIZE) { WDG_ENABLE(3000); - flashWrite(CONVERT_UINT_PTR(BOOTLOADER_ADDRESS + i + j), CONVERT_UINT_PTR(buffer + j)); + success = flashWrite(CONVERT_UINT_PTR(BOOTLOADER_ADDRESS + i + j), CONVERT_UINT_PTR(buffer + j)); WDG_ENABLE(WDG_DURATION); } + if (!success) { + POPUP_WARNING(STR_FIRMWARE_UPDATE_ERROR); + break; + } progressHandler("Bootloader", STR_WRITING, i, flash_size); // Reached end-of-file @@ -108,7 +116,7 @@ void BootloaderFirmwareUpdate::flashFirmware(const char * filename, ProgressHand #endif } - POPUP_INFORMATION(STR_FIRMWARE_UPDATE_SUCCESS); + if (success) POPUP_INFORMATION(STR_FIRMWARE_UPDATE_SUCCESS); watchdogSuspend(0); WDG_RESET(); diff --git a/radio/src/lib_file.cpp b/radio/src/lib_file.cpp index 3523b41eff9..3c772994777 100644 --- a/radio/src/lib_file.cpp +++ b/radio/src/lib_file.cpp @@ -121,7 +121,7 @@ int f_puts(const char* s, FIL* fp) return (len == written && result == FR_OK) ? 0 : EOF; } -#define MAX_FPRINTF_LEN 100 +#define MAX_FPRINTF_LEN 128 int f_printf (FIL* fp, const TCHAR* str, ...) { diff --git a/radio/src/lua/api_stdlcd.cpp b/radio/src/lua/api_stdlcd.cpp index 6095bca8fd4..1fa7c46e63e 100644 --- a/radio/src/lua/api_stdlcd.cpp +++ b/radio/src/lua/api_stdlcd.cpp @@ -595,6 +595,7 @@ static int luaLcdDrawCombobox(lua_State *L) lua_rawgeti(L, 4, i+1); const char * item = luaL_checkstring(L, -1); lcdDrawText(x+2, y+2+9*i, item, 0); + lua_pop(L, 1); } lcdDrawFilledRect(x+1, y+1+9*idx, w-11, 9); lcdDrawFilledRect(x+w-10, y, 10, 11, SOLID, ERASE); @@ -606,6 +607,7 @@ static int luaLcdDrawCombobox(lua_State *L) lua_rawgeti(L, 4, idx+1); const char * item = luaL_checkstring(L, -1); lcdDrawText(x+2, y+2, item, INVERS); + lua_pop(L, 1); } else { lcdDrawFilledRect(x, y, w, 11, SOLID, ERASE); @@ -614,6 +616,7 @@ static int luaLcdDrawCombobox(lua_State *L) lua_rawgeti(L, 4, idx+1); const char * item = luaL_checkstring(L, -1); lcdDrawText(x+2, y+2, item, 0); + lua_pop(L, 1); } lcdDrawSolidHorizontalLine(x+w-8, y+3, 6); diff --git a/radio/src/mixer.cpp b/radio/src/mixer.cpp index 3a7b22f97ba..10fa1af440a 100644 --- a/radio/src/mixer.cpp +++ b/radio/src/mixer.cpp @@ -705,13 +705,13 @@ int getSourceTrimOrigin(int source) int getSourceTrimValue(int source, int stickValue=0) { - auto origin = getSourceTrimOrigin(source); + int rv = 0; + auto origin = getSourceTrimOrigin(abs(source)); if (origin >= 0) { - return getStickTrimValue(origin, stickValue); - } - else { - return 0; + rv = getStickTrimValue(origin, stickValue); + if (source < 0) rv = -rv; } + return rv; } constexpr bitfield_channels_t all_channels_dirty = (bitfield_channels_t)-1; @@ -967,7 +967,7 @@ void evalFlightModeMixes(uint8_t mode, uint8_t tick10ms) if (applyOffsetAndCurve) { bool applyTrims = !(mode & e_perout_mode_notrims); if (!applyTrims && g_model.thrTrim) { - auto origin = getSourceTrimOrigin(srcRaw); + auto origin = getSourceTrimOrigin(srcRawAbs); if (origin == g_model.getThrottleStickTrimSource() - MIXSRC_FIRST_TRIM) { applyTrims = true; } diff --git a/radio/src/pulses/crossfire.cpp b/radio/src/pulses/crossfire.cpp index 0e4c30e2681..58c2d71566b 100644 --- a/radio/src/pulses/crossfire.cpp +++ b/radio/src/pulses/crossfire.cpp @@ -96,20 +96,20 @@ uint8_t createCrossfireModelIDFrame(uint8_t moduleIdx, uint8_t * frame) uint8_t createCrossfireChannelsFrame(uint8_t moduleIdx, uint8_t * frame, int16_t * pulses) { // - // sends channel data and also communicates commanded armed status in arming mode Switch. - // frame len 24 -> arming mode CH5: module will use channel 5 - // frame len 25 -> arming mode Switch: send commanded armed status in extra byte after channel data - // - ModuleData *md = &g_model.moduleData[moduleIdx]; - - uint8_t armingMode = md->crsf.crsfArmingMode; // 0 = Channel mode, 1 = Switch mode - uint8_t lenAdjust = (armingMode == ARMING_MODE_SWITCH) ? 1 : 0; - + // sends channel data and also communicates status information in status byte: + // - arming status in Switch mode (bit 0) + // - arming mode Switch or CH5 (bit 1) + // - bits 2-7 spare + // uint8_t * buf = frame; *buf++ = MODULE_ADDRESS; - *buf++ = 24 + lenAdjust; // 1(ID) + 22(channel data) + (+1 extra byte if Switch mode) + 1(CRC) + *buf++ = 25; // 1(ID) + 22(channel data) + 1(extra status byte) + 1(CRC) uint8_t * crc_start = buf; *buf++ = CHANNELS_ID; + + // + // assemble channel data + // uint32_t bits = 0; uint8_t bitsavailable = 0; for (int i=0; icrsf.crsfArmingMode == ARMING_MODE_SWITCH) { swsrc_t sw = md->crsf.crsfArmingTrigger; - *buf++ = (sw != SWSRC_NONE) && getSwitch(sw, 0); // commanded armed status in Switch mode + *buf = (sw != SWSRC_NONE) && getSwitch(sw, 0); // commanded armed status in Switch mode + } else { + *buf = 0x02; // flag arming mode CH5 } + + buf++; - *buf++ = crc8(crc_start, 23 + lenAdjust); + // + // add crc + // + *buf++ = crc8(crc_start, 24); + return buf - frame; } diff --git a/radio/src/storage/modelslist.cpp b/radio/src/storage/modelslist.cpp index 6feee9c8ce8..3046d7d62da 100644 --- a/radio/src/storage/modelslist.cpp +++ b/radio/src/storage/modelslist.cpp @@ -357,8 +357,8 @@ int ModelMap::addLabel(std::string lbl) if (lbl == STR_UNLABELEDMODEL) return -1; // Limit maximum label length, TODO... Truncate UTF8 Properly - lbl = lbl.substr(0, LABEL_LENGTH); removeYAMLChars(lbl); + lbl = lbl.substr(0, LABEL_LENGTH); if (lbl.size() == 0) return -1; // Add a new label if it doesn't already exist in the list diff --git a/radio/src/storage/modelslist.h b/radio/src/storage/modelslist.h index b26575753f2..4e5285fc4b2 100644 --- a/radio/src/storage/modelslist.h +++ b/radio/src/storage/modelslist.h @@ -87,12 +87,6 @@ class ModelCell bool fetchRfData(); }; -typedef struct { - std::string icon; - // Anything else? -} SLabelDetail; - -typedef std::vector> ModelLabelsVector; typedef std::vector LabelsVector; typedef std::vector ModelsVector; typedef enum { @@ -250,7 +244,5 @@ class ModelsList : public ModelsVector bool loadYamlDirScanner(); }; -ModelLabelsVector getUniqueLabels(); - extern ModelsList modelslist; extern ModelMap modelslabels; diff --git a/radio/src/strhelpers.cpp b/radio/src/strhelpers.cpp index 9a756ae7dc3..502aed1b066 100644 --- a/radio/src/strhelpers.cpp +++ b/radio/src/strhelpers.cpp @@ -1172,6 +1172,7 @@ char *strAppendSigned(char *dest, int32_t value, uint8_t digits, uint8_t radix) return strAppendUnsigned(dest, (uint32_t)value, digits, radix); } +// NOTE: 'dest' buffer must be at least 'len + 1' bytes long to allow for nul terminator char *strAppend(char *dest, const char *source, int len) { if (source == nullptr) { *dest = '\0'; return dest; } diff --git a/radio/src/targets/common/arm/stm32/flash_driver.cpp b/radio/src/targets/common/arm/stm32/flash_driver.cpp index cc41ad11ed9..776ff2e86f6 100644 --- a/radio/src/targets/common/arm/stm32/flash_driver.cpp +++ b/radio/src/targets/common/arm/stm32/flash_driver.cpp @@ -85,6 +85,82 @@ static uint32_t stm32_flash_get_sector_size(uint32_t sector) return 128 * 1024; } +// twice the datasheet maximum for a 128 KB sector erase at x32 parallelism +// (2 s, DS9405 Table 48); the driver never issues a bank or mass erase +#define FLASH_TIMEOUT_MS 4000 + +#if defined(FLASH_FLAG_RDERR) + #define _FLASH_FLAG_RDERR FLASH_FLAG_RDERR +#else + #define _FLASH_FLAG_RDERR 0U +#endif + +// Error flags are sticky until written with 1, so one left over by an earlier +// access since reset (e.g. a stray write to flash, which sets PGSERR) would +// abort the very next erase/program. Clear them before starting an operation. +static void flash_drv_clear_errors() +{ + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | + FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | + FLASH_FLAG_PGSERR | _FLASH_FLAG_RDERR); +} + +static bool flash_drv_wait_last_op() +{ + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + + uint32_t start = DWT->CYCCNT; + uint32_t timeout_cycles = + (uint32_t)(FLASH_TIMEOUT_MS * (SystemCoreClock / 1000UL)); + + while (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) { + if ((DWT->CYCCNT - start) > timeout_cycles) { + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | + FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | + FLASH_FLAG_PGSERR); + return false; + } + } + + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP); + + if (__HAL_FLASH_GET_FLAG(FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | + FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR)) { + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | + FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR | + FLASH_FLAG_EOP); + return false; + } + + return true; +} + +// ES0206 2.2.15: on dual bank devices the data cache may be corrupted by a +// read-while-write, so it must be disabled while flash is erased/programmed +// and reset before being enabled again. +static bool flash_drv_disable_dcache() +{ + bool enabled = (FLASH->ACR & FLASH_ACR_DCEN) != 0; + if (enabled) FLASH->ACR &= ~FLASH_ACR_DCEN; + return enabled; +} + +static void flash_drv_flush_caches(bool dcache_enabled) +{ + if (FLASH->ACR & FLASH_ACR_ICEN) { + FLASH->ACR &= ~FLASH_ACR_ICEN; + FLASH->ACR |= FLASH_ACR_ICRST; + FLASH->ACR &= ~FLASH_ACR_ICRST; + FLASH->ACR |= FLASH_ACR_ICEN; + } + if (dcache_enabled) { + FLASH->ACR |= FLASH_ACR_DCRST; + FLASH->ACR &= ~FLASH_ACR_DCRST; + FLASH->ACR |= FLASH_ACR_DCEN; + } +} + #elif defined(STM32H7) || defined(STM32H7RS) static uint32_t stm32_flash_get_sector(uint32_t address) @@ -129,6 +205,43 @@ static inline void stm32_flash_lock() { HAL_FLASH_Lock(); } static int stm32_flash_erase_sector(uint32_t address) { + int ret = 0; + +#if defined(STM32F2) || defined(STM32F4) + + uint32_t sector = stm32_flash_get_sector(address); + + __disable_irq(); + __DSB(); + + stm32_flash_unlock(); + flash_drv_clear_errors(); + + bool dcache = flash_drv_disable_dcache(); + + if (sector > 11) sector += 4; + + CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE); + FLASH->CR |= FLASH_PSIZE_WORD; + CLEAR_BIT(FLASH->CR, FLASH_CR_SNB); + FLASH->CR |= FLASH_CR_SER | (sector << FLASH_CR_SNB_Pos); + FLASH->CR |= FLASH_CR_STRT; + + if (!flash_drv_wait_last_op()) { + ret = -1; + } + + CLEAR_BIT(FLASH->CR, FLASH_CR_SER | FLASH_CR_SNB); + + __DSB(); + __enable_irq(); + + flash_drv_flush_caches(dcache); + + stm32_flash_lock(); + +#else + FLASH_EraseInitTypeDef eraseInit; eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS; eraseInit.Sector = stm32_flash_get_sector(address); @@ -142,15 +255,20 @@ static int stm32_flash_erase_sector(uint32_t address) eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; #endif - int ret = 0; uint32_t sector_errors = 0; + // Interrupts must stay enabled here: the HAL timeout counts HAL_GetTick(), + // which only advances in the ms timer interrupt. Nothing needs masking, as + // code and vectors run from TCM/SDRAM, never from the bank being erased. stm32_flash_unlock(); if (HAL_FLASHEx_Erase(&eraseInit, §or_errors) != HAL_OK) { ret = -1; } stm32_flash_lock(); + +#endif + return ret; } @@ -162,18 +280,57 @@ static int stm32_flash_erase_sector(uint32_t address) #define FLASH_PROG_WORDS 4UL #define _FLASH_PROGRAM(address, p_data) \ HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, address, (uintptr_t)p_data) -#else - #define FLASH_PROG_WORDS 1UL - #define _FLASH_PROGRAM(address, p_data) \ - HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, *p_data) #endif static int stm32_flash_program(uint32_t address, void* data, uint32_t len) { + int ret = 0; + +#if defined(STM32F2) || defined(STM32F4) + uint32_t* p_data = (uint32_t*)data; uint32_t end_addr = address + len; - int ret = 0; + __disable_irq(); + __DSB(); + stm32_flash_unlock(); + flash_drv_clear_errors(); + + bool dcache = flash_drv_disable_dcache(); + + while (address < end_addr) { + CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE); + FLASH->CR |= FLASH_PSIZE_WORD; + FLASH->CR |= FLASH_CR_PG; + + *(__IO uint32_t*)address = *p_data; + + // PG must be cleared even on failure, or the next erase sees PG+SER + bool ok = flash_drv_wait_last_op(); + CLEAR_BIT(FLASH->CR, FLASH_CR_PG); + + if (!ok) { + ret = -1; + break; + } + + address += sizeof(uint32_t); + p_data++; + } + + __DSB(); + __enable_irq(); + + flash_drv_flush_caches(dcache); + + stm32_flash_lock(); + +#else + + uint32_t* p_data = (uint32_t*)data; + uint32_t end_addr = address + len; + + // interrupts stay enabled, see stm32_flash_erase_sector() stm32_flash_unlock(); while (address < end_addr) { if (_FLASH_PROGRAM(address, p_data) != HAL_OK) { @@ -186,6 +343,9 @@ static int stm32_flash_program(uint32_t address, void* data, uint32_t len) } stm32_flash_lock(); + +#endif + return ret; } @@ -211,17 +371,29 @@ const etx_flash_driver_t stm32_flash_driver = { void unlockFlash() { stm32_flash_unlock(); } void lockFlash() { stm32_flash_lock(); } -void flashWrite(uint32_t* address, const uint32_t* buffer) +bool flashWrite(uint32_t* address, const uint32_t* buffer) { // check first if the address is on a sector boundary uint32_t sector = stm32_flash_get_sector((uintptr_t)address); uint32_t bank = stm32_flash_get_bank((uintptr_t)address); if ((uintptr_t)address == _flash_sector_address(sector, bank)) { - if (stm32_flash_erase_sector((uintptr_t)address) < 0) return; + if (stm32_flash_erase_sector((uintptr_t)address) < 0) return false; } - stm32_flash_program((uintptr_t)address, (uint8_t*)buffer, FLASH_PAGESIZE); + if (stm32_flash_program((uintptr_t)address, (uint8_t*)buffer, + FLASH_PAGESIZE) < 0) + return false; + +#if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) + // drop any lines cached before the erase/program so the read back + // below comes from flash + SCB_InvalidateDCache_by_Addr(address, FLASH_PAGESIZE); +#endif + + // verify the page was actually written, independently of what the + // flash controller reported + return memcmp(address, buffer, FLASH_PAGESIZE) == 0; } // TODO: move this somewhere else, as it depends on firmware layout diff --git a/radio/src/targets/common/arm/stm32/flash_driver.h b/radio/src/targets/common/arm/stm32/flash_driver.h index 86dc0b40107..3f107cf9fc3 100644 --- a/radio/src/targets/common/arm/stm32/flash_driver.h +++ b/radio/src/targets/common/arm/stm32/flash_driver.h @@ -44,5 +44,5 @@ extern const void* stm32_flash_dfu_media; uint32_t isFirmwareStart(const uint8_t * buffer); uint32_t isBootloaderStart(const uint8_t * buffer); -void flashWrite(uint32_t* address, const uint32_t* buffer); +bool flashWrite(uint32_t* address, const uint32_t* buffer); diff --git a/radio/src/targets/pa01/board.h b/radio/src/targets/pa01/board.h index 42a2b5bc765..4ced440aa65 100644 --- a/radio/src/targets/pa01/board.h +++ b/radio/src/targets/pa01/board.h @@ -61,7 +61,7 @@ void getCPUUniqueID(char * s); #define FLASH_PAGESIZE 256 void unlockFlash(); void lockFlash(); -void flashWrite(uint32_t * address, const uint32_t * buffer); +bool flashWrite(uint32_t * address, const uint32_t * buffer); uint32_t isFirmwareStart(const uint8_t * buffer); uint32_t isBootloaderStart(const uint8_t * buffer); diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 0bd0c229372..5df6b3e2eb2 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -498,9 +498,10 @@ void setSelectedUsbMode(int mode) {} void delay_ms(uint32_t ms) { } void delay_us(uint16_t us) { } -void flashWrite(uint32_t *address, const uint32_t *buffer) +bool flashWrite(uint32_t *address, const uint32_t *buffer) { simuSleep(10); + return true; } uint32_t isBootloaderStart(const uint8_t * block) diff --git a/radio/src/targets/st16/board.h b/radio/src/targets/st16/board.h index 863ead25eaf..d713d304e1f 100644 --- a/radio/src/targets/st16/board.h +++ b/radio/src/targets/st16/board.h @@ -60,7 +60,7 @@ void getCPUUniqueID(char * s); #define FLASH_PAGESIZE 256 void unlockFlash(); void lockFlash(); -void flashWrite(uint32_t * address, const uint32_t * buffer); +bool flashWrite(uint32_t * address, const uint32_t * buffer); uint32_t isFirmwareStart(const uint8_t * buffer); uint32_t isBootloaderStart(const uint8_t * buffer); diff --git a/radio/src/targets/taranis/CMakeLists.txt b/radio/src/targets/taranis/CMakeLists.txt index bafba2ac3c0..eb509c7308e 100644 --- a/radio/src/targets/taranis/CMakeLists.txt +++ b/radio/src/targets/taranis/CMakeLists.txt @@ -8,6 +8,7 @@ option(AUTOUPDATE "Auto update internal chips from SD" OFF) option(BIND_KEY "Enable bind button" OFF) option(BLUETOOTH "FrSky BT module support" OFF) option(RGBLEDS "WS2812 addressable LED support" OFF) +option(XLITE_ALT_LAYOUT "Alternative layout for X-Lite button functions" OFF) set(USE_RTC_CLOCK YES) set(HARDWARE_EXTERNAL_MODULE YES) @@ -508,6 +509,10 @@ elseif(PCB STREQUAL XLITE) set(LCD_DRIVER lcd_driver_spi.cpp) set(STATUS_LEDS YES) set(BLUETOOTH YES) + if(XLITE_ALT_LAYOUT) + message("-- Using alternative layout for X-Lite button functions") + add_definitions(-DXLITE_ALT_LAYOUT) + endif() add_definitions(-DMANUFACTURER_FRSKY) elseif(PCB STREQUAL XLITES) set(DEFAULT_INTERNAL_MODULE ISRM_PXX2 CACHE STRING "Default internal module") @@ -529,6 +534,10 @@ elseif(PCB STREQUAL XLITES) set(PXX2 ON) set(BLUETOOTH YES) set(IMU YES) + if(XLITE_ALT_LAYOUT) + message("-- Using alternative layout for X-Lite button functions") + add_definitions(-DXLITE_ALT_LAYOUT) + endif() add_definitions(-DMANUFACTURER_FRSKY) endif() diff --git a/radio/src/targets/taranis/hal.h b/radio/src/targets/taranis/hal.h index 0a155c6d6c3..26a046377c2 100644 --- a/radio/src/targets/taranis/hal.h +++ b/radio/src/targets/taranis/hal.h @@ -53,6 +53,21 @@ #define KEYS_GPIO_PIN_PAGEDN LL_GPIO_PIN_3 // PD.03 #define KEYS_GPIO_REG_ENTER GPIOF #define KEYS_GPIO_PIN_ENTER LL_GPIO_PIN_0 // PF.00 +#elif defined(PCBXLITE) && defined(XLITE_ALT_LAYOUT) + #define KEYS_GPIO_REG_SHIFT GPIOE + #define KEYS_GPIO_PIN_SHIFT LL_GPIO_PIN_11 // PE.11 + #define KEYS_GPIO_REG_EXIT GPIOE + #define KEYS_GPIO_PIN_EXIT LL_GPIO_PIN_7 // PE.07 + #define KEYS_GPIO_REG_ENTER GPIOE + #define KEYS_GPIO_PIN_ENTER LL_GPIO_PIN_8 // PE.08 + #define KEYS_GPIO_REG_UP GPIOB + #define KEYS_GPIO_PIN_UP LL_GPIO_PIN_0 // PB.00 + #define KEYS_GPIO_REG_DOWN GPIOB + #define KEYS_GPIO_PIN_DOWN LL_GPIO_PIN_1 // PB.01 + #define KEYS_GPIO_REG_LEFT GPIOC + #define KEYS_GPIO_PIN_LEFT LL_GPIO_PIN_4 // PC.04 + #define KEYS_GPIO_REG_RIGHT GPIOC + #define KEYS_GPIO_PIN_RIGHT LL_GPIO_PIN_5 // PC.05 #elif defined(PCBXLITE) #define KEYS_GPIO_REG_SHIFT GPIOE #define KEYS_GPIO_PIN_SHIFT LL_GPIO_PIN_8 // PE.08 @@ -370,6 +385,17 @@ #define TRIMS_GPIO_PIN_RVU LL_GPIO_PIN_2 // PC.02 #define TRIMS_GPIO_REG_RHR GPIOC #define TRIMS_GPIO_PIN_RHR LL_GPIO_PIN_13 // PC.13 +#elif defined(PCBXLITE) && defined(XLITE_ALT_LAYOUT) + #define TRIMS_GPIO_REG_LHL GPIOE + #define TRIMS_GPIO_PIN_LHL LL_GPIO_PIN_12 // PE.12 + #define TRIMS_GPIO_REG_LHR GPIOE + #define TRIMS_GPIO_PIN_LHR LL_GPIO_PIN_13 // PE.13 + #define TRIMS_GPIO_REG_LVU GPIOE + #define TRIMS_GPIO_PIN_LVU LL_GPIO_PIN_10 // PE.10 + #define TRIMS_GPIO_REG_LVD GPIOE + #define TRIMS_GPIO_PIN_LVD LL_GPIO_PIN_14 // PE.14 + #define TRIMS_VIRTUAL_RV + #define TRIMS_VIRTUAL_RH #elif defined(PCBXLITE) #define TRIMS_GPIO_REG_LHL GPIOC #define TRIMS_GPIO_PIN_LHL LL_GPIO_PIN_4 // PC.04 @@ -2677,6 +2703,9 @@ #if !defined(SOFTWARE_VOLUME) #define VOLUME_I2C_ADDRESS 0x2E #define VOLUME_I2C_BUS I2C_Bus_1 + + #include + int32_t getVolume(); #endif #define I2C_B1_CLK_RATE 400000 diff --git a/radio/src/targets/taranis/volume_i2c.cpp b/radio/src/targets/taranis/volume_i2c.cpp index d1ed2365cf0..48b5c942e15 100644 --- a/radio/src/targets/taranis/volume_i2c.cpp +++ b/radio/src/targets/taranis/volume_i2c.cpp @@ -57,4 +57,9 @@ void audioSetVolume(uint8_t volume) write_i2c_volume(volumeScale[volume]); } +int32_t getVolume() +{ + return read_i2c_volume(); +} + #endif diff --git a/radio/src/tests/crossfire.cpp b/radio/src/tests/crossfire.cpp index 18350b7f21d..b007b1bb391 100644 --- a/radio/src/tests/crossfire.cpp +++ b/radio/src/tests/crossfire.cpp @@ -22,12 +22,20 @@ #include "gtest/gtest.h" #include "gtests.h" #include "telemetry/telemetry.h" +#include "telemetry/crossfire.h" +#include "crc.h" #if defined(CROSSFIRE) uint8_t createCrossfireChannelsFrame(uint8_t moduleIdx, uint8_t * frame, int16_t * pulses); + +// Spec-defined part of the frame (0x16 RC Channels Packed): sync byte, type, +// 16 x 11-bit channel packing. Expected bytes computed independently, not +// derived from createCrossfireChannelsFrame() itself. TEST(Crossfire, createCrossfireChannelsFrame) { + MODEL_RESET(); + int16_t pulsesStart[MAX_TRAINER_CHANNELS]; uint8_t crossfire[CROSSFIRE_FRAME_MAXLEN]; @@ -38,7 +46,71 @@ TEST(Crossfire, createCrossfireChannelsFrame) createCrossfireChannelsFrame(EXTERNAL_MODULE, crossfire, pulsesStart); - // TODO check + ASSERT_EQ(crossfire[0], MODULE_ADDRESS); + ASSERT_EQ(crossfire[2], CHANNELS_ID); + + const uint8_t expectedChannelData[22] = { + 0xAD, 0xA0, 0x88, 0x5E, 0xC0, 0x73, 0xA4, 0x56, 0x51, 0x4C, 0x6F, + 0xE0, 0x33, 0x22, 0x2B, 0x27, 0x9A, 0x57, 0xF0, 0x1A, 0x99, 0xD5 + }; + ASSERT_EQ(memcmp(&crossfire[3], expectedChannelData, sizeof(expectedChannelData)), 0); +} + +// Status byte after the 0x16 payload is an ExpressLRS extension, not TBS CRSF +// spec (semantics per ExpressLRS's TXModuleEndpoint.cpp / crsf_protocol.h). +// Frame is always 25 bytes (1 ID + 22 channel data + 1 status + 1 CRC); +// bit 0 = commanded armed status (Switch mode only), bit 1 = arming mode is CH5. +TEST(Crossfire, ExpressLRSArmingExtension_CH5Mode) +{ + MODEL_RESET(); + + int16_t pulsesStart[MAX_TRAINER_CHANNELS]; + uint8_t crossfire[CROSSFIRE_FRAME_MAXLEN]; + + memset(crossfire, 0, sizeof(crossfire)); + for (int i=0; i not armed, bit 1 clear (Switch mode) + + uint8_t crc = crc8(&crossfire[2], 24); + ASSERT_EQ(crossfire[26], crc); } TEST(Crossfire, crc8) diff --git a/radio/src/tests/lua.cpp b/radio/src/tests/lua.cpp index 2b7accb094c..830f053def2 100644 --- a/radio/src/tests/lua.cpp +++ b/radio/src/tests/lua.cpp @@ -197,6 +197,46 @@ TEST(Lua, Switches) #endif } +TEST(Lua, testFloatIntegerEquality) +{ + // 0.5 is not an integer, so it must not equal 0 (regression #7587) + // both directions asserted explicitly so the intent is obvious at a glance + luaExecStr("if 0.5 == 0 then error('0.5 == 0') end"); + luaExecStr("if not (0.5 ~= 0) then error('0.5 ~= 0') end"); + luaExecStr("if 0.50 == 0 then error('0.50 == 0') end"); + luaExecStr("if not (0.50 ~= 0) then error('0.50 ~= 0') end"); + luaExecStr("if 0.50 == 0.0 then error('0.50 == 0.0') end"); + // ... even when the value comes from a variable, as in the reported issue + luaExecStr("local v = 0.50; if v == 0 then error('v == 0') end"); + luaExecStr("local v = 0.50; if not (v ~= 0) then error('v ~= 0') end"); + // negative non-integral floats must not equal integers either + luaExecStr("if -0.5 == 0 then error('-0.5 == 0') end"); + luaExecStr("if not (-0.5 ~= 0) then error('-0.5 ~= 0') end"); + luaExecStr("if not (-0.5 < 0) then error('-0.5 < 0') end"); + luaExecStr("if -0.5 > 0 then error('-0.5 > 0') end"); + // integral floats still compare equal to their integer counterpart + luaExecStr("if 1.0 ~= 1 then error('1.0 ~= 1') end"); + luaExecStr("if 0.0 ~= 0 then error('0.0 ~= 0') end"); + luaExecStr("if -1.0 ~= -1 then error('-1.0 ~= -1') end"); + // order comparisons on the same values must remain consistent + luaExecStr("if not (0.5 >= 0) then error('0.5 >= 0') end"); + luaExecStr("if 0.5 <= 0 then error('0.5 <= 0') end"); + luaExecStr("if not (0.5 > 0) then error('0.5 > 0') end"); + luaExecStr("if 0.5 < 0 then error('0.5 < 0') end"); + luaExecStr("if not (0.50 >= 0) then error('0.50 >= 0') end"); + luaExecStr("if 0.50 <= 0 then error('0.50 <= 0') end"); + luaExecStr("if not (1.0 >= 1) then error('1.0 >= 1') end"); + luaExecStr("if not (1.0 <= 1) then error('1.0 <= 1') end"); + luaExecStr("if not (0.0 >= 0) then error('0.0 >= 0') end"); + luaExecStr("if not (0.0 <= 0) then error('0.0 <= 0') end"); + // mixed arithmetic must still yield floats; the fix only affects equality + luaExecStr("if math.type(0.5 + 0) ~= 'float' then error('0.5 + 0') end"); + luaExecStr("if math.type(1.0 + 1) ~= 'float' then error('1.0 + 1') end"); + luaExecStr("if math.type(1 / 2) ~= 'float' then error('1 / 2') end"); + luaExecStr("if math.type(7.5 % 2) ~= 'float' then error('7.5 % 2') end"); + luaExecStr("if math.type(0.5 * 2) ~= 'float' then error('0.5 * 2') end"); +} + TEST(Lua, testLegacyNames) { MODEL_RESET(); diff --git a/radio/src/thirdparty/Lua/src/lvm.c b/radio/src/thirdparty/Lua/src/lvm.c index c186e5e7c26..7a2d5a6ed39 100644 --- a/radio/src/thirdparty/Lua/src/lvm.c +++ b/radio/src/thirdparty/Lua/src/lvm.c @@ -401,7 +401,7 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { return 0; /* only numbers can be equal with different variants */ else { /* two numbers with different variants */ lua_Integer i1, i2; /* compare them as integers */ - return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); + return (tointegerexact(t1, &i1) && tointegerexact(t2, &i2) && i1 == i2); } } /* values have same type and same variant */ diff --git a/radio/src/thirdparty/Lua/src/lvm.h b/radio/src/thirdparty/Lua/src/lvm.h index a8f954f04ce..c066bc07f28 100644 --- a/radio/src/thirdparty/Lua/src/lvm.h +++ b/radio/src/thirdparty/Lua/src/lvm.h @@ -43,6 +43,15 @@ #define tointeger(o,i) \ (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,LUA_FLOORN2I)) +/* non-soft conversion: only integral values are converted. +** This is the strict semantic used by equality ('==' and '~='), where a +** non-integral float such as 0.5 must not compare equal to an integer. +** 'tointeger' (soft, LUA_FLOORN2I) is intentionally kept for API argument +** coercion so that EdgeTX API functions accept unrounded floats; do not +** switch this macro back to the soft conversion. */ +#define tointegerexact(o,i) \ + (ttisinteger(o) ? (*(i) = ivalue(o), 1) : luaV_tointeger(o,i,0)) + #define intop(op,v1,v2) l_castU2S(l_castS2U(v1) op l_castS2U(v2)) #define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) diff --git a/radio/src/translations/bl_translations.h b/radio/src/translations/bl_translations.h index ac45c25a6b5..9a843ebaf46 100644 --- a/radio/src/translations/bl_translations.h +++ b/radio/src/translations/bl_translations.h @@ -63,6 +63,8 @@ #define TR_BL_DIR_EMPTY "Adresar je prazdny" #define TR_BL_WRITING_FW "Nahravani firmware ..." #define TR_BL_WRITING_COMPL "Nahravani dokonceno" + #define TR_BL_WRITING_FAILED TR("Zapis selhal!", "Zapis se nezdaril!") + #define TR_BL_RETRY_OR_DFU TR("Zkuste znovu / DFU", "Zkuste znovu, nebo pouzijte USB (DFU)") #define TR_BL_ENABLE "Povoleno" #define TR_BL_DISABLE "Zakazano" @@ -109,6 +111,8 @@ #define TR_BL_DIR_EMPTY "Katalog er tomt" #define TR_BL_WRITING_FW "Installerer..." #define TR_BL_WRITING_COMPL "Installation slut" + #define TR_BL_WRITING_FAILED TR("Skrivning fejlede!", "Skrivning mislykkedes!") + #define TR_BL_RETRY_OR_DFU TR("Prov igen / DFU", "Prov igen, eller brug USB (DFU)") #define TR_BL_ENABLE "Aktiver" #define TR_BL_DISABLE "Deaktiver" @@ -155,6 +159,8 @@ #define TR_BL_DIR_EMPTY "Verzeichnis leer" #define TR_BL_WRITING_FW "Schreibe..." #define TR_BL_WRITING_COMPL TR("Schreiben fertig","Schreiben abgeschlossen") + #define TR_BL_WRITING_FAILED TR("Schreiben Fehler!", "Schreiben fehlgeschlagen!") + #define TR_BL_RETRY_OR_DFU TR("Neu vers. / DFU", "Erneut versuchen, oder USB (DFU)") #define TR_BL_ENABLE "Aktivieren" #define TR_BL_DISABLE "Deaktivieren" @@ -201,6 +207,8 @@ #define TR_BL_DIR_EMPTY "Repertoire vide" #define TR_BL_WRITING_FW "Ecriture Firmware ..." #define TR_BL_WRITING_COMPL "Ecriture terminée" + #define TR_BL_WRITING_FAILED TR("Echec ecriture!", "Echec de l'ecriture!") + #define TR_BL_RETRY_OR_DFU TR("Reessayer / DFU", "Reessayez, ou utilisez USB (DFU)") #define TR_BL_ENABLE "Activer" #define TR_BL_DISABLE "Désactiver" @@ -247,6 +255,8 @@ #define TR_BL_DIR_EMPTY "Cartella vuota" #define TR_BL_WRITING_FW "Scrittura..." #define TR_BL_WRITING_COMPL "Scrittura completata" + #define TR_BL_WRITING_FAILED TR("Scrittura fallita!", "Scrittura fallita!") + #define TR_BL_RETRY_OR_DFU TR("Riprova / DFU", "Riprova, oppure usa USB (DFU)") #define TR_BL_ENABLE "Abilita" #define TR_BL_DISABLE "Disabilita" @@ -293,6 +303,8 @@ #define TR_BL_DIR_EMPTY "Katalog jest pusty" #define TR_BL_WRITING_FW "Zapis firmware ..." #define TR_BL_WRITING_COMPL "Zapis ukonczony" + #define TR_BL_WRITING_FAILED TR("Zapis nieudany!", "Zapis nie powiodl sie!") + #define TR_BL_RETRY_OR_DFU TR("Powtorz / DFU", "Powtorz, lub uzyj USB (DFU)") #define TR_BL_ENABLE "Enable" #define TR_BL_DISABLE "Disable" @@ -340,6 +352,8 @@ #define TR_BL_DIR_EMPTY "Katalogen aer tom" #define TR_BL_WRITING_FW "Skriver..." #define TR_BL_WRITING_COMPL "Skrivning klar" + #define TR_BL_WRITING_FAILED TR("Skrivning fel!", "Skrivningen misslyckades!") + #define TR_BL_RETRY_OR_DFU TR("Forsok igen / DFU", "Forsok igen, eller anvand USB (DFU)") #define TR_BL_ENABLE "Aktivera" #define TR_BL_DISABLE "Inaktivera" @@ -386,6 +400,8 @@ #define TR_BL_DIR_EMPTY "Directory is empty" #define TR_BL_WRITING_FW "Writing..." #define TR_BL_WRITING_COMPL "Writing complete" + #define TR_BL_WRITING_FAILED TR("Writing failed!", "Writing failed!") + #define TR_BL_RETRY_OR_DFU TR("Retry or use DFU", "Retry, or flash over USB (DFU)") #define TR_BL_ENABLE "Enable" #define TR_BL_DISABLE "Disable"