From 6aa74d2a03dbee135a66c4a93302cadd9ec60ea3 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Sun, 24 Sep 2023 19:02:57 +0200 Subject: [PATCH 01/57] feat: add bootloader support for flash erase --- .../common/arm/stm32/bootloader/boot.cpp | 40 ++++++++++++++++++- .../common/arm/stm32/bootloader/boot.h | 4 ++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/radio/src/targets/common/arm/stm32/bootloader/boot.cpp b/radio/src/targets/common/arm/stm32/bootloader/boot.cpp index a83ab59afd2..ce99a9fa3b3 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/boot.cpp +++ b/radio/src/targets/common/arm/stm32/bootloader/boot.cpp @@ -56,12 +56,17 @@ #define APP_START_ADDRESS (uint32_t)(FIRMWARE_ADDRESS + BOOTLOADER_SIZE) -#if defined(EEPROM) +#if defined(EEPROM) || defined(SPI_FLASH) #define MAIN_MENU_LEN 3 #else #define MAIN_MENU_LEN 2 #endif +#if defined(SPI_FLASH) + #include "spi_flash.h" + #define SEL_CLEAR_FLASH_STORAGE_MENU_LEN 2 +#endif + typedef void (*voidFunction)(void); #define jumpTo(addr) do { \ @@ -369,6 +374,10 @@ int bootloaderMain() memoryType = MEM_EEPROM; state = ST_DIR_CHECK; break; +#elif defined(SPI_FLASH) + case 1: + state = ST_CLEAR_FLASH_CHECK; + break; #endif default: if(vpos < bootloaderGetMenuItemCount(MAIN_MENU_LEN-1)) @@ -511,6 +520,35 @@ int bootloaderMain() else if (memoryType == MEM_EEPROM && eepromWritten >= EEPROM_SIZE) { state = ST_FLASH_DONE; // Backstop } +#endif +#if defined(SPI_FLASH) + } else if (state == ST_CLEAR_FLASH_CHECK) { + bootloaderDrawScreen(state, vpos); + if (event == EVT_KEY_REPT(KEY_DOWN) || event == EVT_KEY_FIRST(KEY_DOWN)) { + if (vpos < SEL_CLEAR_FLASH_STORAGE_MENU_LEN - 1) { vpos++; } + continue; + } + if (event == EVT_KEY_REPT(KEY_UP) || event == EVT_KEY_FIRST(KEY_UP)) { + if (vpos > 0) { vpos--; } + continue; + } + if (event == EVT_KEY_LONG(KEY_ENTER) && vpos == 0) + { + state = ST_CLEAR_FLASH; + } else if (event == EVT_KEY_BREAK(KEY_EXIT) || + (event == EVT_KEY_BREAK(KEY_ENTER) && vpos == 1) ) { + vpos = 0; + state = ST_START; + continue; + } + } else if (state == ST_CLEAR_FLASH) { + bootloaderDrawScreen(state, 0); + lcdRefresh(); + if(event != EVT_KEY_BREAK(KEY_ENTER)) + continue; + flashSpiEraseAll(); + vpos = 0; + state = ST_START; #endif } else if (state == ST_RADIO_MENU) { if(bootloaderRadioMenu(radioMenuItem, event)) diff --git a/radio/src/targets/common/arm/stm32/bootloader/boot.h b/radio/src/targets/common/arm/stm32/bootloader/boot.h index fd93b3ef567..5f747239fef 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/boot.h +++ b/radio/src/targets/common/arm/stm32/bootloader/boot.h @@ -54,6 +54,10 @@ enum BootloaderState { ST_FLASH_DONE, ST_RESTORE_MENU, ST_USB, +#if defined(SPI_FLASH) + ST_CLEAR_FLASH_CHECK, + ST_CLEAR_FLASH, +#endif ST_RADIO_MENU, ST_REBOOT, }; From 075ff2fccea40521fff9837c2730697df2350820 Mon Sep 17 00:00:00 2001 From: rotorman Date: Sun, 26 Dec 2021 19:57:08 +0100 Subject: [PATCH 02/57] feat: PL18 support --- .github/workflows/actions.yml | 5 +- .github/workflows/nightly.yml | 1 + companion/src/firmwares/boards.cpp | 46 +- companion/src/firmwares/boards.h | 13 +- companion/src/firmwares/generalsettings.cpp | 8 +- .../src/firmwares/opentx/opentxeeprom.cpp | 20 +- .../src/firmwares/opentx/opentxinterface.cpp | 15 +- .../src/images/simulator/PL18/bottom.png | Bin 0 -> 178 bytes companion/src/images/simulator/PL18/left.png | Bin 0 -> 1273 bytes companion/src/images/simulator/PL18/right.png | Bin 0 -> 1273 bytes companion/src/images/simulator/PL18/top.png | Bin 0 -> 178 bytes companion/src/simulation/CMakeLists.txt | 1 + companion/src/simulation/simulateduiwidget.h | 15 +- .../src/simulation/simulateduiwidgetPL18.cpp | 76 + .../src/simulation/simulateduiwidgetPL18.ui | 206 ++ companion/src/simulation/simulatorwidget.cpp | 3 + fw.json | 1 + radio/src/CMakeLists.txt | 4 +- radio/src/bitmaps/480x272/CMakeLists.txt | 2 + radio/src/bitmaps/480x272/splash_480x320.png | Bin 0 -> 51772 bytes .../bitmaps/480x272/splash_chr_480x320.png | Bin 0 -> 46524 bytes radio/src/boards/generic_stm32/inputs.cpp | 12 +- radio/src/cli.cpp | 6 +- radio/src/dataconstants.h | 17 +- radio/src/datastructs.h | 2 +- radio/src/datastructs_private.h | 2 +- radio/src/gui/colorlcd/radio_calibration.cpp | 2 +- radio/src/gui/colorlcd/radio_diaganas.cpp | 2 +- radio/src/gui/colorlcd/radio_diagkeys.cpp | 5 + .../colorlcd/radio_ghost_module_config.cpp | 4 +- .../gui/colorlcd/radio_ghost_module_config.h | 2 +- radio/src/gui/colorlcd/radio_version.cpp | 11 +- radio/src/hal/key_driver.cpp | 2 + radio/src/hal/key_driver.h | 6 +- radio/src/keys.cpp | 1 + radio/src/myeeprom.h | 8 +- radio/src/opentx.h | 2 +- radio/src/opentx_types.h | 1 + radio/src/pulses/multi.cpp | 7 +- radio/src/sdcard.cpp | 2 + radio/src/simu.cpp | 4 +- radio/src/storage/yaml/CMakeLists.txt | 2 + radio/src/storage/yaml/yaml_datastructs.cpp | 2 + .../storage/yaml/yaml_datastructs_funcs.cpp | 2 +- .../storage/yaml/yaml_datastructs_pl18.cpp | 902 +++++ .../arm/stm32/bootloader/CMakeLists.txt | 15 +- .../common/arm/stm32/bootloader/boot.cpp | 2 +- .../targets/common/arm/stm32/pwr_driver.cpp | 2 + radio/src/targets/common/arm/stm32/usb_bsp.c | 4 +- radio/src/targets/common/arm/stm32/usb_conf.h | 4 + .../targets/common/arm/stm32/usb_driver.cpp | 2 + radio/src/targets/horus/CMakeLists.txt | 1 - radio/src/targets/nv14/CMakeLists.txt | 1 - radio/src/targets/nv14/board.cpp | 4 +- radio/src/targets/nv14/board.h | 14 +- radio/src/targets/pl18/CMakeLists.txt | 160 + radio/src/targets/pl18/backlight_driver.cpp | 96 + radio/src/targets/pl18/battery_driver.cpp | 438 +++ radio/src/targets/pl18/battery_driver.h | 60 + radio/src/targets/pl18/board.cpp | 284 ++ radio/src/targets/pl18/board.h | 271 ++ .../src/targets/pl18/bootloader/boot_menu.cpp | 312 ++ radio/src/targets/pl18/extmodule_helper.cpp | 49 + radio/src/targets/pl18/hal.h | 647 ++++ radio/src/targets/pl18/haptic_driver.cpp | 63 + radio/src/targets/pl18/key_driver.cpp | 220 ++ radio/src/targets/pl18/lcd_driver.cpp | 3099 +++++++++++++++++ radio/src/targets/pl18/lcd_driver.h | 146 + radio/src/targets/pl18/libopenui_config.h | 42 + radio/src/targets/pl18/sdram_driver.c | 257 ++ .../src/targets/pl18/startup_stm32f42_43xxx.s | 565 +++ radio/src/targets/pl18/stm32_ramboot.ld | 188 + radio/src/targets/pl18/stm32f4_flash.ld | 197 ++ .../targets/pl18/stm32f4_flash_bootloader.ld | 195 ++ radio/src/targets/pl18/tp_cst340.cpp | 604 ++++ radio/src/targets/pl18/tp_cst340.h | 289 ++ radio/src/targets/simu/opentxsimulator.cpp | 2 + radio/src/targets/simu/simpgmspace.cpp | 2 +- radio/src/targets/simu/simufatfs.cpp | 1 + radio/src/targets/taranis/CMakeLists.txt | 4 - radio/src/translations/cn.h | 2 +- radio/src/translations/cz.h | 4 +- radio/src/translations/de.h | 4 +- radio/src/translations/en.h | 4 +- radio/src/translations/es.h | 4 +- radio/src/translations/fr.h | 4 +- radio/src/translations/it.h | 4 +- radio/src/translations/nl.h | 4 +- radio/src/translations/pl.h | 4 +- radio/src/translations/pt.h | 4 +- radio/util/hw_defs/hal_keys.jinja | 9 +- radio/util/hw_defs/legacy_names.py | 68 + tools/build-companion.sh | 5 +- tools/build-flysky.py | 12 +- tools/build-gh.sh | 3 + tools/commit-tests.sh | 3 + tools/generate-yaml.sh | 5 +- 97 files changed, 9684 insertions(+), 121 deletions(-) create mode 100644 companion/src/images/simulator/PL18/bottom.png create mode 100644 companion/src/images/simulator/PL18/left.png create mode 100644 companion/src/images/simulator/PL18/right.png create mode 100644 companion/src/images/simulator/PL18/top.png create mode 100644 companion/src/simulation/simulateduiwidgetPL18.cpp create mode 100644 companion/src/simulation/simulateduiwidgetPL18.ui create mode 100644 radio/src/bitmaps/480x272/splash_480x320.png create mode 100644 radio/src/bitmaps/480x272/splash_chr_480x320.png create mode 100644 radio/src/storage/yaml/yaml_datastructs_pl18.cpp create mode 100644 radio/src/targets/pl18/CMakeLists.txt create mode 100644 radio/src/targets/pl18/backlight_driver.cpp create mode 100644 radio/src/targets/pl18/battery_driver.cpp create mode 100644 radio/src/targets/pl18/battery_driver.h create mode 100644 radio/src/targets/pl18/board.cpp create mode 100644 radio/src/targets/pl18/board.h create mode 100644 radio/src/targets/pl18/bootloader/boot_menu.cpp create mode 100644 radio/src/targets/pl18/extmodule_helper.cpp create mode 100644 radio/src/targets/pl18/hal.h create mode 100644 radio/src/targets/pl18/haptic_driver.cpp create mode 100644 radio/src/targets/pl18/key_driver.cpp create mode 100644 radio/src/targets/pl18/lcd_driver.cpp create mode 100644 radio/src/targets/pl18/lcd_driver.h create mode 100644 radio/src/targets/pl18/libopenui_config.h create mode 100644 radio/src/targets/pl18/sdram_driver.c create mode 100644 radio/src/targets/pl18/startup_stm32f42_43xxx.s create mode 100644 radio/src/targets/pl18/stm32_ramboot.ld create mode 100644 radio/src/targets/pl18/stm32f4_flash.ld create mode 100644 radio/src/targets/pl18/stm32f4_flash_bootloader.ld create mode 100644 radio/src/targets/pl18/tp_cst340.cpp create mode 100644 radio/src/targets/pl18/tp_cst340.h diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 4fcb62a870f..af6e5946e4f 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -46,6 +46,7 @@ jobs: - tx16s - nv14 - el18 + - pl18 - t12 - t16 - t18 @@ -90,9 +91,9 @@ jobs: matrix: target: - nv14;el18 + - pl18 - t12 - - t16 - - t18 + - t16;t18 - t8;zorro;pocket;mt12;commando8 - tlite;tpro;tprov2;lr3pro - t20 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 009f2147320..1e0e2d15d2e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -16,6 +16,7 @@ jobs: matrix: target: - nv14;el18 + - pl18 - t12 - t16 - t18 diff --git a/companion/src/firmwares/boards.cpp b/companion/src/firmwares/boards.cpp index 08e058ac863..39b099c501b 100644 --- a/companion/src/firmwares/boards.cpp +++ b/companion/src/firmwares/boards.cpp @@ -110,7 +110,9 @@ uint32_t Boards::getFourCC(Type board) case BOARD_FLYSKY_NV14: return 0x3A78746F; case BOARD_FLYSKY_EL18: - return 0x3A78746F; // TODO: check this + return 0x3A78746F; + case BOARD_FLYSKY_PL18: + return 0x4878746F; default: return 0; } @@ -159,6 +161,7 @@ int Boards::getEEpromSize(Board::Type board) case BOARD_RADIOMASTER_TX16S: case BOARD_FLYSKY_NV14: case BOARD_FLYSKY_EL18: + case BOARD_FLYSKY_PL18: return 0; default: return 0; @@ -206,6 +209,7 @@ int Boards::getFlashSize(Type board) case BOARD_RADIOMASTER_TX16S: case BOARD_FLYSKY_NV14: case BOARD_FLYSKY_EL18: + case BOARD_FLYSKY_PL18: return FSIZE_HORUS; case BOARD_UNKNOWN: return FSIZE_MAX; @@ -419,6 +423,20 @@ SwitchInfo Boards::getSwitchInfo(Board::Type board, int index) if (index < DIM(switches)) return switches[index]; } + else if (IS_FLYSKY_PL18(board)) { + const Board::SwitchInfo switches[] = { + {SWITCH_2POS, "SA"}, + {SWITCH_3POS, "SB"}, + {SWITCH_2POS, "SC"}, + {SWITCH_3POS, "SD"}, + {SWITCH_3POS, "SE"}, + {SWITCH_2POS, "SF"}, + {SWITCH_3POS, "SG"}, + {SWITCH_3POS, "SH"} + }; + if (index < DIM(switches)) + return switches[index]; + } else if (IS_FAMILY_HORUS_OR_T16(board)) { const Board::SwitchInfo switches[] = { {SWITCH_3POS, "SA"}, @@ -497,6 +515,8 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) return 7; else if (IS_HORUS_X12S(board)) return 3; + else if (IS_FLYSKY_PL18(board)) + return 3; else return 3; @@ -509,7 +529,7 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) case Sliders: if (IS_HORUS_X12S(board) || IS_TARANIS_X9E(board) || IS_JUMPER_T20(board)) return 4; - else if (IS_TARANIS_X9D(board) || IS_HORUS_X10(board) || IS_FAMILY_T16(board)) + else if (IS_TARANIS_X9D(board) || IS_HORUS_X10(board) || IS_FAMILY_T16(board) || IS_FLYSKY_PL18(board)) return 2; else return 0; @@ -531,7 +551,7 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) getCapability(board, Board::MouseAnalogs) + getCapability(board, Board::GyroAnalogs); case MultiposPots: - if (IS_HORUS_OR_TARANIS(board) && !(IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board))) + if (IS_HORUS_OR_TARANIS(board) && !(IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board) || IS_FLYSKY_PL18(board))) return getCapability(board, Board::Pots); else return 0; @@ -558,6 +578,8 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) return 6; else if (board == BOARD_FLYSKY_NV14 || board == BOARD_FLYSKY_EL18) return 8; + else if (board == BOARD_FLYSKY_PL18) + return 8; else if (board == BOARD_RADIOMASTER_TX12_MK2 || board == BOARD_RADIOMASTER_BOXER || board == BOARD_JUMPER_TPRO) return 6; else if (board == BOARD_RADIOMASTER_POCKET) @@ -600,7 +622,7 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) return getCapability(board, Board::Switches); case SwitchPositions: - if (IS_HORUS_OR_TARANIS(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board)) + if (IS_HORUS_OR_TARANIS(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board) || IS_FLYSKY_PL18(board)) return getCapability(board, Board::Switches) * 3; else return 9; @@ -610,7 +632,9 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) case NumTrims: - if (IS_FAMILY_HORUS_OR_T16(board) && !(IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board))) + if (IS_FLYSKY_PL18(board)) + return 8; + else if (IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board)) return 6; else if (IS_IFLIGHT_COMMANDO8(board)) return 0; @@ -626,7 +650,7 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) return IS_STM32(board) ? true : false; case HasColorLcd: - return IS_FAMILY_HORUS_OR_T16(board); + return IS_FAMILY_HORUS_OR_T16(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board); case HasSDCard: return IS_STM32(board); @@ -833,6 +857,14 @@ StringTagMappingTable Boards::getAnalogNamesLookupTable(Board::Type board, const {tr("TltY").toStdString(), "TILT_Y", 14}, }); } + } else if (IS_FLYSKY_PL18(board)) { + tbl.insert(tbl.end(), { + {tr("VRA").toStdString(), "POT1"}, + {tr("VRB").toStdString(), "POT2"}, + {tr("VRC").toStdString(), "POT3"}, + {tr("LS").toStdString(), "LS"}, + {tr("RS").toStdString(), "RS"}, + }); } else if (IS_HORUS_X10(board) || IS_FAMILY_T16(board)) { if (version < adcVersion) { tbl.insert(tbl.end(), { @@ -964,6 +996,8 @@ QString Boards::getBoardName(Board::Type board) return "FlySky NV14"; case BOARD_FLYSKY_EL18: return "FlySky EL18"; + case BOARD_FLYSKY_PL18: + return "FlySky PL18"; case BOARD_BETAFPV_LR3PRO: return "BETAFPV LR3PRO"; case BOARD_IFLIGHT_COMMANDO8: diff --git a/companion/src/firmwares/boards.h b/companion/src/firmwares/boards.h index 75c6473c918..b853436bdfe 100644 --- a/companion/src/firmwares/boards.h +++ b/companion/src/firmwares/boards.h @@ -69,6 +69,7 @@ namespace Board { BOARD_JUMPER_TLITE, BOARD_JUMPER_TLITE_F4, BOARD_FLYSKY_NV14, + BOARD_FLYSKY_PL18, BOARD_RADIOMASTER_ZORRO, BOARD_JUMPER_TPRO, BOARD_BETAFPV_LR3PRO, @@ -398,6 +399,11 @@ inline bool IS_FLYSKY_EL18(Board::Type board) return (board == Board::BOARD_FLYSKY_EL18); } +inline bool IS_FLYSKY_PL18(Board::Type board) +{ + return (board == Board::BOARD_FLYSKY_PL18); +} + inline bool IS_TARANIS_XLITE(Board::Type board) { return board == Board::BOARD_TARANIS_XLITE || board == Board::BOARD_TARANIS_XLITES; @@ -475,7 +481,9 @@ inline bool IS_FAMILY_HORUS(Board::Type board) inline bool IS_FAMILY_HORUS_OR_T16(Board::Type board) { - return IS_FAMILY_HORUS(board) || IS_FAMILY_T16(board) || IS_FLYSKY_NV14(board)/*generally*/ || IS_FLYSKY_EL18(board)/*generally*/; + return IS_FAMILY_HORUS(board) || IS_FAMILY_T16(board) || + IS_FLYSKY_NV14(board)/*generally*/ || IS_FLYSKY_EL18(board)/*generally*/ + || IS_FLYSKY_PL18(board); } inline bool IS_HORUS_OR_TARANIS(Board::Type board) @@ -485,7 +493,8 @@ inline bool IS_HORUS_OR_TARANIS(Board::Type board) inline bool IS_STM32(Board::Type board) { - return IS_TARANIS(board) || IS_FAMILY_HORUS_OR_T16(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board); + return IS_TARANIS(board) || IS_FAMILY_HORUS_OR_T16(board) || + IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board) || IS_FLYSKY_PL18(board); } inline bool IS_ARM(Board::Type board) diff --git a/companion/src/firmwares/generalsettings.cpp b/companion/src/firmwares/generalsettings.cpp index b4cc9e80b55..e915150ff79 100644 --- a/companion/src/firmwares/generalsettings.cpp +++ b/companion/src/firmwares/generalsettings.cpp @@ -154,6 +154,8 @@ void GeneralSettings::init() strcpy(bluetoothName, "t16"); else if (IS_FLYSKY_NV14(board)) strcpy(bluetoothName, "nv14"); + else if (IS_FLYSKY_PL18(board)) + strcpy(bluetoothName, "pl18"); else if (IS_FAMILY_HORUS_OR_T16(board)) strcpy(bluetoothName, "horus"); else if (IS_TARANIS_X9E(board) || IS_TARANIS_SMALL(board)) @@ -285,7 +287,7 @@ void GeneralSettings::setDefaultControlTypes(Board::Type board) return; // TODO: move to Boards, like with switches - if (IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board)) { + if (IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board) && !IS_FLYSKY_PL18(board)) { potConfig[0] = Board::POT_WITH_DETENT; potConfig[1] = Board::POT_MULTIPOS_SWITCH; potConfig[2] = Board::POT_WITH_DETENT; @@ -294,6 +296,10 @@ void GeneralSettings::setDefaultControlTypes(Board::Type board) potConfig[0] = Board::POT_WITHOUT_DETENT; potConfig[1] = Board::POT_WITHOUT_DETENT; } + else if (IS_FLYSKY_PL18(board)) { + potConfig[0] = Board::POT_WITHOUT_DETENT; + potConfig[1] = Board::POT_WITHOUT_DETENT; + } else if (IS_TARANIS_XLITE(board)) { potConfig[0] = Board::POT_WITHOUT_DETENT; potConfig[1] = Board::POT_WITHOUT_DETENT; diff --git a/companion/src/firmwares/opentx/opentxeeprom.cpp b/companion/src/firmwares/opentx/opentxeeprom.cpp index d5339d9982b..6e285788e6b 100644 --- a/companion/src/firmwares/opentx/opentxeeprom.cpp +++ b/companion/src/firmwares/opentx/opentxeeprom.cpp @@ -147,7 +147,7 @@ inline int MAX_XPOTS(Board::Type board, int version) inline int MAX_SLIDERS_STORAGE(Board::Type board, int version) { - if (version >= 219 && (IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board))) + if (version >= 219 && (IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board) && !IS_FLYSKY_PL18(board))) return 4; return Boards::getCapability(board, Board::Sliders); } @@ -183,7 +183,7 @@ inline int SWITCHES_CONFIG_SIZE(Board::Type board, int version) inline int MAX_MOUSE_ANALOG_SOURCES(Board::Type board, int version) { - if (IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board)) + if (IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board) && !IS_FLYSKY_PL18(board)) return 2; else return 0; @@ -211,10 +211,10 @@ inline int MAX_GYRO_ANALOGS(Board::Type board, int version) #define MAX_CURVES(board, version) ((version >= 219 || HAS_LARGE_LCD(board)) ? 32 : 16) #define MAX_GVARS(board, version) 9 #define MAX_SCRIPTS(board) (IS_FAMILY_HORUS_OR_T16(board) ? 9 : 7) -#define MAX_TELEMETRY_SENSORS(board, version) (version <= 218 ? 32 : ((IS_FAMILY_HORUS_OR_T16(board) || IS_TARANIS_X9(board) || IS_FLYSKY_NV14(board)) ? 60 : 40)) +#define MAX_TELEMETRY_SENSORS(board, version) (version <= 218 ? 32 : ((IS_FAMILY_HORUS_OR_T16(board) || IS_TARANIS_X9(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) ? 60 : 40)) #define NUM_PPM_INPUTS(board, version) 16 #define ROTENC_COUNT(board, version) ((IS_STM32(board) && version >= 218) ? 0 : 1) -#define MAX_AUX_TRIMS(board) ((IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board)) ? 2 : 0) +#define MAX_AUX_TRIMS(board) ((IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board) && !IS_FLYSKY_PL18(board)) ? 2 : 0) #define MAX_SOURCE_TYPE_SPECIAL(board, version) SOURCE_TYPE_SPECIAL_COUNT inline int switchIndex(int i, Board::Type board, unsigned int version) @@ -2608,7 +2608,7 @@ class TopBarField: public StructField { TopBarField(DataField * parent, TopBarPersistentData & topBar, Board::Type board, unsigned int version): StructField(parent, "Top Bar") { - Append(new WidgetsContainerPersistentField(this, topBar, IS_FLYSKY_NV14(board) ? 2 : 4, MAX_TOPBAR_OPTIONS, board, version)); + Append(new WidgetsContainerPersistentField(this, topBar, (IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) ? 2 : 4, MAX_TOPBAR_OPTIONS, board, version)); //dump(); } }; @@ -2910,7 +2910,7 @@ void OpenTxModelData::beforeExport() // TODO remove when enum not radio specific requires eeprom change and conversion // Note: this must mirror reverse afterImport - if (!IS_FLYSKY_NV14(board)) + if (!IS_FLYSKY_NV14(board) && !IS_FLYSKY_PL18(board)) modelData.trainerMode -= 1; if (modelData.trainerMode > TRAINER_MODE_SLAVE_JACK) { @@ -2957,7 +2957,7 @@ void OpenTxModelData::afterImport() // TODO remove when enum not radio specific requires eeprom change and conversion // Note: this must mirror reverse beforeExport - if (!IS_FLYSKY_NV14(board)) + if (!IS_FLYSKY_NV14(board) && !IS_FLYSKY_PL18(board)) modelData.trainerMode += 1; if (modelData.trainerMode > TRAINER_MODE_SLAVE_JACK) { @@ -3003,7 +3003,7 @@ OpenTxGeneralData::OpenTxGeneralData(GeneralSettings & generalData, Board::Type internalField.Append(new UnsignedField<16>(this, chkSum)); - if (!IS_FAMILY_HORUS_OR_T16(board) || (IS_FLYSKY_NV14(board))) { + if (!IS_FAMILY_HORUS_OR_T16(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) { internalField.Append(new UnsignedField<8>(this, generalData.currModelIndex)); internalField.Append(new UnsignedField<8>(this, generalData.contrast)); } @@ -3067,11 +3067,11 @@ OpenTxGeneralData::OpenTxGeneralData(GeneralSettings & generalData, Board::Type internalField.Append(new SignedField<8>(this, generalData.PPM_Multiplier)); internalField.Append(new SignedField<8>(this, generalData.hapticLength)); - if (version < 218 || (!IS_TARANIS(board) && !IS_FAMILY_HORUS_OR_T16(board)) || IS_FLYSKY_NV14(board)) { + if (version < 218 || (!IS_TARANIS(board) && !IS_FAMILY_HORUS_OR_T16(board)) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) { internalField.Append(new UnsignedField<8>(this, generalData.reNavigation)); } - if ((!IS_TARANIS(board) && !IS_FAMILY_HORUS_OR_T16(board)) || IS_FLYSKY_NV14(board)) { + if ((!IS_TARANIS(board) && !IS_FAMILY_HORUS_OR_T16(board)) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) { internalField.Append(new UnsignedField<8>(this, generalData.stickReverse)); } diff --git a/companion/src/firmwares/opentx/opentxinterface.cpp b/companion/src/firmwares/opentx/opentxinterface.cpp index 34d441279c0..ca8c88377b7 100644 --- a/companion/src/firmwares/opentx/opentxinterface.cpp +++ b/companion/src/firmwares/opentx/opentxinterface.cpp @@ -124,6 +124,8 @@ const char * OpenTxEepromInterface::getName() return "EdgeTX for FlySky NV14"; case BOARD_FLYSKY_EL18: return "EdgeTX for FlySky EL18"; + case BOARD_FLYSKY_PL18: + return "EdgeTX for FlySky PL18"; case BOARD_BETAFPV_LR3PRO: return "EdgeTx for BETAFPV LR3PRO"; case BOARD_IFLIGHT_COMMANDO8: @@ -664,6 +666,8 @@ int OpenTxFirmware::getCapability(::Capability capability) case LcdWidth: if (IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board)) return 320; + else if (IS_FLYSKY_PL18(board)) + return 480; else if (IS_FAMILY_HORUS_OR_T16(board)) return 480; else if (IS_TARANIS_SMALL(board)) @@ -675,6 +679,8 @@ int OpenTxFirmware::getCapability(::Capability capability) case LcdHeight: if (IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board)) return 480; + else if (IS_FLYSKY_PL18(board)) + return 320; else if (IS_FAMILY_HORUS_OR_T16(board)) return 272; else @@ -777,7 +783,7 @@ int OpenTxFirmware::getCapability(::Capability capability) IS_JUMPER_TPRO(board) || IS_RADIOMASTER_TX12_MK2(board) || IS_RADIOMASTER_BOXER(board) || IS_RADIOMASTER_POCKET(board); case HasBluetooth: return (IS_FAMILY_HORUS_OR_T16(board) || IS_TARANIS_X7(board) || IS_TARANIS_XLITE(board)|| IS_TARANIS_X9E(board) || - IS_TARANIS_X9DP_2019(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board)) ? true : false; + IS_TARANIS_X9DP_2019(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board) || IS_FLYSKY_PL18(board)) ? true : false; case HasADCJitterFilter: return IS_HORUS_OR_TARANIS(board); case HasTelemetryBaudrate: @@ -1244,6 +1250,13 @@ void registerOpenTxFirmwares() addOpenTxRfOptions(firmware, FLEX + AFHDS2A + AFHDS3); registerOpenTxFirmware(firmware); + /* FlySky PL18 board */ + firmware = new OpenTxFirmware(FIRMWAREID("pl18"), QCoreApplication::translate("Firmware", "FlySky PL18"), BOARD_FLYSKY_PL18); + addOpenTxFrskyOptions(firmware); + firmware->addOption("bluetooth", Firmware::tr("Support for bluetooth module")); + addOpenTxRfOptions(firmware, FLEX + AFHDS3); + registerOpenTxFirmware(firmware); + /* FrSky Horus X10 board */ firmware = new OpenTxFirmware(FIRMWAREID("x10"), Firmware::tr("FrSky Horus X10 / X10S"), BOARD_X10); addOpenTxFrskyOptions(firmware); diff --git a/companion/src/images/simulator/PL18/bottom.png b/companion/src/images/simulator/PL18/bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc10a49d42fe209dea8c83e3b8a9eeae29e6b43 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Yoxj5K>M(rM(rsetupUi(this); + + // add actions in order of appearance on the help menu + + // Note: the PL18 has no physical buttons though at some point the trim joystick is repurposed + // allow for colorlcd key events and see what works + // the mouse click areas do not map to visual buttons on the background images + + act = new RadioUiAction(3, QList() << Qt::Key_Up, SIMU_STR_HLP_KEY_UP, SIMU_STR_HLP_ACT_MDL); + addRadioWidget(ui->rightbuttons->addArea(QRect(10, 1, 80, 35), "PL18/left.png", act)); + + m_mouseMidClickAction = new RadioUiAction(2, QList() << Qt::Key_Enter << Qt::Key_Return, SIMU_STR_HLP_KEYS_ACTIVATE, SIMU_STR_HLP_ACT_ROT_DN); + addRadioWidget(ui->rightbuttons->addArea(QRect(10, 40, 80, 35), "PL18/left.png", m_mouseMidClickAction)); + + act = new RadioUiAction(6, QList() << Qt::Key_Left, SIMU_STR_HLP_KEY_LFT, SIMU_STR_HLP_ACT_SYS); + addRadioWidget(ui->leftbuttons->addArea(QRect(10, 80, 80, 35), "PL18/left.png", act)); + + act = new RadioUiAction(5, QList() << Qt::Key_Right, SIMU_STR_HLP_KEY_RGT, SIMU_STR_HLP_ACT_TELE); + addRadioWidget(ui->leftbuttons->addArea(QRect(10, 120, 80, 35), "PL18/left.png", act)); + + act = new RadioUiAction(1, QList() << Qt::Key_PageDown, SIMU_STR_HLP_KEY_PGDN, SIMU_STR_HLP_ACT_PGDN); + addRadioWidget(ui->leftbuttons->addArea(QRect(10, 160, 80, 35), "PL18/left.png", act)); + + act = new RadioUiAction(0, QList() << Qt::Key_PageUp, SIMU_STR_HLP_KEY_PGUP, SIMU_STR_HLP_ACT_PGUP); + addRadioWidget(ui->leftbuttons->addArea(QRect(10, 200, 80, 35), "PL18/left.png", act)); + + act = new RadioUiAction(4, QList() << Qt::Key_Down << Qt::Key_Delete << Qt::Key_Escape << Qt::Key_Backspace, + SIMU_STR_HLP_KEY_DN % "
" % SIMU_STR_HLP_KEYS_EXIT, SIMU_STR_HLP_ACT_RTN); + addRadioWidget(ui->leftbuttons->addArea(QRect(10, 240, 80, 35), "PL18/left.png", act)); + + m_scrollUpAction = new RadioUiAction(-1, QList() << Qt::Key_Minus, SIMU_STR_HLP_KEY_MIN % "|" % SIMU_STR_HLP_MOUSE_UP, SIMU_STR_HLP_ACT_ROT_LFT); + m_scrollDnAction = new RadioUiAction(-1, QList() << Qt::Key_Plus << Qt::Key_Equal, SIMU_STR_HLP_KEY_PLS % "|" % SIMU_STR_HLP_MOUSE_DN, SIMU_STR_HLP_ACT_ROT_RGT); + connectScrollActions(); + + addRadioWidget(ui->leftbuttons->addArea(QRect(10, 280, 30, 30), "PL18/left.png", m_screenshotAction)); + + m_backlightColors << QColor(47, 123, 227); + + setLcd(ui->lcd); +} + +SimulatedUIWidgetPL18::~SimulatedUIWidgetPL18() +{ + delete ui; +} diff --git a/companion/src/simulation/simulateduiwidgetPL18.ui b/companion/src/simulation/simulateduiwidgetPL18.ui new file mode 100644 index 00000000000..c1bbcaedaf4 --- /dev/null +++ b/companion/src/simulation/simulateduiwidgetPL18.ui @@ -0,0 +1,206 @@ + + + SimulatedUIWidgetPL18 + + + + 0 + 0 + 520 + 500 + + + + + 0 + 0 + + + + + 520 + 500 + + + + + 520 + 500 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 100 + 500 + + + + + 100 + 500 + + + + background:url(:/images/simulator/PL18/right.png) + + + + + + + + 0 + 0 + + + + + 480 + 320 + + + + + 480 + 320 + + + + + 5 + + + + + + + + + 0 + 0 + + + + + 100 + 500 + + + + + 100 + 500 + + + + true + + + background:url(:/images/simulator/PL18/left.png); + + + + + + + + 0 + 0 + + + + + 320 + 10 + + + + + 320 + 10 + + + + + 5 + + + + background:url(:/images/simulator/PL18/top.png) + + + + + + + + 0 + 0 + + + + + 320 + 10 + + + + + 320 + 10 + + + + + 5 + false + + + + background:url(:/images/simulator/PL18/bottom.png) + + + + + + + + LcdWidget + QWidget +
lcdwidget.h
+ 1 +
+ + ButtonsWidget + QWidget +
buttonswidget.h
+ 1 +
+
+ + +
diff --git a/companion/src/simulation/simulatorwidget.cpp b/companion/src/simulation/simulatorwidget.cpp index dc0d34acc26..36f30b2586c 100644 --- a/companion/src/simulation/simulatorwidget.cpp +++ b/companion/src/simulation/simulatorwidget.cpp @@ -133,6 +133,9 @@ SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface * simulato case Board::BOARD_FLYSKY_EL18: radioUiWidget = new SimulatedUIWidgetEL18(simulator, this); break; + case Board::BOARD_FLYSKY_PL18: + radioUiWidget = new SimulatedUIWidgetPL18(simulator, this); + break; default: radioUiWidget = new SimulatedUIWidget9X(simulator, this); break; diff --git a/fw.json b/fw.json index 728a2188278..00c53fe8f08 100644 --- a/fw.json +++ b/fw.json @@ -3,6 +3,7 @@ ["BETAFPV LiteRadio 3 Pro", "lr3pro-"], ["Flysky EL18", "el18-"], ["Flysky NV14", "nv14-"], + ["Flysky PL18", "pl18-"], ["FrSky Horus X10", "x10-"], ["FrSky Horus X10 Access", "x10-access-"], ["FrSky Horus X12s", "x12s-"], diff --git a/radio/src/CMakeLists.txt b/radio/src/CMakeLists.txt index 577889d7d4a..e72fe13f0fd 100644 --- a/radio/src/CMakeLists.txt +++ b/radio/src/CMakeLists.txt @@ -1,7 +1,7 @@ include(CMakeForceCompiler) include(Bitmaps) -set(PCB_TYPES X9LITE X9LITES X7 XLITE XLITES X9D X9D+ X9E X10 X12S NV14) +set(PCB_TYPES X9LITE X9LITES X7 XLITE XLITES X9D X9D+ X9E X10 X12S NV14 PL18) set(RADIO_LANGUAGES CN CZ DA DE EN ES FI FR HE IT JP PT RU SK SE PL HU NL TW) set(TTS_LANGUAGES CN CZ DA DE EN ES FR HE IT JP PT RU SK SE PL HU NL) @@ -85,6 +85,8 @@ if(PCB STREQUAL X12S OR PCB STREQUAL X10) include(targets/horus/CMakeLists.txt) elseif(PCB STREQUAL NV14) include(targets/nv14/CMakeLists.txt) +elseif(PCB STREQUAL PL18) + include(targets/pl18/CMakeLists.txt) elseif(PCB STREQUAL X9E OR PCB STREQUAL X9D+ OR PCB STREQUAL X9D OR PCB STREQUAL X7 OR PCB STREQUAL X9LITE OR PCB STREQUAL X9LITES OR PCB STREQUAL XLITE OR PCB STREQUAL XLITES) include(targets/taranis/CMakeLists.txt) else() diff --git a/radio/src/bitmaps/480x272/CMakeLists.txt b/radio/src/bitmaps/480x272/CMakeLists.txt index a1458053abd..6c0f81f6b0a 100644 --- a/radio/src/bitmaps/480x272/CMakeLists.txt +++ b/radio/src/bitmaps/480x272/CMakeLists.txt @@ -4,6 +4,8 @@ set(MASK_ARGS ${BITMAP_SIZE_ARGS}) if(PCB STREQUAL NV14) set(BITMAP_TARGET_PREFIX nv14) +elseif(PCB STREQUAL PL18) + set(BITMAP_TARGET_PREFIX pl18) elseif(PCB STREQUAL X12S) set(BITMAP_TARGET_PREFIX x12s) else() diff --git a/radio/src/bitmaps/480x272/splash_480x320.png b/radio/src/bitmaps/480x272/splash_480x320.png new file mode 100644 index 0000000000000000000000000000000000000000..43e59de80d46ccbc32677de7ab94ccf9e7344ee9 GIT binary patch literal 51772 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 P00000NkvXXu0mjflbqc( literal 0 HcmV?d00001 diff --git a/radio/src/bitmaps/480x272/splash_chr_480x320.png b/radio/src/bitmaps/480x272/splash_chr_480x320.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc10bc0b21c24226f44c783a137be92ec1dd1b8 GIT binary patch literal 46524 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 literal 0 HcmV?d00001 diff --git a/radio/src/boards/generic_stm32/inputs.cpp b/radio/src/boards/generic_stm32/inputs.cpp index 385400e03f0..0af70b757ea 100644 --- a/radio/src/boards/generic_stm32/inputs.cpp +++ b/radio/src/boards/generic_stm32/inputs.cpp @@ -26,23 +26,27 @@ #include "stm32_keys.inc" -void keysInit() +#define __weak __attribute__((weak)) + +__weak void keysInit() { _init_keys(); _init_trims(); } -uint32_t readKeys() +__weak uint32_t readKeys() { return _read_keys(); } -uint32_t readTrims() +__weak uint32_t readTrims() { uint32_t trims = _read_trims(); -#if defined(PCBXLITE) + + #if defined(PCBXLITE) if (_read_keys() & (1 << KEY_SHIFT)) return ((trims & 0x03) << 6) | ((trims & 0x0c) << 2); #endif + return trims; } diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index 8a06aaa9269..f21ca87806d 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -1040,6 +1040,7 @@ int cliSet(const char **argv) } #if defined(ENABLE_SERIAL_PASSTHROUGH) +#if defined(HARDWARE_INTERNAL_MODULE) static etx_module_state_t *spInternalModuleState = nullptr; static void spInternalModuleTx(uint8_t* buf, uint32_t len) @@ -1060,6 +1061,7 @@ static const etx_serial_init spIntmoduleSerialInitParams = { .polarity = ETX_Pol_Normal, }; +#endif // HARDWARE_INTERNAL_MODULE // TODO: use proper method instead extern bool cdcConnected; extern uint32_t usbSerialBaudRate(void*); @@ -1576,7 +1578,7 @@ int cliCrypt(const char ** argv) } #endif -#if defined(HARDWARE_TOUCH) && !defined(PCBNV14) +#if defined(HARDWARE_TOUCH) && !defined(PCBNV14) && !defined(PCBPL18) // from tp_gt911.cpp extern uint8_t tp_gt911_cfgVer; @@ -1648,7 +1650,7 @@ const CliCommand cliCommands[] = { #if defined(ACCESS_DENIED) && defined(DEBUG_CRYPT) { "crypt", cliCrypt, "" }, #endif -#if defined(HARDWARE_TOUCH) && !defined(PCBNV14) +#if defined(HARDWARE_TOUCH) && !defined(PCBNV14) && !defined(PCBPL18) { "reset_gt911", cliResetGT911, ""}, #endif { nullptr, nullptr, nullptr } /* sentinel */ diff --git a/radio/src/dataconstants.h b/radio/src/dataconstants.h index 2459207fdb2..c1e88d0b9f4 100644 --- a/radio/src/dataconstants.h +++ b/radio/src/dataconstants.h @@ -39,7 +39,7 @@ #define LABELS_LENGTH 100 // Maximum length of the label string #define LABEL_LENGTH 16 -#if defined(PCBHORUS) || defined(PCBNV14) +#if defined(PCBHORUS) || defined(PCBNV14) || defined(PCBPL18) #define MAX_MODELS 60 #define MAX_OUTPUT_CHANNELS 32 // number of real output channels CH1-CH32 #define MAX_FLIGHT_MODES 9 @@ -98,7 +98,7 @@ enum CurveType { #define MIN_POINTS_PER_CURVE 3 #define MAX_POINTS_PER_CURVE 17 -#if defined(PCBHORUS) || defined(PCBNV14) +#if defined(PCBHORUS) || defined(PCBNV14) || defined(PCBPL18) #define LEN_MODEL_NAME 15 #define LEN_TIMER_NAME 8 #define LEN_FLIGHT_MODE_NAME 10 @@ -408,10 +408,10 @@ enum PotsWarnMode { #define MAX_FLEX_SWITCHES 0 #endif -#if defined(RADIO_T20) -#define MAX_TRIMS 8 +#if NUM_TRIMS > 6 + #define MAX_TRIMS 8 #else -#define MAX_TRIMS 6 + #define MAX_TRIMS 6 #endif #define MAX_XPOTS_POSITIONS (MAX_POTS * XPOTS_MULTIPOS_COUNT) @@ -428,6 +428,13 @@ enum SwitchSources { SWSRC_FIRST_TRIM SKIP, SWSRC_LAST_TRIM SKIP = SWSRC_FIRST_TRIM + 2 * MAX_TRIMS - 1, +#if NUM_TRIMS > 6 + SWSRC_TrimT7Down, + SWSRC_TrimT7Up, + SWSRC_TrimT8Down, + SWSRC_TrimT8Up, +#endif + SWSRC_FIRST_LOGICAL_SWITCH SKIP, SWSRC_LAST_LOGICAL_SWITCH SKIP = SWSRC_FIRST_LOGICAL_SWITCH + MAX_LOGICAL_SWITCHES - 1, diff --git a/radio/src/datastructs.h b/radio/src/datastructs.h index 392aefe8fba..fba828f073a 100644 --- a/radio/src/datastructs.h +++ b/radio/src/datastructs.h @@ -87,7 +87,7 @@ static inline void check_struct() CHKSIZE(CurveHeader, 4); CHKSIZE(CustomScreenData, 852); CHKTYPE(TopBarPersistentData, 444); -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) // TODO #else // Common for all variants diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index b149e4e12c0..3c8ec69cde9 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -602,7 +602,7 @@ PACK(struct CustomScreenData { #define TOPBAR_DATA #endif -#if defined(PCBHORUS) || defined(PCBTARANIS) || defined(PCBNV14) +#if defined(PCBHORUS) || defined(PCBTARANIS) || defined(PCBNV14) || defined(PCBPL18) #define SCRIPT_DATA \ NOBACKUP(ScriptData scriptsData[MAX_SCRIPTS]); #else diff --git a/radio/src/gui/colorlcd/radio_calibration.cpp b/radio/src/gui/colorlcd/radio_calibration.cpp index 9c54cbea837..8ddba702d29 100644 --- a/radio/src/gui/colorlcd/radio_calibration.cpp +++ b/radio/src/gui/colorlcd/radio_calibration.cpp @@ -93,7 +93,7 @@ void RadioCalibrationPage::buildBody(FormWindow * window) deco->setSlidersVisible(true); deco->setFlightModeVisible(false); -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) new TextButton(window, {LCD_W - 120, LCD_H - 140, 90, 40}, "Next", [=]() -> uint8_t { nextStep(); diff --git a/radio/src/gui/colorlcd/radio_diaganas.cpp b/radio/src/gui/colorlcd/radio_diaganas.cpp index 903c32c8161..18ef7709ba9 100644 --- a/radio/src/gui/colorlcd/radio_diaganas.cpp +++ b/radio/src/gui/colorlcd/radio_diaganas.cpp @@ -177,7 +177,7 @@ class AnaCalibratedViewWindow: public AnaViewWindow { }, COLOR_THEME_PRIMARY1); lv_obj_set_grid_cell(lbl->getLvObj(), LV_GRID_ALIGN_STRETCH, 0, 5, LV_GRID_ALIGN_CENTER, 0, 1); -#if !defined(SIMU) && !defined(PCBNV14) +#if !defined(SIMU) && !defined(PCBNV14) && !defined(PCBPL18) line = newLine(grid); auto lbl2 = new StaticText(line, rect_t{}, std::string("Touch GT911 FW ver: ") + std::to_string(touchGT911fwver), COLOR_THEME_PRIMARY1); lv_obj_set_grid_cell(lbl2->getLvObj(), LV_GRID_ALIGN_STRETCH, 0, 5, LV_GRID_ALIGN_CENTER, 0, 1); diff --git a/radio/src/gui/colorlcd/radio_diagkeys.cpp b/radio/src/gui/colorlcd/radio_diagkeys.cpp index a12c102e85c..734488bcaa2 100644 --- a/radio/src/gui/colorlcd/radio_diagkeys.cpp +++ b/radio/src/gui/colorlcd/radio_diagkeys.cpp @@ -122,8 +122,13 @@ class RadioKeyDiagsWindow : public Window for (uint8_t i = 0; i < keysGetMaxTrims() * 2; i++) { coord_t y = 1 + FH + FH * (i / 2); if (i & 1) { +#if defined(PCBPL18) + dc->drawText(TRIM_COLUMN, y, "TR", COLOR_THEME_PRIMARY1); + dc->drawNumber(TRIM_COLUMN + 20, y, i / 2 + 1, COLOR_THEME_PRIMARY1); +#else dc->drawText(TRIM_COLUMN, y, "T", COLOR_THEME_PRIMARY1); dc->drawNumber(TRIM_COLUMN + 10, y, i / 2 + 1, COLOR_THEME_PRIMARY1); +#endif } displayTrimState(dc, i & 1 ? TRIM_PLUS_COLUMN : TRIM_MINUS_COLUMN, y, _trimMap[i]); } diff --git a/radio/src/gui/colorlcd/radio_ghost_module_config.cpp b/radio/src/gui/colorlcd/radio_ghost_module_config.cpp index f5976861f1c..0120f1775ff 100644 --- a/radio/src/gui/colorlcd/radio_ghost_module_config.cpp +++ b/radio/src/gui/colorlcd/radio_ghost_module_config.cpp @@ -110,12 +110,14 @@ static void ghostmoduleconfig_cb(lv_event_t* e) } } +#if defined(HARDWARE_KEYS) && !defined(PCBPL18) void RadioGhostModuleConfig::onCancel() { reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYLEFT; reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE; moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL; } +#endif RadioGhostModuleConfig::RadioGhostModuleConfig(uint8_t moduleIdx) : Page(ICON_RADIO_TOOLS), @@ -144,7 +146,7 @@ void RadioGhostModuleConfig::buildBody(FormWindow * window) new GhostModuleConfigWindow(window, {0, 0, LCD_W, LCD_H - MENU_HEADER_HEIGHT - 5}); } -#if defined(HARDWARE_KEYS) +#if defined(HARDWARE_KEYS) && !defined(PCBPL18) void RadioGhostModuleConfig::onEvent(event_t event) { switch (event) { diff --git a/radio/src/gui/colorlcd/radio_ghost_module_config.h b/radio/src/gui/colorlcd/radio_ghost_module_config.h index d80a6991559..3d758ff37ea 100644 --- a/radio/src/gui/colorlcd/radio_ghost_module_config.h +++ b/radio/src/gui/colorlcd/radio_ghost_module_config.h @@ -28,7 +28,7 @@ class RadioGhostModuleConfig: public Page public: explicit RadioGhostModuleConfig(uint8_t moduleIdx); -#if defined(HARDWARE_KEYS) +#if defined(HARDWARE_KEYS) && !defined(PCBPL18) void onEvent(event_t event) override; void checkEvents() override; void onCancel() override; diff --git a/radio/src/gui/colorlcd/radio_version.cpp b/radio/src/gui/colorlcd/radio_version.cpp index ce97935bd25..7d38e02b685 100644 --- a/radio/src/gui/colorlcd/radio_version.cpp +++ b/radio/src/gui/colorlcd/radio_version.cpp @@ -77,13 +77,14 @@ class VersionDialog : public Dialog memclear(&reusableBuffer.hardwareAndSettings.modules, sizeof(reusableBuffer.hardwareAndSettings.modules)); reusableBuffer.hardwareAndSettings.updateTime = get_tmr10ms(); - +#if defined(HARDWARE_INTERNAL_MODULE) // Query modules if (isModulePXX2(INTERNAL_MODULE) && modulePortPowered(INTERNAL_MODULE)) { moduleState[INTERNAL_MODULE].readModuleInformation( &reusableBuffer.hardwareAndSettings.modules[INTERNAL_MODULE], PXX2_HW_INFO_TX_ID, PXX2_MAX_RECEIVERS_PER_MODULE - 1); } +#endif if (isModulePXX2(EXTERNAL_MODULE) && modulePortPowered(EXTERNAL_MODULE)) { moduleState[EXTERNAL_MODULE].readModuleInformation( @@ -165,11 +166,13 @@ class VersionDialog : public Dialog void update() { +#if defined(HARDWARE_INTERNAL_MODULE) updateModule(INTERNAL_MODULE, int_name, int_module_status_w, int_status, int_rx_name_w, int_rx_name, int_rx_status_w, int_rx_status); +#endif updateModule(EXTERNAL_MODULE, ext_name, ext_module_status_w, ext_status, @@ -309,11 +312,13 @@ class VersionDialog : public Dialog { if (get_tmr10ms() >= reusableBuffer.hardwareAndSettings.updateTime) { // Query modules +#if defined(HARDWARE_INTERNAL_MODULE) if (isModulePXX2(INTERNAL_MODULE) && modulePortPowered(INTERNAL_MODULE)) { moduleState[INTERNAL_MODULE].readModuleInformation( &reusableBuffer.hardwareAndSettings.modules[INTERNAL_MODULE], PXX2_HW_INFO_TX_ID, PXX2_MAX_RECEIVERS_PER_MODULE - 1); } +#endif if (isModulePXX2(EXTERNAL_MODULE) && modulePortPowered(EXTERNAL_MODULE)) { moduleState[EXTERNAL_MODULE].readModuleInformation( &reusableBuffer.hardwareAndSettings.modules[EXTERNAL_MODULE], @@ -332,7 +337,7 @@ RadioVersionPage::RadioVersionPage(): { } -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) extern const char* boardLcdType; #endif @@ -355,7 +360,7 @@ void RadioVersionPage::build(FormWindow * window) version += options[i]; } -#if defined(PCBNV14) && !defined(SIMU) +#if (defined(PCBNV14) || defined(PCBPL18)) && !defined(SIMU) version += nl; version += "LCD: "; version += boardLcdType; diff --git a/radio/src/hal/key_driver.cpp b/radio/src/hal/key_driver.cpp index 6529bc04d70..dc500167f8e 100644 --- a/radio/src/hal/key_driver.cpp +++ b/radio/src/hal/key_driver.cpp @@ -22,6 +22,8 @@ #include "key_driver.h" #include "definitions.h" +#include "dataconstants.h" + #include "hal_keys.inc" uint32_t keysGetSupported() diff --git a/radio/src/hal/key_driver.h b/radio/src/hal/key_driver.h index f0d20baa5f5..1701071a621 100644 --- a/radio/src/hal/key_driver.h +++ b/radio/src/hal/key_driver.h @@ -50,11 +50,7 @@ enum EnumKeys { MAX_KEYS }; -#if defined(RADIO_T20) -#define MAX_TRIMS 8 -#else -#define MAX_TRIMS 6 -#endif + // returns a bit field with each key set as (1 << KEY_xxx) uint32_t readKeys(); diff --git a/radio/src/keys.cpp b/radio/src/keys.cpp index e9cd936dfa0..c997b03cc93 100644 --- a/radio/src/keys.cpp +++ b/radio/src/keys.cpp @@ -30,6 +30,7 @@ #include "timers_driver.h" #include "hal/watchdog_driver.h" #include "hal/rotary_encoder.h" +#include "dataconstants.h" // required by watchdog macro.. #if !defined(SIMU) diff --git a/radio/src/myeeprom.h b/radio/src/myeeprom.h index a1ca3c55395..69eafc19f2a 100644 --- a/radio/src/myeeprom.h +++ b/radio/src/myeeprom.h @@ -169,7 +169,13 @@ enum CurveRefType { #define TRIM_ELE (-2) #define TRIM_THR (-3) #define TRIM_AIL (-4) -#if defined(PCBHORUS) +#if defined(PCBPL18) + #define TRIM_T5 (-5) + #define TRIM_T6 (-6) + #define TRIM_T7 (-7) + #define TRIM_T8 (-8) + #define TRIM_LAST TRIM_T8 +#elif defined(PCBHORUS) #define TRIM_T5 (-5) #define TRIM_T6 (-6) #define TRIM_LAST TRIM_T6 diff --git a/radio/src/opentx.h b/radio/src/opentx.h index 101d417d255..437b2a72f58 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -203,7 +203,7 @@ extern uint8_t heartbeat; #include "keys.h" #include "pwr.h" -// #if defined(PCBFRSKY) || defined(PCBNV14) +// #if defined(PCBFRSKY) || defined(PCBNV14) || defined(PCBPL18) // extern uint8_t potsPos[NUM_XPOTS]; // #endif diff --git a/radio/src/opentx_types.h b/radio/src/opentx_types.h index 4f556d12c04..07bcd68b809 100644 --- a/radio/src/opentx_types.h +++ b/radio/src/opentx_types.h @@ -83,6 +83,7 @@ typedef uint32_t LcdFlags; typedef uint16_t event_t; typedef uint32_t tmr10ms_t; +typedef int32_t rotenc_t; typedef int32_t getvalue_t; typedef uint32_t mixsrc_t; typedef int32_t swsrc_t; diff --git a/radio/src/pulses/multi.cpp b/radio/src/pulses/multi.cpp index 27ddaf56cd2..e2fbd4bd6c7 100644 --- a/radio/src/pulses/multi.cpp +++ b/radio/src/pulses/multi.cpp @@ -116,12 +116,9 @@ static void sendFailsafeChannels(uint8_t*& p_buf, uint8_t module) static void setupPulsesMulti(uint8_t*& p_buf, uint8_t module) { static int counter[2] = {0,0}; //TODO - static uint8_t invert[2] = {0x00, //internal -#if defined(PCBTARANIS) || defined(PCBHORUS) || defined(PCBNV14) + static uint8_t invert[2] = { + 0x00, //internal 0x08 //external -#else - 0x00 //external -#endif }; uint8_t type=MULTI_NORMAL; diff --git a/radio/src/sdcard.cpp b/radio/src/sdcard.cpp index 9555ebb60ca..10a03799e8a 100644 --- a/radio/src/sdcard.cpp +++ b/radio/src/sdcard.cpp @@ -559,6 +559,8 @@ void sdDone() f_mount(nullptr, "", 0); // unmount SD } + + storageDeInit(); } uint32_t sdMounted() diff --git a/radio/src/simu.cpp b/radio/src/simu.cpp index 9523affbbbc..d1e9d9cde2a 100644 --- a/radio/src/simu.cpp +++ b/radio/src/simu.cpp @@ -348,7 +348,7 @@ long OpenTxSim::onMouseMove(FXObject*,FXSelector,void*v) void OpenTxSim::updateKeysAndSwitches(bool start) { static int keys[] = { -#if defined(PCBNV14) +#if defined(PCBFLYSKY) // no keys #elif defined(PCBHORUS) KEY_Page_Up, KEY_PAGEUP, @@ -570,7 +570,7 @@ void OpenTxSim::refreshDisplay() setPixel(x, y, color); #else #if LCD_DEPTH == 4 - pixel_t *p = &simuLcdBuf[y / 2 * LCD_W + x]; + pixel_t* p = &simuLcdBuf[y / 2 * LCD_W + x]; uint8_t z = (y & 1) ? (*p >> 4) : (*p & 0x0F); if (z) { FXColor color; diff --git a/radio/src/storage/yaml/CMakeLists.txt b/radio/src/storage/yaml/CMakeLists.txt index b320ff016a9..6a7e537bbe6 100644 --- a/radio/src/storage/yaml/CMakeLists.txt +++ b/radio/src/storage/yaml/CMakeLists.txt @@ -20,6 +20,8 @@ elseif(PCB STREQUAL X10) set(YAML_GEN_OUTPUT storage/yaml/yaml_datastructs_x10.cpp) elseif(PCB STREQUAL NV14) set(YAML_GEN_OUTPUT storage/yaml/yaml_datastructs_nv14.cpp) +elseif(PCB STREQUAL PL18) + set(YAML_GEN_OUTPUT storage/yaml/yaml_datastructs_pl18.cpp) elseif(PCB STREQUAL X7) if(PCBREV STREQUAL TPRO) set(YAML_GEN_OUTPUT storage/yaml/yaml_datastructs_tpro.cpp) diff --git a/radio/src/storage/yaml/yaml_datastructs.cpp b/radio/src/storage/yaml/yaml_datastructs.cpp index fe77e7c5220..3b1a2254cf1 100644 --- a/radio/src/storage/yaml/yaml_datastructs.cpp +++ b/radio/src/storage/yaml/yaml_datastructs.cpp @@ -34,6 +34,8 @@ #include "yaml_datastructs_x10.cpp" #elif defined(PCBNV14) #include "yaml_datastructs_nv14.cpp" +#elif defined(PCBPL18) + #include "yaml_datastructs_pl18.cpp" #elif defined(PCBX7) #if defined(RADIO_TPRO) || defined(RADIO_TPROV2) #include "yaml_datastructs_tpro.cpp" diff --git a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp index 85cec0b2301..0745654ef8f 100644 --- a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp @@ -38,7 +38,7 @@ // ======== // // If any of these static_assert() fails, you need to check that -// the functions bellow are still applicable. +// the functions below are still applicable. // // Please note that the sizes used here are those from the v220 format // (see storage/conversions/yaml/datastructs_220.h) diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp new file mode 100644 index 00000000000..b330a845a9e --- /dev/null +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -0,0 +1,902 @@ +// generated by generate_yaml.py + +// +// Enums first +// + +const struct YamlIdStr enum_BacklightMode[] = { + { e_backlight_mode_off, "backlight_mode_off" }, + { e_backlight_mode_keys, "backlight_mode_keys" }, + { e_backlight_mode_sticks, "backlight_mode_sticks" }, + { e_backlight_mode_all, "backlight_mode_all" }, + { e_backlight_mode_on, "backlight_mode_on" }, + { 0, NULL } +}; +const struct YamlIdStr enum_AntennaModes[] = { + { ANTENNA_MODE_INTERNAL, "MODE_INTERNAL" }, + { ANTENNA_MODE_ASK, "MODE_ASK" }, + { ANTENNA_MODE_PER_MODEL, "MODE_PER_MODEL" }, + { ANTENNA_MODE_EXTERNAL, "MODE_EXTERNAL" }, + { 0, NULL } +}; +const struct YamlIdStr enum_ModuleType[] = { + { MODULE_TYPE_NONE, "TYPE_NONE" }, + { MODULE_TYPE_PPM, "TYPE_PPM" }, + { MODULE_TYPE_XJT_PXX1, "TYPE_XJT_PXX1" }, + { MODULE_TYPE_ISRM_PXX2, "TYPE_ISRM_PXX2" }, + { MODULE_TYPE_DSM2, "TYPE_DSM2" }, + { MODULE_TYPE_CROSSFIRE, "TYPE_CROSSFIRE" }, + { MODULE_TYPE_MULTIMODULE, "TYPE_MULTIMODULE" }, + { MODULE_TYPE_R9M_PXX1, "TYPE_R9M_PXX1" }, + { MODULE_TYPE_R9M_PXX2, "TYPE_R9M_PXX2" }, + { MODULE_TYPE_R9M_LITE_PXX1, "TYPE_R9M_LITE_PXX1" }, + { MODULE_TYPE_R9M_LITE_PXX2, "TYPE_R9M_LITE_PXX2" }, + { MODULE_TYPE_GHOST, "TYPE_GHOST" }, + { MODULE_TYPE_R9M_LITE_PRO_PXX2, "TYPE_R9M_LITE_PRO_PXX2" }, + { MODULE_TYPE_SBUS, "TYPE_SBUS" }, + { MODULE_TYPE_XJT_LITE_PXX2, "TYPE_XJT_LITE_PXX2" }, + { MODULE_TYPE_FLYSKY_AFHDS2A, "TYPE_FLYSKY_AFHDS2A" }, + { MODULE_TYPE_FLYSKY_AFHDS3, "TYPE_FLYSKY_AFHDS3" }, + { MODULE_TYPE_LEMON_DSMP, "TYPE_LEMON_DSMP" }, + { 0, NULL } +}; +const struct YamlIdStr enum_TrainerMultiplex[] = { + { TRAINER_OFF, "OFF" }, + { TRAINER_ADD, "ADD" }, + { TRAINER_REPL, "REPL" }, + { 0, NULL } +}; +const struct YamlIdStr enum_BeeperMode[] = { + { e_mode_quiet, "mode_quiet" }, + { e_mode_alarms, "mode_alarms" }, + { e_mode_nokeys, "mode_nokeys" }, + { e_mode_all, "mode_all" }, + { 0, NULL } +}; +const struct YamlIdStr enum_BluetoothModes[] = { + { BLUETOOTH_OFF, "OFF" }, + { BLUETOOTH_TELEMETRY, "TELEMETRY" }, + { BLUETOOTH_TRAINER, "TRAINER" }, + { 0, NULL } +}; +const struct YamlIdStr enum_Functions[] = { + { FUNC_OVERRIDE_CHANNEL, "OVERRIDE_CHANNEL" }, + { FUNC_TRAINER, "TRAINER" }, + { FUNC_INSTANT_TRIM, "INSTANT_TRIM" }, + { FUNC_RESET, "RESET" }, + { FUNC_SET_TIMER, "SET_TIMER" }, + { FUNC_ADJUST_GVAR, "ADJUST_GVAR" }, + { FUNC_VOLUME, "VOLUME" }, + { FUNC_SET_FAILSAFE, "SET_FAILSAFE" }, + { FUNC_RANGECHECK, "RANGECHECK" }, + { FUNC_BIND, "BIND" }, + { FUNC_PLAY_SOUND, "PLAY_SOUND" }, + { FUNC_PLAY_TRACK, "PLAY_TRACK" }, + { FUNC_PLAY_VALUE, "PLAY_VALUE" }, + { FUNC_PLAY_SCRIPT, "PLAY_SCRIPT" }, + { FUNC_BACKGND_MUSIC, "BACKGND_MUSIC" }, + { FUNC_BACKGND_MUSIC_PAUSE, "BACKGND_MUSIC_PAUSE" }, + { FUNC_VARIO, "VARIO" }, + { FUNC_HAPTIC, "HAPTIC" }, + { FUNC_LOGS, "LOGS" }, + { FUNC_BACKLIGHT, "BACKLIGHT" }, + { FUNC_SCREENSHOT, "SCREENSHOT" }, + { FUNC_RACING_MODE, "RACING_MODE" }, + { FUNC_DISABLE_TOUCH, "DISABLE_TOUCH" }, + { FUNC_SET_SCREEN, "SET_SCREEN" }, + { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { 0, NULL } +}; +const struct YamlIdStr enum_TimerModes[] = { + { TMRMODE_OFF, "OFF" }, + { TMRMODE_ON, "ON" }, + { TMRMODE_START, "START" }, + { TMRMODE_THR, "THR" }, + { TMRMODE_THR_REL, "THR_REL" }, + { TMRMODE_THR_START, "THR_START" }, + { 0, NULL } +}; +const struct YamlIdStr enum_MixerMultiplex[] = { + { MLTPX_ADD, "ADD" }, + { MLTPX_MUL, "MUL" }, + { MLTPX_REPL, "REPL" }, + { 0, NULL } +}; +const struct YamlIdStr enum_MixSources[] = { + { MIXSRC_NONE, "NONE" }, + { MIXSRC_MIN, "MIN" }, + { MIXSRC_MAX, "MAX" }, + { MIXSRC_TrimRud, "TrimRud" }, + { MIXSRC_TrimEle, "TrimEle" }, + { MIXSRC_TrimThr, "TrimThr" }, + { MIXSRC_TrimAil, "TrimAil" }, + { MIXSRC_TrimT5, "TrimT5" }, + { MIXSRC_TrimT6, "TrimT6" }, + { MIXSRC_TrimT7, "TrimT7" }, + { MIXSRC_TrimT8, "TrimT8" }, + { MIXSRC_TX_VOLTAGE, "TX_VOLTAGE" }, + { MIXSRC_TX_TIME, "TX_TIME" }, + { MIXSRC_TX_GPS, "TX_GPS" }, + { 0, NULL } +}; +const struct YamlIdStr enum_LogicalSwitchesFunctions[] = { + { LS_FUNC_NONE, "FUNC_NONE" }, + { LS_FUNC_VEQUAL, "FUNC_VEQUAL" }, + { LS_FUNC_VALMOSTEQUAL, "FUNC_VALMOSTEQUAL" }, + { LS_FUNC_VPOS, "FUNC_VPOS" }, + { LS_FUNC_VNEG, "FUNC_VNEG" }, + { LS_FUNC_RANGE, "FUNC_RANGE" }, + { LS_FUNC_APOS, "FUNC_APOS" }, + { LS_FUNC_ANEG, "FUNC_ANEG" }, + { LS_FUNC_AND, "FUNC_AND" }, + { LS_FUNC_OR, "FUNC_OR" }, + { LS_FUNC_XOR, "FUNC_XOR" }, + { LS_FUNC_EDGE, "FUNC_EDGE" }, + { LS_FUNC_EQUAL, "FUNC_EQUAL" }, + { LS_FUNC_GREATER, "FUNC_GREATER" }, + { LS_FUNC_LESS, "FUNC_LESS" }, + { LS_FUNC_DIFFEGREATER, "FUNC_DIFFEGREATER" }, + { LS_FUNC_ADIFFEGREATER, "FUNC_ADIFFEGREATER" }, + { LS_FUNC_TIMER, "FUNC_TIMER" }, + { LS_FUNC_STICKY, "FUNC_STICKY" }, + { 0, NULL } +}; +const struct YamlIdStr enum_SwashType[] = { + { SWASH_TYPE_NONE, "TYPE_NONE" }, + { SWASH_TYPE_120, "TYPE_120" }, + { SWASH_TYPE_120X, "TYPE_120X" }, + { SWASH_TYPE_140, "TYPE_140" }, + { SWASH_TYPE_90, "TYPE_90" }, + { 0, NULL } +}; +const struct YamlIdStr enum_SwitchSources[] = { + { SWSRC_NONE, "NONE" }, + { SWSRC_TrimT7Down, "TrimT7Down" }, + { SWSRC_TrimT7Up, "TrimT7Up" }, + { SWSRC_TrimT8Down, "TrimT8Down" }, + { SWSRC_TrimT8Up, "TrimT8Up" }, + { SWSRC_ON, "ON" }, + { SWSRC_ONE, "ONE" }, + { SWSRC_TELEMETRY_STREAMING, "TELEMETRY_STREAMING" }, + { SWSRC_RADIO_ACTIVITY, "RADIO_ACTIVITY" }, + { SWSRC_OFF, "OFF" }, + { 0, NULL } +}; +const struct YamlIdStr enum_PotsWarnMode[] = { + { POTS_WARN_OFF, "WARN_OFF" }, + { POTS_WARN_MANUAL, "WARN_MANUAL" }, + { POTS_WARN_AUTO, "WARN_AUTO" }, + { 0, NULL } +}; +const struct YamlIdStr enum_ModelOverridableEnable[] = { + { OVERRIDE_GLOBAL, "GLOBAL" }, + { OVERRIDE_OFF, "OFF" }, + { OVERRIDE_ON, "ON" }, + { 0, NULL } +}; +const struct YamlIdStr enum_FailsafeModes[] = { + { FAILSAFE_NOT_SET, "NOT_SET" }, + { FAILSAFE_HOLD, "HOLD" }, + { FAILSAFE_CUSTOM, "CUSTOM" }, + { FAILSAFE_NOPULSES, "NOPULSES" }, + { FAILSAFE_RECEIVER, "RECEIVER" }, + { 0, NULL } +}; +const struct YamlIdStr enum_TelemetrySensorFormula[] = { + { TELEM_FORMULA_ADD, "FORMULA_ADD" }, + { TELEM_FORMULA_AVERAGE, "FORMULA_AVERAGE" }, + { TELEM_FORMULA_MIN, "FORMULA_MIN" }, + { TELEM_FORMULA_MAX, "FORMULA_MAX" }, + { TELEM_FORMULA_MULTIPLY, "FORMULA_MULTIPLY" }, + { TELEM_FORMULA_TOTALIZE, "FORMULA_TOTALIZE" }, + { TELEM_FORMULA_CELL, "FORMULA_CELL" }, + { TELEM_FORMULA_CONSUMPTION, "FORMULA_CONSUMPTION" }, + { TELEM_FORMULA_DIST, "FORMULA_DIST" }, + { 0, NULL } +}; +const struct YamlIdStr enum_TelemetrySensorType[] = { + { TELEM_TYPE_CUSTOM, "TYPE_CUSTOM" }, + { TELEM_TYPE_CALCULATED, "TYPE_CALCULATED" }, + { 0, NULL } +}; +const struct YamlIdStr enum_ZoneOptionValueEnum[] = { + { ZOV_Unsigned, "Unsigned" }, + { ZOV_Signed, "Signed" }, + { ZOV_Bool, "Bool" }, + { ZOV_String, "String" }, + { ZOV_Source, "Source" }, + { ZOV_Color, "Color" }, + { 0, NULL } +}; +const struct YamlIdStr enum_USBJoystickIfMode[] = { + { USBJOYS_JOYSTICK, "JOYSTICK" }, + { USBJOYS_GAMEPAD, "GAMEPAD" }, + { USBJOYS_MULTIAXIS, "MULTIAXIS" }, + { 0, NULL } +}; +const struct YamlIdStr enum_USBJoystickCh[] = { + { USBJOYS_CH_NONE, "CH_NONE" }, + { USBJOYS_CH_BUTTON, "CH_BUTTON" }, + { USBJOYS_CH_AXIS, "CH_AXIS" }, + { USBJOYS_CH_SIM, "CH_SIM" }, + { 0, NULL } +}; + +// +// Structs last +// + +static const struct YamlNode struct_CalibData[] = { + YAML_IDX_CUST("calib",r_calib,w_calib), + YAML_SIGNED( "mid", 16 ), + YAML_SIGNED( "spanNeg", 16 ), + YAML_SIGNED( "spanPos", 16 ), + YAML_END +}; +static const struct YamlNode struct_signed_16[] = { + YAML_IDX, + YAML_SIGNED( "val", 16 ), + YAML_END +}; +static const struct YamlNode struct_TrainerMix[] = { + YAML_IDX, + YAML_UNSIGNED( "srcChn", 6 ), + YAML_ENUM("mode", 2, enum_TrainerMultiplex), + YAML_SIGNED( "studWeight", 8 ), + YAML_END +}; +static const struct YamlNode struct_TrainerData[] = { + YAML_ARRAY("calib", 16, 4, struct_signed_16, NULL), + YAML_ARRAY("mix", 16, 4, struct_TrainerMix, NULL), + YAML_END +}; +static const struct YamlNode struct_anonymous_1[] = { + YAML_STRING("name", 6), + YAML_END +}; +static const struct YamlNode struct_anonymous_2[] = { + YAML_SIGNED( "val", 16 ), + YAML_UNSIGNED( "mode", 8 ), + YAML_UNSIGNED( "param", 8 ), + YAML_SIGNED( "spare", 16 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_3[] = { + YAML_SIGNED( "val1", 32 ), + YAML_SIGNED( "val2", 16 ), + YAML_END +}; +static const struct YamlNode union_anonymous_0_elmts[] = { + YAML_STRUCT("play", 48, struct_anonymous_1, NULL), + YAML_STRUCT("all", 48, struct_anonymous_2, NULL), + YAML_STRUCT("clear", 48, struct_anonymous_3, NULL), + YAML_END +}; +static const struct YamlNode struct_CustomFunctionData[] = { + YAML_IDX, + YAML_SIGNED_CUST( "swtch", 10, r_swtchSrc, w_swtchSrc ), + YAML_ENUM("func", 6, enum_Functions), + YAML_CUSTOM("def",r_customFn,w_customFn), + YAML_PADDING( 48 ), + YAML_PADDING( 8 ), + YAML_END +}; +static const struct YamlNode struct_RadioData[] = { + YAML_UNSIGNED( "manuallyEdited", 1 ), + YAML_SIGNED( "timezoneMinutes", 3 ), + YAML_PADDING( 4 ), + YAML_CUSTOM("semver",nullptr,w_semver), + YAML_CUSTOM("board",nullptr,w_board), + YAML_ARRAY("calib", 48, 22, struct_CalibData, NULL), + YAML_PADDING( 16 ), + YAML_SIGNED( "currModel", 8 ), + YAML_UNSIGNED( "contrast", 8 ), + YAML_UNSIGNED( "vBatWarn", 8 ), + YAML_SIGNED( "txVoltageCalibration", 8 ), + YAML_ENUM("backlightMode", 3, enum_BacklightMode), + YAML_ENUM("antennaMode", 2, enum_AntennaModes), + YAML_UNSIGNED( "disableRtcWarning", 1 ), + YAML_UNSIGNED( "keysBacklight", 1 ), + YAML_UNSIGNED( "dontPlayHello", 1 ), + YAML_ENUM("internalModule", 8, enum_ModuleType), + YAML_STRUCT("trainer", 128, struct_TrainerData, NULL), + YAML_UNSIGNED( "view", 8 ), + YAML_PADDING( 2 ), + YAML_UNSIGNED( "fai", 1 ), + YAML_SIGNED_CUST( "beepMode", 2, r_beeperMode, w_beeperMode ), + YAML_UNSIGNED( "alarmsFlash", 1 ), + YAML_UNSIGNED( "disableMemoryWarning", 1 ), + YAML_UNSIGNED( "disableAlarmWarning", 1 ), + YAML_UNSIGNED( "stickMode", 2 ), + YAML_SIGNED( "timezone", 5 ), + YAML_UNSIGNED( "adjustRTC", 1 ), + YAML_UNSIGNED( "inactivityTimer", 8 ), + YAML_CUSTOM("telemetryBaudrate",r_telemetryBaudrate,nullptr), + YAML_UNSIGNED( "internalModuleBaudrate", 3 ), + YAML_SIGNED( "splashMode", 3 ), + YAML_SIGNED_CUST( "hapticMode", 2, r_beeperMode, w_beeperMode ), + YAML_SIGNED( "switchesDelay", 8 ), + YAML_UNSIGNED( "lightAutoOff", 8 ), + YAML_UNSIGNED( "templateSetup", 8 ), + YAML_SIGNED( "PPM_Multiplier", 8 ), + YAML_SIGNED_CUST( "hapticLength", 8, r_5pos, w_5pos ), + YAML_SIGNED_CUST( "beepLength", 3, r_5pos, w_5pos ), + YAML_SIGNED_CUST( "hapticStrength", 3, r_5pos, w_5pos ), + YAML_UNSIGNED( "gpsFormat", 1 ), + YAML_PADDING( 1 ), + YAML_UNSIGNED_CUST( "speakerPitch", 8, r_spPitch, w_spPitch ), + YAML_SIGNED_CUST( "speakerVolume", 8, r_vol, w_vol ), + YAML_SIGNED_CUST( "vBatMin", 8, r_vbat_min, w_vbat_min ), + YAML_SIGNED_CUST( "vBatMax", 8, r_vbat_max, w_vbat_max ), + YAML_UNSIGNED( "backlightBright", 8 ), + YAML_UNSIGNED( "globalTimer", 32 ), + YAML_UNSIGNED( "bluetoothBaudrate", 4 ), + YAML_ENUM("bluetoothMode", 4, enum_BluetoothModes), + YAML_UNSIGNED( "countryCode", 2 ), + YAML_SIGNED( "pwrOnSpeed", 3 ), + YAML_SIGNED( "pwrOffSpeed", 3 ), + YAML_CUSTOM("jitterFilter",r_jitterFilter,nullptr), + YAML_UNSIGNED( "noJitterFilter", 1 ), + YAML_UNSIGNED( "imperial", 1 ), + YAML_UNSIGNED( "disableRssiPoweroffAlarm", 1 ), + YAML_UNSIGNED( "USBMode", 2 ), + YAML_UNSIGNED( "jackMode", 2 ), + YAML_PADDING( 1 ), + YAML_STRING("ttsLanguage", 2), + YAML_SIGNED_CUST( "beepVolume", 4, r_5pos, w_5pos ), + YAML_SIGNED_CUST( "wavVolume", 4, r_5pos, w_5pos ), + YAML_SIGNED_CUST( "varioVolume", 4, r_5pos, w_5pos ), + YAML_SIGNED_CUST( "backgroundVolume", 4, r_5pos, w_5pos ), + YAML_SIGNED_CUST( "varioPitch", 8, r_vPitch, w_vPitch ), + YAML_SIGNED_CUST( "varioRange", 8, r_vPitch, w_vPitch ), + YAML_SIGNED( "varioRepeat", 8 ), + YAML_ARRAY("customFn", 72, 64, struct_CustomFunctionData, cfn_is_active), + YAML_CUSTOM("auxSerialMode",r_serialMode,nullptr), + YAML_CUSTOM("aux2SerialMode",r_serialMode,nullptr), + YAML_ARRAY("serialPort", 8, 4, struct_serialConfig, nullptr), + YAML_ARRAY("sticksConfig", 0, MAX_STICKS, struct_stickConfig, stick_name_valid), + YAML_ARRAY("slidersConfig", 0, MAX_POTS, struct_sliderConfig, nullptr), + YAML_ARRAY("potsConfig", 4, 16, struct_potConfig, nullptr), + YAML_ARRAY("switchConfig", 2, 32, struct_switchConfig, nullptr), + YAML_STRING("currModelFilename", 17), + YAML_UNSIGNED( "modelQuickSelect", 1 ), + YAML_UNSIGNED( "blOffBright", 7 ), + YAML_STRING("bluetoothName", 10), + YAML_STRING("ownerRegistrationID", 8), + YAML_CUSTOM("rotEncDirection",r_rotEncDirection,nullptr), + YAML_UNSIGNED( "rotEncMode", 2 ), + YAML_SIGNED( "uartSampleMode", 2 ), + YAML_UNSIGNED( "stickDeadZone", 3 ), + YAML_UNSIGNED( "audioMuteEnable", 1 ), + YAML_STRING("selectedTheme", 26), + YAML_UNSIGNED( "radioThemesDisabled", 1 ), + YAML_UNSIGNED( "radioGFDisabled", 1 ), + YAML_UNSIGNED( "radioTrainerDisabled", 1 ), + YAML_UNSIGNED( "modelHeliDisabled", 1 ), + YAML_UNSIGNED( "modelFMDisabled", 1 ), + YAML_UNSIGNED( "modelCurvesDisabled", 1 ), + YAML_UNSIGNED( "modelGVDisabled", 1 ), + YAML_UNSIGNED( "modelLSDisabled", 1 ), + YAML_UNSIGNED( "modelSFDisabled", 1 ), + YAML_UNSIGNED( "modelCustomScriptsDisabled", 1 ), + YAML_UNSIGNED( "modelTelemetryDisabled", 1 ), + YAML_END +}; +static const struct YamlNode struct_unsigned_8[] = { + YAML_IDX, + YAML_UNSIGNED( "val", 8 ), + YAML_END +}; +static const struct YamlNode struct_ModelHeader[] = { + YAML_STRING("name", 15), + YAML_ARRAY("modelId", 8, 2, struct_unsigned_8, NULL), + YAML_STRING("bitmap", 14), + YAML_STRING("labels", 100), + YAML_END +}; +static const struct YamlNode struct_TimerData[] = { + YAML_IDX, + YAML_UNSIGNED( "start", 22 ), + YAML_SIGNED_CUST( "swtch", 10, r_swtchSrc, w_swtchSrc ), + YAML_SIGNED( "value", 22 ), + YAML_ENUM("mode", 3, enum_TimerModes), + YAML_UNSIGNED( "countdownBeep", 2 ), + YAML_UNSIGNED( "minuteBeep", 1 ), + YAML_UNSIGNED( "persistent", 2 ), + YAML_SIGNED( "countdownStart", 2 ), + YAML_UNSIGNED( "showElapsed", 1 ), + YAML_UNSIGNED( "extraHaptic", 1 ), + YAML_PADDING( 6 ), + YAML_STRING("name", 8), + YAML_END +}; +static const struct YamlNode struct_CurveRef[] = { + YAML_UNSIGNED( "type", 8 ), + YAML_SIGNED_CUST( "value", 8, in_read_weight, in_write_weight ), + YAML_END +}; +static const struct YamlNode struct_MixData[] = { + YAML_SIGNED_CUST( "weight", 11, in_read_weight, in_write_weight ), + YAML_UNSIGNED( "destCh", 5 ), + YAML_UNSIGNED_CUST( "srcRaw", 10, r_mixSrcRaw, w_mixSrcRaw ), + YAML_UNSIGNED( "carryTrim", 1 ), + YAML_UNSIGNED( "mixWarn", 2 ), + YAML_ENUM("mltpx", 2, enum_MixerMultiplex), + YAML_PADDING( 1 ), + YAML_SIGNED_CUST( "offset", 13, in_read_weight, in_write_weight ), + YAML_SIGNED_CUST( "swtch", 10, r_swtchSrc, w_swtchSrc ), + YAML_UNSIGNED_CUST( "flightModes", 9, r_flightModes, w_flightModes ), + YAML_STRUCT("curve", 16, struct_CurveRef, NULL), + YAML_UNSIGNED( "delayUp", 8 ), + YAML_UNSIGNED( "delayDown", 8 ), + YAML_UNSIGNED( "speedUp", 8 ), + YAML_UNSIGNED( "speedDown", 8 ), + YAML_STRING("name", 6), + YAML_END +}; +static const struct YamlNode struct_LimitData[] = { + YAML_IDX, + YAML_SIGNED_CUST( "min", 11, in_read_weight, in_write_weight ), + YAML_SIGNED_CUST( "max", 11, in_read_weight, in_write_weight ), + YAML_SIGNED( "ppmCenter", 10 ), + YAML_SIGNED_CUST( "offset", 11, in_read_weight, in_write_weight ), + YAML_UNSIGNED( "symetrical", 1 ), + YAML_UNSIGNED( "revert", 1 ), + YAML_PADDING( 3 ), + YAML_SIGNED( "curve", 8 ), + YAML_STRING("name", 6), + YAML_END +}; +static const struct YamlNode struct_ExpoData[] = { + YAML_UNSIGNED( "mode", 2 ), + YAML_UNSIGNED( "scale", 14 ), + YAML_CUSTOM("carryTrim",r_carryTrim,nullptr), + YAML_SIGNED( "trimSource", 6 ), + YAML_UNSIGNED_CUST( "srcRaw", 10, r_mixSrcRaw, w_mixSrcRaw ), + YAML_UNSIGNED( "chn", 5 ), + YAML_SIGNED_CUST( "swtch", 10, r_swtchSrc, w_swtchSrc ), + YAML_UNSIGNED_CUST( "flightModes", 9, r_flightModes, w_flightModes ), + YAML_SIGNED_CUST( "weight", 8, in_read_weight, in_write_weight ), + YAML_STRING("name", 6), + YAML_SIGNED_CUST( "offset", 8, in_read_weight, in_write_weight ), + YAML_STRUCT("curve", 16, struct_CurveRef, NULL), + YAML_END +}; +static const struct YamlNode struct_CurveHeader[] = { + YAML_IDX, + YAML_UNSIGNED( "type", 1 ), + YAML_UNSIGNED( "smooth", 1 ), + YAML_SIGNED( "points", 6 ), + YAML_STRING("name", 3), + YAML_END +}; +static const struct YamlNode struct_signed_8[] = { + YAML_IDX, + YAML_SIGNED( "val", 8 ), + YAML_END +}; +static const struct YamlNode struct_LogicalSwitchData[] = { + YAML_IDX, + YAML_ENUM("func", 8, enum_LogicalSwitchesFunctions), + YAML_CUSTOM("def",r_logicSw,w_logicSw), + YAML_PADDING( 10 ), + YAML_PADDING( 10 ), + YAML_SIGNED_CUST( "andsw", 10, r_swtchSrc, w_swtchSrc ), + YAML_PADDING( 1 ), + YAML_PADDING( 1 ), + YAML_PADDING( 16 ), + YAML_UNSIGNED( "delay", 8 ), + YAML_UNSIGNED( "duration", 8 ), + YAML_END +}; +static const struct YamlNode struct_SwashRingData[] = { + YAML_ENUM("type", 8, enum_SwashType), + YAML_UNSIGNED( "value", 8 ), + YAML_UNSIGNED_CUST( "collectiveSource", 8, r_mixSrcRaw, w_mixSrcRaw ), + YAML_UNSIGNED_CUST( "aileronSource", 8, r_mixSrcRaw, w_mixSrcRaw ), + YAML_UNSIGNED_CUST( "elevatorSource", 8, r_mixSrcRaw, w_mixSrcRaw ), + YAML_SIGNED( "collectiveWeight", 8 ), + YAML_SIGNED( "aileronWeight", 8 ), + YAML_SIGNED( "elevatorWeight", 8 ), + YAML_END +}; +static const struct YamlNode struct_trim_t[] = { + YAML_IDX, + YAML_SIGNED( "value", 11 ), + YAML_UNSIGNED( "mode", 5 ), + YAML_END +}; +static const struct YamlNode struct_FlightModeData[] = { + YAML_IDX, + YAML_ARRAY("trim", 16, 8, struct_trim_t, NULL), + YAML_STRING("name", 10), + YAML_SIGNED_CUST( "swtch", 10, r_swtchSrc, w_swtchSrc ), + YAML_PADDING( 6 ), + YAML_UNSIGNED( "fadeIn", 8 ), + YAML_UNSIGNED( "fadeOut", 8 ), + YAML_ARRAY("gvars", 16, 9, struct_signed_16, gvar_is_active), + YAML_END +}; +static const struct YamlNode struct_GVarData[] = { + YAML_IDX, + YAML_STRING("name", 3), + YAML_UNSIGNED( "min", 12 ), + YAML_UNSIGNED( "max", 12 ), + YAML_UNSIGNED( "popup", 1 ), + YAML_UNSIGNED( "prec", 1 ), + YAML_UNSIGNED( "unit", 2 ), + YAML_PADDING( 4 ), + YAML_END +}; +static const struct YamlNode struct_VarioData[] = { + YAML_UNSIGNED_CUST( "source", 7, r_tele_sensor, w_tele_sensor ), + YAML_UNSIGNED( "centerSilent", 1 ), + YAML_SIGNED( "centerMax", 8 ), + YAML_SIGNED( "centerMin", 8 ), + YAML_SIGNED( "min", 8 ), + YAML_SIGNED( "max", 8 ), + YAML_END +}; +static const struct YamlNode struct_RssiAlarmData[] = { + YAML_CUSTOM("disabled",r_rssiDisabled,nullptr), + YAML_CUSTOM("warning",r_rssiWarning,nullptr), + YAML_CUSTOM("critical",r_rssiCritical,nullptr), + YAML_END +}; +static const struct YamlNode struct_RFAlarmData[] = { + YAML_SIGNED( "warning", 8 ), + YAML_SIGNED( "critical", 8 ), + YAML_END +}; +static const struct YamlNode struct_PpmModule[] = { + YAML_SIGNED( "delay", 6 ), + YAML_UNSIGNED( "pulsePol", 1 ), + YAML_UNSIGNED( "outputType", 1 ), + YAML_SIGNED( "frameLength", 8 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_5[] = { + YAML_PADDING( 8 ), + YAML_UNSIGNED( "disableTelemetry", 1 ), + YAML_UNSIGNED( "disableMapping", 1 ), + YAML_UNSIGNED( "autoBindMode", 1 ), + YAML_UNSIGNED( "lowPowerMode", 1 ), + YAML_UNSIGNED( "receiverTelemetryOff", 1 ), + YAML_UNSIGNED( "receiverHigherChannels", 1 ), + YAML_PADDING( 2 ), + YAML_SIGNED( "optionValue", 8 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_6[] = { + YAML_UNSIGNED( "power", 2 ), + YAML_PADDING( 2 ), + YAML_UNSIGNED( "receiverTelemetryOff", 1 ), + YAML_UNSIGNED( "receiverHigherChannels", 1 ), + YAML_SIGNED( "antennaMode", 2 ), + YAML_PADDING( 8 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_7[] = { + YAML_PADDING( 6 ), + YAML_UNSIGNED( "noninverted", 1 ), + YAML_PADDING( 1 ), + YAML_SIGNED( "refreshRate", 8 ), + YAML_END +}; +static const struct YamlNode struct_string_64[] = { + YAML_IDX, + YAML_STRING("val", 8), + YAML_END +}; +static const struct YamlNode struct_anonymous_8[] = { + YAML_UNSIGNED( "receivers", 7 ), + YAML_UNSIGNED( "racingMode", 1 ), + YAML_ARRAY("receiverName", 64, 3, struct_string_64, NULL), + YAML_END +}; +static const struct YamlNode struct_anonymous_9[] = { + YAML_ARRAY("rx_id", 8, 4, struct_unsigned_8, NULL), + YAML_UNSIGNED( "mode", 3 ), + YAML_UNSIGNED( "rfPower", 1 ), + YAML_UNSIGNED( "reserved", 4 ), + YAML_ARRAY("rx_freq", 8, 2, struct_unsigned_8, NULL), + YAML_END +}; +static const struct YamlNode struct_anonymous_10[] = { + YAML_UNSIGNED( "emi", 2 ), + YAML_UNSIGNED( "telemetry", 1 ), + YAML_UNSIGNED( "phyMode", 3 ), + YAML_UNSIGNED( "reserved", 2 ), + YAML_UNSIGNED( "rfPower", 8 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_11[] = { + YAML_UNSIGNED( "raw12bits", 1 ), + YAML_UNSIGNED( "telemetryBaudrate", 3 ), + YAML_PADDING( 4 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_12[] = { + YAML_UNSIGNED( "telemetryBaudrate", 3 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_13[] = { + YAML_UNSIGNED( "flags", 8 ), + YAML_END +}; +static const struct YamlNode union_anonymous_4_elmts[] = { + YAML_ARRAY("raw", 8, 25, struct_unsigned_8, NULL), + YAML_STRUCT("ppm", 16, struct_PpmModule, NULL), + YAML_STRUCT("multi", 24, struct_anonymous_5, NULL), + YAML_STRUCT("pxx", 16, struct_anonymous_6, NULL), + YAML_STRUCT("sbus", 16, struct_anonymous_7, NULL), + YAML_STRUCT("pxx2", 200, struct_anonymous_8, NULL), + YAML_STRUCT("flysky", 56, struct_anonymous_9, NULL), + YAML_STRUCT("afhds3", 16, struct_anonymous_10, NULL), + YAML_STRUCT("ghost", 8, struct_anonymous_11, NULL), + YAML_STRUCT("crsf", 8, struct_anonymous_12, NULL), + YAML_STRUCT("dsmp", 8, struct_anonymous_13, NULL), + YAML_END +}; +static const struct YamlNode struct_ModuleData[] = { + YAML_IDX, + YAML_UNSIGNED_CUST( "type", 8, r_moduleType, w_moduleType ), + YAML_CUSTOM("subType",r_modSubtype,w_modSubtype), + YAML_UNSIGNED( "channelsStart", 8 ), + YAML_SIGNED_CUST( "channelsCount", 8, r_channelsCount, w_channelsCount ), + YAML_ENUM("failsafeMode", 4, enum_FailsafeModes), + YAML_PADDING( 4 ), + YAML_UNION("mod", 200, union_anonymous_4_elmts, select_mod_type), + YAML_END +}; +static const struct YamlNode struct_TrainerModuleData[] = { + YAML_UNSIGNED_CUST( "mode", 8, r_trainerMode, w_trainerMode ), + YAML_UNSIGNED( "channelsStart", 8 ), + YAML_SIGNED( "channelsCount", 8 ), + YAML_SIGNED( "frameLength", 8 ), + YAML_SIGNED( "delay", 6 ), + YAML_UNSIGNED( "pulsePol", 1 ), + YAML_PADDING( 1 ), + YAML_END +}; +static const struct YamlNode union_ScriptDataInput_elmts[] = { + YAML_SIGNED( "value", 16 ), + YAML_UNSIGNED_CUST( "source", 16, r_mixSrcRaw, w_mixSrcRaw ), + YAML_END +}; +static const struct YamlNode union_ScriptDataInput[] = { + YAML_IDX, + YAML_UNION("u", 16, union_ScriptDataInput_elmts, select_script_input), + YAML_END +}; +static const struct YamlNode struct_ScriptData[] = { + YAML_IDX, + YAML_STRING("file", 6), + YAML_STRING("name", 6), + YAML_ARRAY("inputs", 16, 6, union_ScriptDataInput, NULL), + YAML_END +}; +static const struct YamlNode struct_string_32[] = { + YAML_IDX, + YAML_STRING("val", 4), + YAML_END +}; +static const struct YamlNode union_anonymous_14_elmts[] = { + YAML_UNSIGNED( "id", 16 ), + YAML_UNSIGNED( "persistentValue", 16 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_16[] = { + YAML_UNSIGNED( "physID", 5 ), + YAML_UNSIGNED( "rxIndex", 3 ), + YAML_END +}; +static const struct YamlNode union_anonymous_15_elmts[] = { + YAML_STRUCT("frskyInstance", 8, struct_anonymous_16, NULL), + YAML_UNSIGNED( "instance", 8 ), + YAML_ENUM("formula", 8, enum_TelemetrySensorFormula), + YAML_END +}; +static const struct YamlNode struct_anonymous_18[] = { + YAML_UNSIGNED( "ratio", 16 ), + YAML_SIGNED( "offset", 16 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_19[] = { + YAML_UNSIGNED( "source", 8 ), + YAML_UNSIGNED( "index", 8 ), + YAML_PADDING( 16 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_20[] = { + YAML_ARRAY("sources", 8, 4, struct_signed_8, NULL), + YAML_END +}; +static const struct YamlNode struct_anonymous_21[] = { + YAML_UNSIGNED( "source", 8 ), + YAML_PADDING( 24 ), + YAML_END +}; +static const struct YamlNode struct_anonymous_22[] = { + YAML_UNSIGNED( "gps", 8 ), + YAML_UNSIGNED( "alt", 8 ), + YAML_PADDING( 16 ), + YAML_END +}; +static const struct YamlNode union_anonymous_17_elmts[] = { + YAML_STRUCT("custom", 32, struct_anonymous_18, NULL), + YAML_STRUCT("cell", 32, struct_anonymous_19, NULL), + YAML_STRUCT("calc", 32, struct_anonymous_20, NULL), + YAML_STRUCT("consumption", 32, struct_anonymous_21, NULL), + YAML_STRUCT("dist", 32, struct_anonymous_22, NULL), + YAML_UNSIGNED( "param", 32 ), + YAML_END +}; +static const struct YamlNode struct_TelemetrySensor[] = { + YAML_IDX, + YAML_UNION("id1", 16, union_anonymous_14_elmts, select_id1), + YAML_UNION("id2", 8, union_anonymous_15_elmts, select_id2), + YAML_STRING("label", 4), + YAML_UNSIGNED( "subId", 8 ), + YAML_ENUM("type", 1, enum_TelemetrySensorType), + YAML_PADDING( 1 ), + YAML_UNSIGNED( "unit", 6 ), + YAML_UNSIGNED( "prec", 2 ), + YAML_UNSIGNED( "autoOffset", 1 ), + YAML_UNSIGNED( "filter", 1 ), + YAML_UNSIGNED( "logs", 1 ), + YAML_UNSIGNED( "persistent", 1 ), + YAML_UNSIGNED( "onlyPositive", 1 ), + YAML_PADDING( 1 ), + YAML_UNION("cfg", 32, union_anonymous_17_elmts, select_sensor_cfg), + YAML_END +}; +static const struct YamlNode union_ZoneOptionValue_elmts[] = { + YAML_UNSIGNED( "unsignedValue", 32 ), + YAML_SIGNED( "signedValue", 32 ), + YAML_UNSIGNED( "boolValue", 32 ), + YAML_STRING("stringValue", 8), + YAML_CUSTOM("source",r_zov_source,w_zov_source), + YAML_CUSTOM("color",r_zov_color,w_zov_color), + YAML_END +}; +static const struct YamlNode struct_ZoneOptionValueTyped[] = { + YAML_IDX, + YAML_ENUM("type", 32, enum_ZoneOptionValueEnum), + YAML_UNION("value", 64, union_ZoneOptionValue_elmts, select_zov), + YAML_END +}; +static const struct YamlNode struct_WidgetPersistentData[] = { + YAML_ARRAY("options", 96, 5, struct_ZoneOptionValueTyped, NULL), + YAML_END +}; +static const struct YamlNode struct_ZonePersistentData[] = { + YAML_IDX, + YAML_STRING("widgetName", 12), + YAML_STRUCT("widgetData", 480, struct_WidgetPersistentData, NULL), + YAML_END +}; +static const struct YamlNode struct_LayoutPersistentData[] = { + YAML_ARRAY("zones", 576, 10, struct_ZonePersistentData, NULL), + YAML_ARRAY("options", 96, 10, struct_ZoneOptionValueTyped, NULL), + YAML_END +}; +static const struct YamlNode struct_CustomScreenData[] = { + YAML_IDX, + YAML_STRING("LayoutId", 12), + YAML_STRUCT("layoutData", 6720, struct_LayoutPersistentData, NULL), + YAML_END +}; +static const struct YamlNode struct_TopBarPersistentData[] = { + YAML_ARRAY("zones", 576, 6, struct_ZonePersistentData, NULL), + YAML_ARRAY("options", 96, 1, struct_ZoneOptionValueTyped, NULL), + YAML_END +}; +static const struct YamlNode struct_USBJoystickChData[] = { + YAML_IDX, + YAML_ENUM("mode", 3, enum_USBJoystickCh), + YAML_UNSIGNED( "inversion", 1 ), + YAML_UNSIGNED( "param", 4 ), + YAML_UNSIGNED( "btn_num", 5 ), + YAML_UNSIGNED( "switch_npos", 3 ), + YAML_END +}; +static const struct YamlNode struct_ModelData[] = { + YAML_CUSTOM("semver",nullptr,w_semver), + YAML_STRUCT("header", 1048, struct_ModelHeader, NULL), + YAML_ARRAY("timers", 136, 3, struct_TimerData, NULL), + YAML_UNSIGNED( "telemetryProtocol", 3 ), + YAML_UNSIGNED( "thrTrim", 1 ), + YAML_UNSIGNED( "noGlobalFunctions", 1 ), + YAML_UNSIGNED( "displayTrims", 2 ), + YAML_UNSIGNED( "ignoreSensorIds", 1 ), + YAML_SIGNED( "trimInc", 3 ), + YAML_UNSIGNED( "disableThrottleWarning", 1 ), + YAML_UNSIGNED( "displayChecklist", 1 ), + YAML_UNSIGNED( "extendedLimits", 1 ), + YAML_UNSIGNED( "extendedTrims", 1 ), + YAML_UNSIGNED( "throttleReversed", 1 ), + YAML_UNSIGNED( "enableCustomThrottleWarning", 1 ), + YAML_UNSIGNED( "disableTelemetryWarning", 1 ), + YAML_UNSIGNED( "showInstanceIds", 1 ), + YAML_UNSIGNED( "checklistInteractive", 1 ), + YAML_PADDING( 4 ), + YAML_SIGNED( "customThrottleWarningPosition", 8 ), + YAML_UNSIGNED( "beepANACenter", 16 ), + YAML_ARRAY("mixData", 160, 64, struct_MixData, NULL), + YAML_ARRAY("limitData", 104, 32, struct_LimitData, NULL), + YAML_ARRAY("expoData", 136, 64, struct_ExpoData, NULL), + YAML_ARRAY("curves", 32, 32, struct_CurveHeader, NULL), + YAML_ARRAY("points", 8, 512, struct_signed_8, NULL), + YAML_ARRAY("logicalSw", 72, 64, struct_LogicalSwitchData, NULL), + YAML_ARRAY("customFn", 72, 64, struct_CustomFunctionData, cfn_is_active), + YAML_STRUCT("swashR", 64, struct_SwashRingData, swash_is_active), + YAML_ARRAY("flightModeData", 384, 9, struct_FlightModeData, fmd_is_active), + YAML_UNSIGNED_CUST( "thrTraceSrc", 8, r_thrSrc, w_thrSrc ), + YAML_CUSTOM("switchWarningState",r_swtchWarn,w_swtchWarn), + YAML_PADDING( 64 ), + YAML_ARRAY("gvars", 56, 9, struct_GVarData, NULL), + YAML_STRUCT("varioData", 40, struct_VarioData, NULL), + YAML_UNSIGNED_CUST( "rssiSource", 8, r_tele_sensor, w_tele_sensor ), + YAML_STRUCT("rssiAlarms", 0, struct_RssiAlarmData, NULL), + YAML_STRUCT("rfAlarms", 16, struct_RFAlarmData, NULL), + YAML_UNSIGNED( "thrTrimSw", 3 ), + YAML_ENUM("potsWarnMode", 2, enum_PotsWarnMode), + YAML_ENUM("jitterFilter", 2, enum_ModelOverridableEnable), + YAML_PADDING( 1 ), + YAML_ARRAY("moduleData", 232, 2, struct_ModuleData, NULL), + YAML_ARRAY("failsafeChannels", 16, 32, struct_signed_16, NULL), + YAML_STRUCT("trainerData", 40, struct_TrainerModuleData, NULL), + YAML_ARRAY("scriptsData", 192, 9, struct_ScriptData, NULL), + YAML_ARRAY("inputNames", 32, 32, struct_string_32, NULL), + YAML_UNSIGNED( "potsWarnEnabled", 16 ), + YAML_ARRAY("potsWarnPosition", 8, 16, struct_signed_8, NULL), + YAML_ARRAY("telemetrySensors", 112, 60, struct_TelemetrySensor, NULL), + YAML_ARRAY("screenData", 6816, 10, struct_CustomScreenData, NULL), + YAML_STRUCT("topbarData", 3552, struct_TopBarPersistentData, NULL), + YAML_UNSIGNED( "view", 8 ), + YAML_STRING("modelRegistrationID", 8), + YAML_UNSIGNED( "usbJoystickExtMode", 1 ), + YAML_ENUM("usbJoystickIfMode", 3, enum_USBJoystickIfMode), + YAML_UNSIGNED( "usbJoystickCircularCut", 4 ), + YAML_ARRAY("usbJoystickCh", 16, 26, struct_USBJoystickChData, NULL), + YAML_ENUM("radioThemesDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("radioGFDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("radioTrainerDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelHeliDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelFMDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelCurvesDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelGVDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelLSDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelSFDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelCustomScriptsDisabled", 2, enum_ModelOverridableEnable), + YAML_ENUM("modelTelemetryDisabled", 2, enum_ModelOverridableEnable), + YAML_END +}; +static const struct YamlNode struct_PartialModel[] = { + YAML_STRUCT("header", 1048, struct_ModelHeader, NULL), + YAML_ARRAY("timers", 136, 3, struct_TimerData, NULL), + YAML_END +}; + +#define MAX_RADIODATA_MODELDATA_PARTIALMODEL_STR_LEN 29 + +static const struct YamlNode __RadioData_root_node = YAML_ROOT( struct_RadioData ); + +const YamlNode* get_radiodata_nodes() +{ + return &__RadioData_root_node; +} +static const struct YamlNode __ModelData_root_node = YAML_ROOT( struct_ModelData ); + +const YamlNode* get_modeldata_nodes() +{ + return &__ModelData_root_node; +} +static const struct YamlNode __PartialModel_root_node = YAML_ROOT( struct_PartialModel ); + +const YamlNode* get_partialmodel_nodes() +{ + return &__PartialModel_root_node; +} + diff --git a/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt b/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt index 117e1c6431c..af6d3b15246 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt +++ b/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt @@ -69,8 +69,7 @@ if(DEBUG_SEGGER_RTT) ) endif() - -if(PCB STREQUAL X10 OR PCB STREQUAL X12S OR PCB STREQUAL NV14) +if(PCB STREQUAL X10 OR PCB STREQUAL X12S OR PCB STREQUAL NV14 OR PCB STREQUAL PL18) set(BOOTLOADER_SRC ${BOOTLOADER_SRC} ../../../../../gui/colorlcd/fonts.cpp @@ -90,12 +89,22 @@ if(PCB STREQUAL X10 OR PCB STREQUAL X12S OR PCB STREQUAL NV14) ../../../../../targets/${TARGET_DIR}/haptic_driver.cpp ) + if(NOT PCB STREQUAL PL18) + set(BOOTLOADER_SRC ${BOOTLOADER_SRC} + ../../../../../targets/common/arm/stm32/diskio_sdio.cpp + ) + else() + set(BOOTLOADER_SRC ${BOOTLOADER_SRC} + ../../../../../targets/pl18/key_driver.cpp + ) + endif() + # Add LVGL sources foreach(LVGL_FILE ${LVGL_SRC_FILES_MINIMAL}) set(BOOTLOADER_SRC ${BOOTLOADER_SRC} ../../../../../${LVGL_FILE}) endforeach() - if(NOT PCB STREQUAL NV14) + if(NOT (PCB STREQUAL NV14 OR PCB STREQUAL PL18)) set(BOOTLOADER_SRC ${BOOTLOADER_SRC} ../../../../../targets/${TARGET_DIR}/led_driver.cpp diff --git a/radio/src/targets/common/arm/stm32/bootloader/boot.cpp b/radio/src/targets/common/arm/stm32/bootloader/boot.cpp index ce99a9fa3b3..32a1bdce27f 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/boot.cpp +++ b/radio/src/targets/common/arm/stm32/bootloader/boot.cpp @@ -603,6 +603,6 @@ int bootloaderMain() return 0; } -#if !defined(SIMU) && (defined(PCBHORUS) || defined(PCBNV14)) +#if !defined(SIMU) && (defined(PCBHORUS) || defined(PCBFLYSKY)) void *__dso_handle = nullptr; #endif diff --git a/radio/src/targets/common/arm/stm32/pwr_driver.cpp b/radio/src/targets/common/arm/stm32/pwr_driver.cpp index 46a50b36e72..fb5b7e49e96 100644 --- a/radio/src/targets/common/arm/stm32/pwr_driver.cpp +++ b/radio/src/targets/common/arm/stm32/pwr_driver.cpp @@ -38,9 +38,11 @@ void pwrInit() #endif // Internal module power +#if defined(HARDWARE_INTERNAL_MODULE) INTERNAL_MODULE_OFF(); GPIO_InitStructure.GPIO_Pin = INTMODULE_PWR_GPIO_PIN; GPIO_Init(INTMODULE_PWR_GPIO, &GPIO_InitStructure); +#endif // External module power EXTERNAL_MODULE_PWR_OFF(); diff --git a/radio/src/targets/common/arm/stm32/usb_bsp.c b/radio/src/targets/common/arm/stm32/usb_bsp.c index 1317a11a87c..26a964749e9 100644 --- a/radio/src/targets/common/arm/stm32/usb_bsp.c +++ b/radio/src/targets/common/arm/stm32/usb_bsp.c @@ -49,7 +49,8 @@ void USB_OTG_BSP_Init(USB_OTG_CORE_HANDLE *pdev) GPIO_PinAFConfig(USB_GPIO, USB_GPIO_PinSource_DM, USB_GPIO_AF); GPIO_PinAFConfig(USB_GPIO, USB_GPIO_PinSource_DP, USB_GPIO_AF); - + +#if defined(USB_GPIO_PIN_VBUS) /* Configure VBUS Pin */ GPIO_InitStructure.GPIO_Pin = USB_GPIO_PIN_VBUS; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; @@ -57,6 +58,7 @@ void USB_OTG_BSP_Init(USB_OTG_CORE_HANDLE *pdev) GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; GPIO_Init(USB_GPIO, &GPIO_InitStructure); +#endif RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS, ENABLE) ; diff --git a/radio/src/targets/common/arm/stm32/usb_conf.h b/radio/src/targets/common/arm/stm32/usb_conf.h index 22f3b2656db..337195ed4c9 100644 --- a/radio/src/targets/common/arm/stm32/usb_conf.h +++ b/radio/src/targets/common/arm/stm32/usb_conf.h @@ -29,6 +29,8 @@ #include "stm32f2xx.h" #endif +#include "hal.h" + /* USB Core and PHY interface configuration. Tip: To avoid modifying these defines each time you need to change the USB configuration, you can declare the needed define in your toolchain @@ -97,7 +99,9 @@ #endif /****************** USB OTG MISC CONFIGURATION ********************************/ +#if defined(USB_GPIO_PIN_VBUS) #define VBUS_SENSING_ENABLED +#endif /****************** USB OTG MODE CONFIGURATION ********************************/ //#define USE_HOST_MODE diff --git a/radio/src/targets/common/arm/stm32/usb_driver.cpp b/radio/src/targets/common/arm/stm32/usb_driver.cpp index 57545f2e90b..67f43716dae 100644 --- a/radio/src/targets/common/arm/stm32/usb_driver.cpp +++ b/radio/src/targets/common/arm/stm32/usb_driver.cpp @@ -57,6 +57,7 @@ void setSelectedUsbMode(int mode) selectedUsbMode = usbMode(mode); } +#if defined(USB_GPIO_PIN_VBUS) int usbPlugged() { static uint8_t debouncedState = 0; @@ -71,6 +72,7 @@ int usbPlugged() return debouncedState; } +#endif USB_OTG_CORE_HANDLE USB_OTG_dev; diff --git a/radio/src/targets/horus/CMakeLists.txt b/radio/src/targets/horus/CMakeLists.txt index 8d409d6cf71..62b2a30e399 100644 --- a/radio/src/targets/horus/CMakeLists.txt +++ b/radio/src/targets/horus/CMakeLists.txt @@ -158,7 +158,6 @@ add_definitions(-DPCBHORUS -DSTM32F429_439xx -DSTM32F429xx -DSDRAM -DCCMRAM -DCO add_definitions(-DEEPROM_VARIANT=0 -DAUDIO -DVOICE -DRTCLOCK) add_definitions(-DGPS_USART_BAUDRATE=${INTERNAL_GPS_BAUDRATE}) add_definitions(-DPWR_BUTTON_${PWR_BUTTON}) -add_definitions(-DHARDWARE_TRAINER_JACK) add_definitions(-DSTM32_SUPPORT_32BIT_TIMERS) set(SDRAM ON) diff --git a/radio/src/targets/nv14/CMakeLists.txt b/radio/src/targets/nv14/CMakeLists.txt index cc502c9307b..3913b075ed1 100644 --- a/radio/src/targets/nv14/CMakeLists.txt +++ b/radio/src/targets/nv14/CMakeLists.txt @@ -19,7 +19,6 @@ set(BITMAPS_DIR 480x272) set(HARDWARE_EXTERNAL_MODULE YES) set(TARGET_DIR nv14) -add_definitions(-DHARDWARE_TRAINER_JACK) set(RTC_BACKUP_RAM YES) set(PPM_LIMITS_SYMETRICAL YES) diff --git a/radio/src/targets/nv14/board.cpp b/radio/src/targets/nv14/board.cpp index becfd623c3c..2c1ba8680dd 100644 --- a/radio/src/targets/nv14/board.cpp +++ b/radio/src/targets/nv14/board.cpp @@ -38,7 +38,6 @@ #include "flysky_gimbal_driver.h" #include "timers_driver.h" -#include "lcd_driver.h" #include "lcd_driver.h" #include "battery_driver.h" #include "touch_driver.h" @@ -73,7 +72,8 @@ void delay_self(int count) for (; count > 0; count--); } } -#define RCC_AHB1PeriphMinimum (PWR_RCC_AHB1Periph |\ + +#define RCC_AHB1PeriphMinimum (PWR_RCC_AHB1Periph | \ LCD_RCC_AHB1Periph |\ BACKLIGHT_RCC_AHB1Periph |\ SDRAM_RCC_AHB1Periph \ diff --git a/radio/src/targets/nv14/board.h b/radio/src/targets/nv14/board.h index af203d5de4b..19f50e6a643 100644 --- a/radio/src/targets/nv14/board.h +++ b/radio/src/targets/nv14/board.h @@ -115,25 +115,17 @@ extern HardwareOptions hardwareOptions; #endif // defined(SIMU) #define EXTERNAL_MODULE_PWR_OFF EXTERNAL_MODULE_OFF -#define IS_UART_MODULE(port) (port == INTERNAL_MODULE) -#define IS_PXX2_INTERNAL_ENABLED() (false) #if !defined(NUM_FUNCTIONS_SWITCHES) #define NUM_FUNCTIONS_SWITCHES 0 #endif -#define NUM_TRIMS_KEYS (NUM_TRIMS * 2) - -#define DEFAULT_STICK_DEADZONE 2 - -// 2 pots without detent -#define DEFAULT_POTS_CONFIG \ - (POT_WITHOUT_DETENT << 0) + \ - (POT_WITHOUT_DETENT << 2) +#define DEFAULT_STICK_DEADZONE 2 #define BATTERY_WARN 36 // 3.6V #define BATTERY_MIN 35 // 3.5V #define BATTERY_MAX 42 // 4.2V +#define BATTERY_DIVIDER 2942 #if defined(__cplusplus) && !defined(SIMU) extern "C" { @@ -275,6 +267,4 @@ bool touchPanelEventOccured(); struct TouchState touchPanelRead(); struct TouchState getInternalTouchState(); -#define BATTERY_DIVIDER 2942 - #endif // _BOARD_H_ diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt new file mode 100644 index 00000000000..9dd2993af6d --- /dev/null +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -0,0 +1,160 @@ +option(UNEXPECTED_SHUTDOWN "Enable the Unexpected Shutdown screen" ON) +option(STICKS_DEAD_ZONE "Enable sticks dead zone" YES) +option(MULTIMODULE "DIY Multiprotocol TX Module (https://github.com/pascallanger/DIY-Multiprotocol-TX-Module)" ON) +option(AFHDS2 "Support for AFHDS2" OFF) +option(GHOST "Ghost TX Module" ON) +option(PXX1 "PXX1 protocol support" ON) +option(PXX2 "PXX2 protocol support" OFF) +option(MODULE_SIZE_STD "Standard size TX Module" ON) + +set(PWR_BUTTON "PRESS" CACHE STRING "Pwr button type (PRESS/SWITCH)") +set(CPU_TYPE STM32F4) +set(HSE_VALUE 12000000) +set(SDCARD YES) +set(STORAGE_MODELSLIST YES) +set(HAPTIC YES) +set(GUI_DIR colorlcd) +set(BITMAPS_DIR 480x272) +set(HARDWARE_EXTERNAL_MODULE YES) +set(WIRELESS_CHARGER YES) +set(TARGET_DIR pl18) + +set(LINKER_SCRIPT targets/pl18/stm32f4_flash_bootloader.ld) + +set(RTC_BACKUP_RAM YES) +set(PPM_LIMITS_SYMETRICAL YES) +# for size report script +set(CPU_TYPE_FULL STM32F429xI) +set(SIZE_TARGET_MEM_DEFINE "MEM_SIZE_SDRAM2=8192") +option(USB_SERIAL "Enable USB serial (CDC)" ON) + +set(RF_BAUD_RATE 921600 230400 115200 57600 38400 19200 9600 4800 2400 1200) +set(PCB_RF_BAUD 921600 CACHE STRING "INTERNAL_MODULE_BAUDRATE: ${RF_BAUD_RATE}") +set_property(CACHE PCB_RF_BAUD PROPERTY STRINGS ${RF_BAUD_RATE}) + +set(FLAVOUR pl18) +add_definitions(-DPCBPL18 -DPCBFLYSKY) +add_definitions(-DBATTERY_CHARGE) +add_definitions(-DSOFTWARE_VOLUME) +add_definitions(-DSPI_FLASH) + +# defines existing internal modules +set(INTERNAL_MODULES MULTI CACHE STRING "Internal modules") +set(DEFAULT_INTERNAL_MODULE MULTIMODULE CACHE STRING "Default internal module") + +set(BITMAPS_TARGET pl18_bitmaps) +set(FONTS_TARGET x12_fonts) +set(LCD_DRIVER lcd_driver.cpp) +set(TOUCH_DRIVER tp_cst340.cpp) +set(HARDWARE_TOUCH YES) +set(RADIO_DEPENDENCIES ${RADIO_DEPENDENCIES} ${BITMAPS_TARGET}) +set(FIRMWARE_DEPENDENCIES datacopy) + +set(HARDWARE_TOUCH ON) +set(SOFTWARE_KEYBOARD ON) +set(FLYSKY_GIMBAL ON) + +add_definitions( + -DSTM32F429_439xx -DSTM32F429xx + -DSDRAM -DCOLORLCD -DLIBOPENUI + -DHARDWARE_TOUCH -DHARDWARE_KEYS + -DSOFTWARE_KEYBOARD -DUSE_TRIMS_AS_BUTTONS) + +set(SDRAM ON) + +add_definitions(-DEEPROM_VARIANT=0 -DAUDIO -DVOICE -DRTCLOCK) +add_definitions(-DGPS_USART_BAUDRATE=${INTERNAL_GPS_BAUDRATE}) +add_definitions(-DPWR_BUTTON_${PWR_BUTTON}) +add_definitions(-DCROSSFIRE_NATIVE) +add_definitions(-DHARDWARE_EXTERNAL_MODULE) +add_definitions(-DWIRELESS_CHARGER) + +if(STICKS_DEAD_ZONE) + add_definitions(-DSTICK_DEAD_ZONE) +endif() + +if(NOT UNEXPECTED_SHUTDOWN) + add_definitions(-DNO_UNEXPECTED_SHUTDOWN) +endif() + +if(STICKS_DEAD_ZONE) + add_definitions(-DSTICK_DEAD_ZONE) +endif() +set(AFHDS3 ON) + +# VCP CLI +set(ENABLE_SERIAL_PASSTHROUGH ON CACHE BOOL "Enable serial passthrough") +set(CLI ON CACHE BOOL "Enable CLI") + +include_directories(${RADIO_SRC_DIR}/fonts/colorlcd gui/${GUI_DIR} gui/${GUI_DIR}/layouts) + +file(GLOB THEMES_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd ${RADIO_SRC_DIR}/gui/colorlcd/themes/*.cpp) +file(GLOB LAYOUTS_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd ${RADIO_SRC_DIR}/gui/colorlcd/layouts/*.cpp) +file(GLOB WIDGETS_SRC RELATIVE ${RADIO_SRC_DIR}/gui/colorlcd ${RADIO_SRC_DIR}/gui/colorlcd/widgets/*.cpp) + +set(SRC + ${SRC} + io/frsky_firmware_update.cpp + ) + +set(GVAR_SCREEN model_gvars.cpp) + +if(BOOTLOADER) + set(FIRMWARE_TARGET_SRC + ${FIRMWARE_TARGET_SRC} + ../common/arm/loadboot.cpp + ) +endif() + +set(SRC + ${SRC} + io/frsky_firmware_update.cpp + io/multi_firmware_update.cpp + ) + +if (MULTIMODULE) + add_definitions(-DMULTI_PROTOLIST) + set(SRC ${SRC} + io/multi_protolist.cpp + ) +endif() + +set(FIRMWARE_TARGET_SRC + ${FIRMWARE_TARGET_SRC} + ${LCD_DRIVER} + ${TOUCH_DRIVER} + board.cpp + key_driver.cpp + battery_driver.cpp + backlight_driver.cpp + sdram_driver.c + ) + +set(FIRMWARE_SRC + ${FIRMWARE_SRC} + hal/adc_driver.cpp + targets/common/arm/stm32/stm32_adc.cpp + targets/common/arm/stm32/timers_driver.cpp + targets/common/arm/stm32/stm32_pulse_driver.cpp + targets/common/arm/stm32/stm32_usart_driver.cpp + targets/common/arm/stm32/trainer_driver.cpp + targets/common/arm/stm32/pwr_driver.cpp + targets/common/arm/stm32/audio_dac_driver.cpp + targets/common/arm/stm32/spi_flash.cpp + targets/common/arm/stm32/diskio_spi_flash.cpp + drivers/frftl.cpp + ) + +# Make malloc() thread-safe +add_definitions(-DTHREADSAFE_MALLOC) + +set(STM32LIB_SRC + STM32F4xx_StdPeriph_Driver/src/stm32f4xx_sdio.c + STM32F4xx_StdPeriph_Driver/src/stm32f4xx_fmc.c + STM32F4xx_StdPeriph_Driver/src/stm32f4xx_ltdc.c + STM32F4xx_StdPeriph_Driver/src/stm32f4xx_tim.c + STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dma2d.c + STM32F4xx_StdPeriph_Driver/src/stm32f4xx_exti.c + STM32F4xx_StdPeriph_Driver/src/stm32f4xx_syscfg.c + ) + diff --git a/radio/src/targets/pl18/backlight_driver.cpp b/radio/src/targets/pl18/backlight_driver.cpp new file mode 100644 index 00000000000..c2299de114b --- /dev/null +++ b/radio/src/targets/pl18/backlight_driver.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "opentx_types.h" +#include "board.h" + +#include "globals.h" +#include "lcd_driver.h" + +void backlightLowInit( void ) +{ + RCC_AHB1PeriphClockCmd(BACKLIGHT_RCC_AHB1Periph, ENABLE); + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = BACKLIGHT_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_Init(BACKLIGHT_GPIO, &GPIO_InitStructure); + GPIO_WriteBit( BACKLIGHT_GPIO, BACKLIGHT_GPIO_PIN, Bit_RESET ); +} + +void backlightInit() +{ + // PIN init + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = BACKLIGHT_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(BACKLIGHT_GPIO, &GPIO_InitStructure); + GPIO_PinAFConfig(BACKLIGHT_GPIO, BACKLIGHT_GPIO_PinSource, BACKLIGHT_GPIO_AF); + + // TODO review this when the timer will be chosen + BACKLIGHT_TIMER->ARR = 100; + BACKLIGHT_TIMER->PSC = BACKLIGHT_TIMER_FREQ / 1000000 - 1; // 10kHz (same as FrOS) + BACKLIGHT_TIMER->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE; // PWM mode 1 + BACKLIGHT_TIMER->CCER = TIM_CCER_CC1E | TIM_CCER_CC1NE; + BACKLIGHT_TIMER->CCR1 = 100; // 100% on init + BACKLIGHT_TIMER->EGR = TIM_EGR_UG; + BACKLIGHT_TIMER->CR1 |= TIM_CR1_CEN; // Counter enable + BACKLIGHT_TIMER->BDTR |= TIM_BDTR_MOE; +} + +uint8_t lastDutyCycle = 0; + +void backlightEnable(uint8_t dutyCycle) +{ + BACKLIGHT_TIMER->CCR1 = dutyCycle; + if(!dutyCycle) { + //experimental to turn off LCD when no backlight + if(lcdOffFunction) lcdOffFunction(); + } + else if(!lastDutyCycle) { + if(lcdOnFunction) lcdOnFunction(); + else lcdInit(); + } + lastDutyCycle = dutyCycle; +} + +void lcdOff() { + backlightEnable(0); +} + +void lcdOn(){ + if(lcdOnFunction) lcdOnFunction(); + else lcdInit(); + backlightEnable(BACKLIGHT_LEVEL_MAX); +} + +bool boardBacklightOn; + +bool isBacklightEnabled() +{ + if (globalData.unexpectedShutdown) return true; + return boardBacklightOn; +} diff --git a/radio/src/targets/pl18/battery_driver.cpp b/radio/src/targets/pl18/battery_driver.cpp new file mode 100644 index 00000000000..7aa1e9dc703 --- /dev/null +++ b/radio/src/targets/pl18/battery_driver.cpp @@ -0,0 +1,438 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "opentx.h" +#include "battery_driver.h" + +#define __BATTERY_DRIVER_C__ + +#define BATTERY_W 140 +#define BATTERY_H 200 +#define BATTERY_TOP ((LCD_H - BATTERY_H)/2) +#define BATTERY_CONNECTOR_W 32 +#define BATTERY_CONNECTOR_H 10 +#define BATTERY_BORDER 4 +#define BATTERY_W_INNER (BATTERY_W - 2*BATTERY_BORDER) +#define BATTERY_H_INNER (BATTERY_H - 2*BATTERY_BORDER) +#define BATTERY_TOP_INNER (BATTERY_TOP + BATTERY_BORDER) + +#define UCHARGER_SAMPLING_CNT 10 +#define UCHARGER_CHARGING_SAMPLING_CNT 10 +#define WCHARGER_SAMPLING_CNT 30 +#define WCHARGER_CHARGING_SAMPLING_CNT 10 +#define WCHARGER_LOW_CURRENT_DELAY_CNT 6000 +#define WCHARGER_HIGH_CURRENT_DELAY_CNT 24000 + +typedef struct +{ + bool hasCharger : 1; + bool isChargeEnd : 1; + bool isChargerDetectionReady : 1; + bool isChargingDetectionReady : 1; + bool isHighCurrent : 1; + uint8_t chargerSamplingCount; + uint8_t chargingSamplingCount; + uint8_t chargeEndSamplingCount; +} STRUCT_BATTERY_CHARGER; + +STRUCT_BATTERY_CHARGER uCharger; // USB charger +#if defined(WIRELESS_CHARGER) +STRUCT_BATTERY_CHARGER wCharger; // Wireless charger +uint16_t wirelessLowCurrentDelay = 0; +uint16_t wirelessHighCurrentDelay = 0; +#endif + +void chargerDetection(STRUCT_BATTERY_CHARGER* charger, uint8_t chargerPinActive, uint8_t samplingCountThreshold) +{ + if ((charger->hasCharger && chargerPinActive) || (!charger->hasCharger && !chargerPinActive)) + { + charger->chargerSamplingCount = 0; + } + else + { + charger->chargerSamplingCount++; + if (charger->chargerSamplingCount >= samplingCountThreshold) + { + charger->chargerSamplingCount = 0; + charger->hasCharger = !charger->hasCharger; + charger->isChargerDetectionReady = true; + } + } +} + +void resetChargeEndDetection(STRUCT_BATTERY_CHARGER* charger) +{ + charger->isChargeEnd = false; + charger->isChargingDetectionReady = false; + charger->chargingSamplingCount = 0; + charger->isHighCurrent = false; +} + +void chargeEndDetection(STRUCT_BATTERY_CHARGER* charger, uint8_t chargeEndPinActive, uint8_t samplingCountThreshold) +{ + if (charger->isChargeEnd) + { + if (chargeEndPinActive) + { + charger->chargingSamplingCount = 0; + if (charger->isChargingDetectionReady) + { + charger->chargeEndSamplingCount = 0; + } + else + { + charger->chargeEndSamplingCount++; + if (charger->chargeEndSamplingCount >= samplingCountThreshold) + { + charger->chargeEndSamplingCount = 0; + charger->isChargingDetectionReady = true; + } + } + } + else + { + charger->chargeEndSamplingCount = 0; + charger->chargingSamplingCount++; + if (charger->chargingSamplingCount >= samplingCountThreshold) + { + charger->chargingSamplingCount = 0; + charger->isChargeEnd = false; + charger->isChargingDetectionReady = true; + } + } + } + else + { + if (!chargeEndPinActive) + { + charger->chargeEndSamplingCount = 0; + if (charger->isChargingDetectionReady) + { + charger->chargingSamplingCount = 0; + } + else + { + charger->chargingSamplingCount++; + if (charger->chargingSamplingCount >= samplingCountThreshold) + { + charger->chargingSamplingCount = 0; + charger->isChargingDetectionReady = true; + } + } + } + else + { + charger->chargingSamplingCount = 0; + charger->chargeEndSamplingCount++; + if (charger->chargeEndSamplingCount >= samplingCountThreshold) + { + charger->chargeEndSamplingCount = 0; + charger->isChargeEnd = true; + charger->isChargingDetectionReady = true; + } + } + } +} + +uint16_t get_battery_charge_state() +{ + uint16_t state = CHARGE_UNKNOWN; + + chargerDetection(&uCharger, IS_UCHARGER_ACTIVE(), UCHARGER_SAMPLING_CNT); + if (uCharger.isChargerDetectionReady) + { + if (uCharger.hasCharger) // USB charger can be detected properly no matter it is enabled or not + { + ENABLE_UCHARGER(); + chargeEndDetection(&uCharger, IS_UCHARGER_CHARGE_END_ACTIVE(), UCHARGER_CHARGING_SAMPLING_CNT); + if (uCharger.isChargingDetectionReady) + { + if (uCharger.isChargeEnd) + { + state = CHARGE_FINISHED; + } + else + { + state = CHARGE_STARTED; + } + } + } + else + { + resetChargeEndDetection(&uCharger); + + // Disable USB charger if it is not present, so that wireless charger can be detected properly + DISABLE_UCHARGER(); + } + } + +#if defined(WIRELESS_CHARGER) + chargerDetection(&wCharger, IS_WCHARGER_ACTIVE(), WCHARGER_SAMPLING_CNT); + if (wCharger.isChargerDetectionReady) + { + if (wCharger.hasCharger) // Wireless charger can only be detected when USB charger is disabled + { + chargeEndDetection(&wCharger, IS_WCHARGER_CHARGE_END_ACTIVE(), WCHARGER_CHARGING_SAMPLING_CNT); + if (wCharger.isChargingDetectionReady) + { + if (wCharger.isChargeEnd) + { + state = CHARGE_FINISHED; + } + else + { + state = CHARGE_STARTED; + } + } + + // Charge current control + wirelessLowCurrentDelay = 0; + if (wirelessHighCurrentDelay >= WCHARGER_HIGH_CURRENT_DELAY_CNT) + { + wCharger.isHighCurrent = true; + WCHARGER_CURRENT_HIGH(); + } + else + { + wirelessHighCurrentDelay++; + } + } + else + { + resetChargeEndDetection(&wCharger); + + // Charge current control + wirelessHighCurrentDelay = 0; + if (wirelessLowCurrentDelay >= WCHARGER_LOW_CURRENT_DELAY_CNT) + { + wCharger.isHighCurrent = false; + WCHARGER_CURRENT_LOW(); + } + else + { + wirelessLowCurrentDelay++; + } + } + } + +#endif + + return state; +} + +bool isChargerActive() +{ +#if defined(WIRELESS_CHARGER) + while (!(uCharger.isChargerDetectionReady && wCharger.isChargerDetectionReady)) + { + get_battery_charge_state(); + delay_ms(10); + } + return uCharger.hasCharger || wCharger.hasCharger; +#else + while (!uCharger.isChargerDetectionReady) + { + get_battery_charge_state(); + delay_ms(10); + } + return uCharger.hasCharger; +#endif +} + +void battery_charge_init() +{ + GPIO_InitTypeDef GPIO_InitStructure; + + // Input pins + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + + // USB charger status pins + GPIO_InitStructure.GPIO_Pin = UCHARGER_GPIO_PIN; + GPIO_Init(UCHARGER_GPIO, &GPIO_InitStructure); + GPIO_InitStructure.GPIO_Pin = UCHARGER_CHARGE_END_GPIO_PIN; + GPIO_Init(UCHARGER_CHARGE_END_GPIO, &GPIO_InitStructure); + +#if defined(WIRELESS_CHARGER) + // Wireless charger status pins + GPIO_InitStructure.GPIO_Pin = WCHARGER_GPIO_PIN; + GPIO_Init(WCHARGER_GPIO, &GPIO_InitStructure); + GPIO_InitStructure.GPIO_Pin = WCHARGER_CHARGE_END_GPIO_PIN; + GPIO_Init(WCHARGER_CHARGE_END_GPIO, &GPIO_InitStructure); +#endif + + // Output pins + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + + // USB charger control pins + GPIO_InitStructure.GPIO_Pin = UCHARGER_EN_GPIO_PIN; + GPIO_Init(UCHARGER_EN_GPIO, &GPIO_InitStructure); + + // USB charger state init + ENABLE_UCHARGER(); + uCharger.hasCharger = !IS_UCHARGER_ACTIVE(); // Init for sampling count works + uCharger.isChargerDetectionReady = false; + resetChargeEndDetection(&uCharger); + +#if defined(WIRELESS_CHARGER) + // Wireless charger control pins + GPIO_InitStructure.GPIO_Pin = WCHARGER_EN_GPIO_PIN; + GPIO_Init(WCHARGER_EN_GPIO, &GPIO_InitStructure); + GPIO_InitStructure.GPIO_Pin = WCHARGER_I_CONTROL_GPIO_PIN; + GPIO_Init(WCHARGER_I_CONTROL_GPIO, &GPIO_InitStructure); + + // Wireless charger state init + ENABLE_WCHARGER(); + WCHARGER_CURRENT_LOW(); + wCharger.hasCharger = !IS_WCHARGER_ACTIVE(); // Init for sampling count works + wCharger.isChargerDetectionReady = false; + resetChargeEndDetection(&wCharger); + +#endif +} + +void drawChargingInfo(uint16_t chargeState) { + static int progress = 0; + const char* text = chargeState == CHARGE_STARTED ? STR_BATTERYCHARGING : STR_BATTERYFULL; + int h = 0; + LcdFlags color = 0; + if (CHARGE_STARTED == chargeState) + { + if (progress >= 100) + { + progress = 0; + } + else + { + progress += 25; + } + text = STR_BATTERYCHARGING; + h = ((BATTERY_H_INNER * progress) / 100); + color = COLOR_THEME_EDIT; + } + else if (CHARGE_FINISHED == chargeState) + { + text = STR_BATTERYFULL; + h = BATTERY_H_INNER; + color = COLOR_THEME_EDIT; + } + else + { + text = STR_BATTERYNONE; + h = BATTERY_H_INNER; + color = COLOR_THEME_PRIMARY1; + } + + BACKLIGHT_ENABLE(); + lcd->drawSizedText(LCD_W / 2, LCD_H - 50, text, strlen(text), CENTERED | COLOR_THEME_PRIMARY2); + + lcd->drawFilledRect((LCD_W - BATTERY_W) / 2, BATTERY_TOP, BATTERY_W, BATTERY_H, SOLID, COLOR_THEME_PRIMARY2); + lcd->drawFilledRect((LCD_W - BATTERY_W_INNER) / 2, BATTERY_TOP_INNER, BATTERY_W_INNER, BATTERY_H_INNER, SOLID, COLOR_THEME_PRIMARY1); + + lcd->drawFilledRect((LCD_W - BATTERY_W_INNER) / 2, BATTERY_TOP_INNER + BATTERY_H_INNER - h, BATTERY_W_INNER, h, SOLID, color); + lcd->drawFilledRect((LCD_W - BATTERY_CONNECTOR_W) / 2, BATTERY_TOP - BATTERY_CONNECTOR_H, BATTERY_CONNECTOR_W, BATTERY_CONNECTOR_H, SOLID, COLOR_THEME_PRIMARY2); +} +#define CHARGE_INFO_DURATION 500 +void TouchInit(); + +//this method should be called by timer interrupt or by GPIO interrupt +void handle_battery_charge(uint32_t last_press_time) +{ +#if !defined(SIMU) + static uint32_t updateTime = 0; + static uint16_t lastState = CHARGE_UNKNOWN; + static uint32_t info_until = 0; + static bool lcdInited = false; + + uint32_t now = get_tmr10ms(); + uint16_t chargeState = get_battery_charge_state(); + if (chargeState != CHARGE_UNKNOWN) { + + if (lastState != chargeState) { + //avoid positive check when none and unknown + if (lastState + chargeState > 1) { + //charge state changed - last state known + info_until = now + (CHARGE_INFO_DURATION); + } + } + //power buttons pressed + else if (now - last_press_time < POWER_ON_DELAY) { + info_until = now + CHARGE_INFO_DURATION; + } + lastState = chargeState; + } + + + if(now > info_until) { + info_until = 0; + lcd->clear(); + BACKLIGHT_DISABLE(); + if(lcdInited) { + lcdOff(); + } + return; + } + + + if (updateTime == 0 || ((get_tmr10ms() - updateTime) >= 50)) + { + if (!lcdInited) { + backlightInit(); + lcdInit(); + lcdInitDisplayDriver(); + lcdInited = true; + TouchInit(); + } + else { + lcdOn(); + } + updateTime = get_tmr10ms(); + lcdInitDirectDrawing(); + lcd->clear(); + drawChargingInfo(chargeState); + + // DEBUG INFO +#if 0 + char buffer[1024]; + + sprintf(buffer, "%d,%d,%d,%d", uCharger.isChargerDetectionReady, uCharger.hasCharger, IS_UCHARGER_ACTIVE(), uCharger.chargerSamplingCount); + lcd->drawSizedText(100, 10, buffer, strlen(buffer), CENTERED | COLOR_THEME_PRIMARY2); + + sprintf(buffer, "%d,%d,%d,%d,%d,", uCharger.isChargingDetectionReady, uCharger.isChargeEnd, IS_UCHARGER_CHARGE_END_ACTIVE(), uCharger.chargingSamplingCount, uCharger.chargeEndSamplingCount); + lcd->drawSizedText(100, 40, buffer, strlen(buffer), CENTERED | COLOR_THEME_PRIMARY2); + + sprintf(buffer, "%d,%d,%d,%d,%d", wCharger.isChargerDetectionReady, wCharger.hasCharger, IS_WCHARGER_ACTIVE(), wCharger.chargerSamplingCount, wCharger.isHighCurrent); + lcd->drawSizedText(100, 70, buffer, strlen(buffer), CENTERED | COLOR_THEME_PRIMARY2); + + sprintf(buffer, "%d,%d,%d,%d,%d,", wCharger.isChargingDetectionReady, wCharger.isChargeEnd, IS_WCHARGER_CHARGE_END_ACTIVE(), wCharger.chargingSamplingCount, wCharger.chargeEndSamplingCount); + lcd->drawSizedText(100, 100, buffer, strlen(buffer), CENTERED | COLOR_THEME_PRIMARY2); + + sprintf(buffer, "%d", isChargerActive()); + lcd->drawSizedText(100, 130, buffer, strlen(buffer), CENTERED | COLOR_THEME_PRIMARY2); +#endif + + lcdRefresh(); + } +#endif +} + diff --git a/radio/src/targets/pl18/battery_driver.h b/radio/src/targets/pl18/battery_driver.h new file mode 100644 index 00000000000..6fdd651d51f --- /dev/null +++ b/radio/src/targets/pl18/battery_driver.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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. + */ + +/*************************************************************************************************** + +***************************************************************************************************/ +#ifndef __BATTERY_DRIVER_H__ + #define __BATTERY_DRIVER_H__ +/*************************************************************************************************** + +***************************************************************************************************/ + +#include "board.h" +#include "hal.h" + +enum ChargeState +{ + CHARGE_UNKNOWN, + CHARGE_NONE, + CHARGE_STARTED, + CHARGE_FINISHED +}; + +#define IS_UCHARGER_ACTIVE() GPIO_ReadInputDataBit(UCHARGER_GPIO, UCHARGER_GPIO_PIN) +#define IS_UCHARGER_CHARGE_END_ACTIVE() GPIO_ReadInputDataBit(UCHARGER_CHARGE_END_GPIO, UCHARGER_CHARGE_END_GPIO_PIN) +#define ENABLE_UCHARGER() GPIO_SetBits(UCHARGER_EN_GPIO, UCHARGER_EN_GPIO_PIN) +#define DISABLE_UCHARGER() GPIO_ResetBits(UCHARGER_EN_GPIO, UCHARGER_EN_GPIO_PIN) + +#define IS_WCHARGER_ACTIVE() GPIO_ReadInputDataBit(WCHARGER_GPIO, WCHARGER_GPIO_PIN) +#define IS_WCHARGER_CHARGE_END_ACTIVE() GPIO_ReadInputDataBit(WCHARGER_CHARGE_END_GPIO, WCHARGER_CHARGE_END_GPIO_PIN) +#define ENABLE_WCHARGER() GPIO_SetBits(WCHARGER_EN_GPIO, WCHARGER_EN_GPIO_PIN) +#define DISABLE_WCHARGER() GPIO_ResetBits(WCHARGER_EN_GPIO, WCHARGER_EN_GPIO_PIN) +#define WCHARGER_CURRENT_LOW() GPIO_ResetBits(WCHARGER_I_CONTROL_GPIO, WCHARGER_I_CONTROL_GPIO_PIN) +#define WCHARGER_CURRENT_HIGH() GPIO_SetBits(WCHARGER_I_CONTROL_GPIO, WCHARGER_I_CONTROL_GPIO_PIN) + +extern void battery_charge_init(); +extern void handle_battery_charge(uint32_t last_press_time); +extern uint16_t get_battery_charge_state(); +extern uint16_t getBatteryVoltage(); // returns current battery voltage in 10mV steps +extern bool isChargerActive(); + +#endif diff --git a/radio/src/targets/pl18/board.cpp b/radio/src/targets/pl18/board.cpp new file mode 100644 index 00000000000..1483406932e --- /dev/null +++ b/radio/src/targets/pl18/board.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "stm32_adc.h" + +#include "board.h" +#include "boards/generic_stm32/module_ports.h" + +#include "hal/adc_driver.h" +#include "hal/trainer_driver.h" +#include "hal/switch_driver.h" + +#include "globals.h" +#include "sdcard.h" +#include "touch.h" +#include "debug.h" + +#include "flysky_gimbal_driver.h" +#include "timers_driver.h" + +#include "battery_driver.h" +#include "tp_cst340.h" +#include "watchdog_driver.h" + +#include "bitmapbuffer.h" +#include "colors.h" + +#include + +#if defined(__cplusplus) && !defined(SIMU) +extern "C" { +#endif +#include "usb_dcd_int.h" +#include "usb_bsp.h" +#if defined(__cplusplus) && !defined(SIMU) +} +#endif + +// common ADC driver +extern const etx_hal_adc_driver_t _adc_driver; + +enum PowerReason { + SHUTDOWN_REQUEST = 0xDEADBEEF, + SOFTRESET_REQUEST = 0xCAFEDEAD, +}; + +constexpr uint32_t POWER_REASON_SIGNATURE = 0x0178746F; + +bool UNEXPECTED_SHUTDOWN() +{ +#if defined(SIMU) || defined(NO_UNEXPECTED_SHUTDOWN) + return false; +#else + if (WAS_RESET_BY_WATCHDOG()) + return true; + else if (WAS_RESET_BY_SOFTWARE()) + return RTC->BKP0R != SOFTRESET_REQUEST; + else + return RTC->BKP1R == POWER_REASON_SIGNATURE && RTC->BKP0R != SHUTDOWN_REQUEST; +#endif +} + +void SET_POWER_REASON(uint32_t value) +{ + RTC->BKP0R = value; + RTC->BKP1R = POWER_REASON_SIGNATURE; +} + +void watchdogInit(unsigned int duration) +{ + // IWDG->KR = 0x5555; // Unlock registers + // IWDG->PR = 3; // Divide by 32 => 1kHz clock + // IWDG->KR = 0x5555; // Unlock registers + // IWDG->RLR = duration; // 1.5 seconds nominal + // IWDG->KR = 0xAAAA; // reload + // IWDG->KR = 0xCCCC; // start +} + +#if defined(SEMIHOSTING) +extern "C" void initialise_monitor_handles(); +#endif + +#if defined(SPI_FLASH) +extern "C" void flushFTL(); +#endif + +void delay_self(int count) +{ + for (int i = 50000; i > 0; i--) + { + for (; count > 0; count--); + } +} +#define RCC_AHB1PeriphMinimum (PWR_RCC_AHB1Periph |\ + LCD_RCC_AHB1Periph |\ + BACKLIGHT_RCC_AHB1Periph |\ + SDRAM_RCC_AHB1Periph \ + ) +#define RCC_AHB1PeriphOther (AUDIO_RCC_AHB1Periph |\ + TELEMETRY_RCC_AHB1Periph |\ + TRAINER_RCC_AHB1Periph |\ + HAPTIC_RCC_AHB1Periph |\ + EXTMODULE_RCC_AHB1Periph \ + ) +#define RCC_AHB3PeriphMinimum (SDRAM_RCC_AHB3Periph) +#define RCC_APB1PeriphMinimum (BACKLIGHT_RCC_APB1Periph) +#define RCC_APB1PeriphOther (TELEMETRY_RCC_APB1Periph |\ + AUDIO_RCC_APB1Periph \ + ) +#define RCC_APB2PeriphMinimum (LCD_RCC_APB2Periph) +#define RCC_APB2PeriphOther (HAPTIC_RCC_APB2Periph) + +void boardInit() +{ +#if defined(SEMIHOSTING) + initialise_monitor_handles(); +#endif + +#if !defined(SIMU) + RCC_AHB1PeriphClockCmd(RCC_AHB1PeriphMinimum | RCC_AHB1PeriphOther, ENABLE); + RCC_AHB3PeriphClockCmd(RCC_AHB3PeriphMinimum, ENABLE); + RCC_APB1PeriphClockCmd(RCC_APB1PeriphMinimum | RCC_APB1PeriphOther, ENABLE); + RCC_APB2PeriphClockCmd(RCC_APB2PeriphMinimum | RCC_APB2PeriphOther, ENABLE); + + // enable interrupts + __enable_irq(); +#endif + +#if defined(DEBUG) + serialInit(SP_AUX1, UART_MODE_DEBUG); +#endif + + TRACE("\nPL18 board started :)"); + delay_ms(10); + TRACE("RCC->CSR = %08x", RCC->CSR); + + pwrInit(); + boardInitModulePorts(); + + init_trainer(); + battery_charge_init(); + flysky_gimbal_init(); + timersInit(); + TouchInit(); + usbInit(); + + uint32_t press_start = 0; + uint32_t press_end = 0; + + if (UNEXPECTED_SHUTDOWN()) { + pwrOn(); + } else if (isChargerActive()) { + while (true) { + pwrOn(); + uint32_t now = get_tmr10ms(); + if (pwrPressed()) { + press_end = now; + if (press_start == 0) press_start = now; + if ((now - press_start) > POWER_ON_DELAY) { + break; + } + } else if (!isChargerActive()) { + boardOff(); + } else { + uint32_t press_end_touch = press_end; + if (touchPanelEventOccured()) { + touchPanelRead(); + press_end_touch = get_tmr10ms(); + } + press_start = 0; + handle_battery_charge(press_end_touch); + delay_ms(10); + press_end = 0; + } + } + } + + keysInit(); + switchInit(); + audioInit(); + adcInit(&_adc_driver); + hapticInit(); + + + #if defined(RTCLOCK) + rtcInit(); // RTC must be initialized before rambackupRestore() is called +#endif + + +#if defined(DEBUG) + DBGMCU_APB1PeriphConfig( + DBGMCU_IWDG_STOP | DBGMCU_TIM1_STOP | DBGMCU_TIM2_STOP | + DBGMCU_TIM3_STOP | DBGMCU_TIM4_STOP | DBGMCU_TIM5_STOP | + DBGMCU_TIM6_STOP | DBGMCU_TIM7_STOP | DBGMCU_TIM8_STOP | + DBGMCU_TIM9_STOP | DBGMCU_TIM10_STOP | DBGMCU_TIM11_STOP | + DBGMCU_TIM12_STOP | DBGMCU_TIM13_STOP | DBGMCU_TIM14_STOP, + ENABLE); +#endif +} + +extern void rtcDisableBackupReg(); + +void boardOff() +{ + lcdOff(); + + while (pwrPressed()) { + WDG_RESET(); + } + + SysTick->CTRL = 0; // turn off systick + + // Shutdown the Haptic + hapticDone(); + + rtcDisableBackupReg(); + +#if !defined(BOOT) + if (isChargerActive()) + { + delay_ms(100); // Add a delay to wait for lcdOff + RTC->BKP0R = SOFTRESET_REQUEST; + NVIC_SystemReset(); + } + else +#endif + { + RTC->BKP0R = SHUTDOWN_REQUEST; + pwrOff(); + } + + // We reach here only in forced power situations, such as hw-debugging with external power + // Enter STM32 stop mode / deep-sleep + // Code snippet from ST Nucleo PWR_EnterStopMode example +#define PDMode 0x00000000U +#if defined(PWR_CR_MRUDS) && defined(PWR_CR_LPUDS) && defined(PWR_CR_FPDS) + MODIFY_REG(PWR->CR, (PWR_CR_PDDS | PWR_CR_LPDS | PWR_CR_FPDS | PWR_CR_LPUDS | PWR_CR_MRUDS), PDMode); +#elif defined(PWR_CR_MRLVDS) && defined(PWR_CR_LPLVDS) && defined(PWR_CR_FPDS) + MODIFY_REG(PWR->CR, (PWR_CR_PDDS | PWR_CR_LPDS | PWR_CR_FPDS | PWR_CR_LPLVDS | PWR_CR_MRLVDS), PDMode); +#else + MODIFY_REG(PWR->CR, (PWR_CR_PDDS| PWR_CR_LPDS), PDMode); +#endif /* PWR_CR_MRUDS && PWR_CR_LPUDS && PWR_CR_FPDS */ + +/* Set SLEEPDEEP bit of Cortex System Control Register */ + SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk)); + + // To avoid HardFault at return address, end in an endless loop + while (1) { + + } +} + +int usbPlugged() +{ + static uint8_t debouncedState = 0; + static uint8_t lastState = 0; + + uint8_t state = GPIO_ReadInputDataBit(UCHARGER_GPIO, UCHARGER_GPIO_PIN); + + if (state == lastState) + debouncedState = state; + else + lastState = state; + + return debouncedState; +} diff --git a/radio/src/targets/pl18/board.h b/radio/src/targets/pl18/board.h new file mode 100644 index 00000000000..fcdf46eecb8 --- /dev/null +++ b/radio/src/targets/pl18/board.h @@ -0,0 +1,271 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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. + */ + +#ifndef _BOARD_H_ +#define _BOARD_H_ + +#include "definitions.h" +#include "opentx_constants.h" + +#include "board_common.h" +#include "hal.h" +#include "hal/serial_port.h" + +#define FLASHSIZE 0x200000 +#define BOOTLOADER_SIZE 0x20000 +#define FIRMWARE_ADDRESS 0x08000000 + +#define MB *1024*1024 +#define LUA_MEM_EXTRA_MAX (2 MB) // max allowed memory usage for Lua bitmaps (in bytes) +#define LUA_MEM_MAX (6 MB) // max allowed memory usage for complete Lua (in bytes), 0 means unlimited + +extern uint16_t sessionTimer; + +#define SLAVE_MODE() (g_model.trainerData.mode == TRAINER_MODE_SLAVE) + +// Board driver +void boardInit(); +void boardOff(); + +// CPU Unique ID +#define LEN_CPU_UID (3*8+2) +void getCPUUniqueID(char * s); + +// Flash Write driver +#define FLASH_PAGESIZE 256 +void unlockFlash(); +void lockFlash(); +void flashWrite(uint32_t * address, const uint32_t * buffer); +uint32_t isFirmwareStart(const uint8_t * buffer); +uint32_t isBootloaderStart(const uint8_t * buffer); + +// SDRAM driver +void SDRAM_Init(); + +// Pulses driver +#if !defined(SIMU) + +#define INTERNAL_MODULE_ON() GPIO_SetBits(INTMODULE_PWR_GPIO, INTMODULE_PWR_GPIO_PIN) +#define INTERNAL_MODULE_OFF() GPIO_ResetBits(INTMODULE_PWR_GPIO, INTMODULE_PWR_GPIO_PIN) +#define EXTERNAL_MODULE_ON() GPIO_SetBits(EXTMODULE_PWR_GPIO, EXTMODULE_PWR_GPIO_PIN) +#define EXTERNAL_MODULE_OFF() GPIO_ResetBits(EXTMODULE_PWR_GPIO, EXTMODULE_PWR_GPIO_PIN) +#define EXTERNAL_MODULE_PWR_OFF EXTERNAL_MODULE_OFF +#define BLUETOOTH_MODULE_ON() GPIO_ResetBits(BT_EN_GPIO, BT_EN_GPIO_PIN) +#define BLUETOOTH_MODULE_OFF() GPIO_SetBits(BT_EN_GPIO, BT_EN_GPIO_PIN) +#define IS_INTERNAL_MODULE_ON() (false) +#define IS_EXTERNAL_MODULE_ON() (GPIO_ReadInputDataBit(EXTMODULE_PWR_GPIO, EXTMODULE_PWR_GPIO_PIN) == Bit_SET) + +#else + +#define INTERNAL_MODULE_OFF() +#define INTERNAL_MODULE_ON() +#define EXTERNAL_MODULE_ON() +#define EXTERNAL_MODULE_OFF() +#define BLUETOOTH_MODULE_ON() +#define BLUETOOTH_MODULE_OFF() +#define IS_INTERNAL_MODULE_ON() (false) +#define IS_EXTERNAL_MODULE_ON() (false) + +#endif // defined(SIMU) + +#if !defined(NUM_FUNCTIONS_SWITCHES) +#define NUM_FUNCTIONS_SWITCHES 0 +#endif + +#define NUM_TRIMS 8 +#define DEFAULT_STICK_DEADZONE 2 + +#define BATTERY_WARN 37 // 3.7V +#define BATTERY_MIN 35 // 3.4V +#define BATTERY_MAX 43 // 4.3V +#define BATTERY_DIVIDER 962 + +enum EnumPowerupState +{ + BOARD_POWER_OFF = 0xCAFEDEAD, + BOARD_POWER_ON = 0xDEADBEEF, + BOARD_STARTED = 0xBAADF00D, + BOARD_REBOOT = 0xC00010FF, +}; + +bool UNEXPECTED_SHUTDOWN(); +void SET_POWER_REASON(uint32_t value); + +#if defined(__cplusplus) && !defined(SIMU) +extern "C" { +#endif + +// Power driver +#define SOFT_PWR_CTRL +#define POWER_ON_DELAY 10 // 1s +void pwrInit(); +void extModuleInit(); +uint32_t pwrCheck(); +uint32_t lowPowerCheck(); + +void pwrOn(); +void pwrSoftReboot(); +void pwrOff(); +void pwrResetHandler(); +bool pwrPressed(); +bool pwrOffPressed(); +#if defined(PWR_EXTRA_SWITCH_GPIO) + bool pwrForcePressed(); +#else + #define pwrForcePressed() false +#endif +uint32_t pwrPressedDuration();; + +const etx_serial_port_t* auxSerialGetPort(int port_nr); +#define AUX_SERIAL_POWER_ON() +#define AUX_SERIAL_POWER_OFF() + +// LCD driver +#define LCD_W 480 +#define LCD_H 320 +#define LCD_DEPTH 16 +#define LCD_CONTRAST_DEFAULT 20 + +void lcdInit(); +void lcdCopy(void * dest, void * src); + +void DMAFillRect(uint16_t* dest, uint16_t destw, uint16_t desth, uint16_t x, + uint16_t y, uint16_t w, uint16_t h, uint16_t color); +void DMACopyBitmap(uint16_t* dest, uint16_t destw, uint16_t desth, uint16_t x, + uint16_t y, const uint16_t* src, uint16_t srcw, + uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, + uint16_t h); +void DMACopyAlphaBitmap(uint16_t* dest, uint16_t destw, uint16_t desth, + uint16_t x, uint16_t y, const uint16_t* src, + uint16_t srcw, uint16_t srch, uint16_t srcx, + uint16_t srcy, uint16_t w, uint16_t h); +void DMACopyAlphaMask(uint16_t* dest, uint16_t destw, uint16_t desth, + uint16_t x, uint16_t y, const uint8_t* src, uint16_t srcw, + uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, + uint16_t h, uint16_t bg_color); +void DMABitmapConvert(uint16_t* dest, const uint8_t* src, uint16_t w, + uint16_t h, uint32_t format); + +void lcdOff(); +void lcdOn(); + +#define lcdRefreshWait(...) + +// Backlight driver +#define BACKLIGHT_LEVEL_MAX 100 +#define BACKLIGHT_FORCED_ON BACKLIGHT_LEVEL_MAX + 1 +#define BACKLIGHT_LEVEL_MIN 1 + +extern bool boardBacklightOn; +void backlightLowInit( void ); +void backlightInit(); +void backlightEnable(uint8_t dutyCycle); +void backlightFullOn(); +bool isBacklightEnabled(); + +#define BACKLIGHT_ENABLE() \ + { \ + boardBacklightOn = true; \ + backlightEnable(globalData.unexpectedShutdown \ + ? BACKLIGHT_LEVEL_MAX \ + : BACKLIGHT_LEVEL_MAX - currentBacklightBright); \ + } + +#define BACKLIGHT_DISABLE() \ + { \ + boardBacklightOn = false; \ + backlightEnable(globalData.unexpectedShutdown ? BACKLIGHT_LEVEL_MAX \ + : ((g_eeGeneral.blOffBright == BACKLIGHT_LEVEL_MIN) && \ + (g_eeGeneral.backlightMode != e_backlight_mode_off)) \ + ? 0 \ + : g_eeGeneral.blOffBright); \ + } + +#if !defined(SIMU) +void usbJoystickUpdate(); +#endif +#define USB_NAME "FlySky PL18" +#define USB_MANUFACTURER 'F', 'l', 'y', 'S', 'k', 'y', ' ', ' ' /* 8 bytes */ +#define USB_PRODUCT 'P', 'L', '1', '8', ' ', ' ', ' ', ' ' /* 8 Bytes */ + +#if defined(__cplusplus) && !defined(SIMU) +} +#endif + +// Audio driver +void audioInit(); +void audioConsumeCurrentBuffer(); +void audioSpiWriteBuffer(const uint8_t * buffer, uint32_t size); +void audioSpiSetSpeed(uint8_t speed); +uint8_t audioHardReset(); +uint8_t audioSoftReset(); +void audioSendRiffHeader(); +void audioOn(); +void audioOff(); +bool isAudioReady(); +bool audioChipReset(); + +#define SPI_SPEED_2 0 +#define SPI_SPEED_4 1 +#define SPI_SPEED_8 2 +#define SPI_SPEED_16 3 +#define SPI_SPEED_32 4 +#define SPI_SPEED_64 5 +#define SPI_SPEED_128 6 +#define SPI_SPEED_256 7 + +#define audioDisableIrq() // interrupts must stay enabled on Horus +#define audioEnableIrq() // interrupts must stay enabled on Horus +#if defined(PCBNV14) +#define setSampleRate(freq) +#else +void setSampleRate(uint32_t frequency); +#endif +void setScaledVolume(uint8_t volume); +void setVolume(uint8_t volume); +int32_t getVolume(); +#define VOLUME_LEVEL_MAX 23 +#define VOLUME_LEVEL_DEF 12 + +// Telemetry driver +#define INTMODULE_FIFO_SIZE 512 +#define TELEMETRY_FIFO_SIZE 512 + +// Haptic driver +void hapticInit(); +void hapticDone(); +void hapticOff(); +void hapticOn(uint32_t pwmPercent); + +// Second serial port driver +//#define AUX_SERIAL +#define DEBUG_BAUDRATE 115200 +#define LUA_DEFAULT_BAUDRATE 115200 + +extern uint8_t currentTrainerMode; +void checkTrainerSettings(); + +// Touch panel driver +bool touchPanelEventOccured(); +struct TouchState touchPanelRead(); +struct TouchState getInternalTouchState(); + +#endif // _BOARD_H_ diff --git a/radio/src/targets/pl18/bootloader/boot_menu.cpp b/radio/src/targets/pl18/bootloader/boot_menu.cpp new file mode 100644 index 00000000000..1848732b404 --- /dev/null +++ b/radio/src/targets/pl18/bootloader/boot_menu.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "board.h" +#include "fw_version.h" +#include "lcd.h" + +#include "translations.h" + +#include "../../common/arm/stm32/bootloader/boot.h" +#include "../../common/arm/stm32/bootloader/bin_files.h" + +#include + +#define SELECTED_COLOR (INVERS | COLOR_THEME_SECONDARY1) +#define DEFAULT_PADDING 28 +#define DOUBLE_PADDING 56 +#define MESSAGE_TOP (LCD_H - (2*DOUBLE_PADDING)) + +const uint8_t __bmp_plug_usb[] { +#include "bmp_plug_usb.lbm" +}; +LZ4Bitmap BMP_PLUG_USB(BMP_ARGB4444, __bmp_plug_usb); + +const uint8_t __bmp_usb_plugged[] { +#include "bmp_usb_plugged.lbm" +}; +LZ4Bitmap BMP_USB_PLUGGED(BMP_ARGB4444, __bmp_usb_plugged); + +const uint8_t __bmp_background[] { +#include "bmp_background.lbm" +}; +LZ4Bitmap BMP_BACKGROUND(BMP_ARGB4444, __bmp_background); + +#define BL_GREEN COLOR2FLAGS(RGB(73, 219, 62)) +#define BL_RED COLOR2FLAGS(RGB(229, 32, 30)) +#define BL_BACKGROUND COLOR2FLAGS(BLACK) +#define BL_FOREGROUND COLOR2FLAGS(WHITE) +#define BL_SELECTED COLOR2FLAGS(RGB(11, 65, 244)) // deep blue + +extern BitmapBuffer * lcd; + +void bootloaderInitScreen() +{ + lcdInitDisplayDriver(); + backlightInit(); + backlightEnable(100); + setTrimsAsButtons(true); +} + +static void bootloaderDrawTitle(const char* text) +{ + lcd->drawText(LCD_W/2, DEFAULT_PADDING, text, CENTERED | BL_FOREGROUND); + lcd->drawSolidFilledRect(DEFAULT_PADDING, DOUBLE_PADDING, LCD_W - DOUBLE_PADDING, 2, BL_FOREGROUND); +} + +static void bootloaderDrawFooter() +{ + lcd->drawSolidFilledRect(DEFAULT_PADDING, LCD_H - (DOUBLE_PADDING + 4), LCD_W - DOUBLE_PADDING, 2, BL_FOREGROUND); +} + +static void bootloaderDrawBackground() +{ + // we have plenty of memory, let's cache that background + static BitmapBuffer* _background = nullptr; + + if (!_background) { + _background = new BitmapBuffer(BMP_RGB565, LCD_W, LCD_H); + + for (int i=0; idrawBitmap(i, j, bg_bmp); + } + } + _background->drawFilledRect(0, 0, LCD_W, LCD_H, SOLID, + COLOR2FLAGS(BLACK), OPACITY(4)); + } + + if (_background) { + lcd->drawBitmap(0, 0, _background); + lcd->drawFilledRect(0, 0, LCD_W, LCD_H, SOLID, + COLOR2FLAGS(BLACK), OPACITY(4)); + } + else { + lcd->clear(BL_BACKGROUND); + } +} + +void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) +{ + lcdInitDirectDrawing(); + bootloaderDrawBackground(); + + int center = LCD_W/2; + if (st == ST_START) { + + bootloaderDrawTitle(BOOTLOADER_TITLE); + + lcd->drawText(62, 75, LV_SYMBOL_CHARGE, BL_FOREGROUND); + coord_t pos = lcd->drawText(84, 75, "Write Firmware", BL_FOREGROUND); + pos += 8; + +#if defined(SPI_FLASH) + lcd->drawText(60, 110, LV_SYMBOL_WARNING, BL_FOREGROUND); + pos = lcd->drawText(84, 110, "Erase Flash Storage", BL_FOREGROUND); + pos += 8; + + lcd->drawText(60, 145, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); + lcd->drawText(84, 145, "Exit", BL_FOREGROUND); +#else + lcd->drawText(60, 110, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); + lcd->drawText(84, 110, "Exit", BL_FOREGROUND); +#endif + + pos -= 79; + lcd->drawSolidRect(79, 72 + (opt*35), pos, 26, 2, BL_SELECTED); + + lcd->drawBitmap(center - 55, 165, (const BitmapBuffer*)&BMP_PLUG_USB); + lcd->drawText(center, 250, "Or plug in a USB cable", CENTERED | BL_FOREGROUND); + lcd->drawText(center, 275, "for mass storage", CENTERED | BL_FOREGROUND); + + bootloaderDrawFooter(); + lcd->drawText(center, LCD_H - DOUBLE_PADDING, + "Current Firmware:", CENTERED | BL_FOREGROUND); + lcd->drawText(center, LCD_H - DEFAULT_PADDING, + getFirmwareVersion(nullptr), CENTERED | BL_FOREGROUND); + } + +// #if defined(SPI_FLASH) && defined(SDCARD) +// else if (st == ST_SELECT_STORAGE) { +// bootloaderDrawTitle(LV_SYMBOL_DIRECTORY " select storage"); +// lcd->drawText(62, 75, LV_SYMBOL_DIRECTORY, BL_FOREGROUND); +// coord_t pos = lcd->drawText(84, 75, "Internal", BL_FOREGROUND); +// pos += 8; +// lcd->drawText(60, 110, LV_SYMBOL_SD_CARD, BL_FOREGROUND); +// lcd->drawText(84, 110, "SD Card", BL_FOREGROUND); +// pos -= 79; +// lcd->drawSolidRect(79, (opt == 0) ? 72 : 107, pos, 26, 2, BL_SELECTED); +// bootloaderDrawFooter(); +// lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, +// "[R TRIM] to select storage", BL_FOREGROUND); +// lcd->drawText(DOUBLE_PADDING, LCD_H - DEFAULT_PADDING, +// LV_SYMBOL_NEW_LINE " [L TRIM] to exit", BL_FOREGROUND); +// } +// #endif + +#if defined(SPI_FLASH) + else if (st == ST_CLEAR_FLASH_CHECK) { + + bootloaderDrawTitle("erase internal flash storage"); + + lcd->drawText(62, 75, LV_SYMBOL_DRIVE, BL_FOREGROUND); + coord_t pos = lcd->drawText(84, 75, "Erase Flash Storage", BL_FOREGROUND); + pos += 8; + + lcd->drawText(60, 110, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); + lcd->drawText(84, 110, "Exit", BL_FOREGROUND); + + pos -= 79; + lcd->drawSolidRect(79, (opt == 0) ? 72 : 107, pos, 26, 2, BL_SELECTED); + + bootloaderDrawFooter(); + lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, "Hold [R TRIM] long to erase storage", BL_FOREGROUND); + lcd->drawText(DOUBLE_PADDING, LCD_H - DEFAULT_PADDING, LV_SYMBOL_NEW_LINE "[L TRIM] to exit", BL_FOREGROUND); + } + else if (st == ST_CLEAR_FLASH) { + bootloaderDrawTitle("erasing internal flash storage"); + + lcd->drawText(62, 75, "This may take up to 150s", BL_FOREGROUND); + bootloaderDrawFooter(); + } +#endif + + else if (st == ST_USB) { + lcd->drawBitmap(center - 26, 98, (const BitmapBuffer*)&BMP_USB_PLUGGED); + lcd->drawText(center, 168, "USB Connected", CENTERED | BL_FOREGROUND); + } else if (st == ST_FILE_LIST || st == ST_DIR_CHECK || + st == ST_FLASH_CHECK || st == ST_FLASHING || + st == ST_FLASH_DONE) { + + bootloaderDrawTitle(LV_SYMBOL_SD_CARD " /FIRMWARE"); + + if (st == ST_FLASHING || st == ST_FLASH_DONE) { + LcdFlags color = BL_RED; // red + + if (st == ST_FLASH_DONE) { + color = BL_GREEN /* green */; + opt = 100; // Completed > 100% + } + + lcd->drawRect(DEFAULT_PADDING, 120, LCD_W - DOUBLE_PADDING, 31, 2, + SOLID, BL_SELECTED); + lcd->drawSolidFilledRect(DEFAULT_PADDING + 4, 124, + ((LCD_W - DOUBLE_PADDING - 8) * opt) / 100, 23, + color); + } else if (st == ST_DIR_CHECK) { + if (opt == FR_NO_PATH) { + lcd->drawText(20, MESSAGE_TOP, + LV_SYMBOL_CLOSE " Directory is missing", BL_FOREGROUND); + } else { + lcd->drawText(20, MESSAGE_TOP, LV_SYMBOL_CLOSE " Directory is empty", + BL_FOREGROUND); + } + } else if (st == ST_FLASH_CHECK) { + bootloaderDrawFilename(str, 0, true); + + if (opt == FC_ERROR) { + lcd->drawText(20, MESSAGE_TOP, + LV_SYMBOL_CLOSE " " TR_BL_INVALID_FIRMWARE, + BL_FOREGROUND); + } else if (opt == FC_OK) { + VersionTag tag; + memset(&tag, 0, sizeof(tag)); + extractFirmwareVersion(&tag); + + lcd->drawText(LCD_W / 4 + DEFAULT_PADDING, + MESSAGE_TOP - DEFAULT_PADDING, + "Fork:", RIGHT | BL_FOREGROUND); + lcd->drawSizedText(LCD_W / 4 + 6 + DEFAULT_PADDING, + MESSAGE_TOP - DEFAULT_PADDING, tag.fork, 6, + BL_FOREGROUND); + + lcd->drawText(LCD_W / 4 + DEFAULT_PADDING, MESSAGE_TOP, + "Version:", RIGHT | BL_FOREGROUND); + lcd->drawText(LCD_W / 4 + 6 + DEFAULT_PADDING, MESSAGE_TOP, + tag.version, BL_FOREGROUND); + + lcd->drawText(LCD_W / 4 + DEFAULT_PADDING, + MESSAGE_TOP + DEFAULT_PADDING, + "Radio:", RIGHT | BL_FOREGROUND); + lcd->drawText(LCD_W / 4 + 6 + DEFAULT_PADDING, + MESSAGE_TOP + DEFAULT_PADDING, tag.flavour, + BL_FOREGROUND); + + lcd->drawText(LCD_W - DOUBLE_PADDING, MESSAGE_TOP - 10, + LV_SYMBOL_OK, BL_GREEN); + } + } + + bootloaderDrawFooter(); + + if (st != ST_DIR_CHECK && (st != ST_FLASH_CHECK || opt == FC_OK)) { + + lcd->drawText(DEFAULT_PADDING, LCD_H - DOUBLE_PADDING - 2, + LV_SYMBOL_CHARGE, BL_FOREGROUND); + + if (st == ST_FILE_LIST) { + lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, + "[R TRIM] to select file", BL_FOREGROUND); + } else if (st == ST_FLASH_CHECK && opt == FC_OK) { + lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, + "Hold [R TRIM] long to flash", BL_FOREGROUND); + } else if (st == ST_FLASHING) { + lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, + "Writing Firmware ...", BL_FOREGROUND); + } else if (st == ST_FLASH_DONE) { + lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, + "Writing Completed", BL_FOREGROUND); + } + } + + if (st != ST_FLASHING) { + lcd->drawText(DOUBLE_PADDING, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_NEW_LINE " [L TRIM] to exit", BL_FOREGROUND); + } + } +} + +void bootloaderDrawFilename(const char* str, uint8_t line, bool selected) +{ + lcd->drawText(DEFAULT_PADDING, 75 + (line * 25), LV_SYMBOL_FILE, BL_FOREGROUND); + lcd->drawText(DEFAULT_PADDING + 30, 75 + (line * 25), str, BL_FOREGROUND); + + if (selected) { + lcd->drawSolidRect(DEFAULT_PADDING + 25, 72 + (line * 25), LCD_W - (DEFAULT_PADDING + 25) - 28, 26, 2, BL_SELECTED); + } +} +uint32_t bootloaderGetMenuItemCount(int baseCount) +{ + return baseCount; +} + +bool bootloaderRadioMenu(uint32_t menuItem, event_t event) +{ + return true; +} + +void blExit(void) +{ + lcdClear(); + lcdRefresh(); + lcdRefreshWait(); +} diff --git a/radio/src/targets/pl18/extmodule_helper.cpp b/radio/src/targets/pl18/extmodule_helper.cpp new file mode 100644 index 00000000000..dbb02e90be2 --- /dev/null +++ b/radio/src/targets/pl18/extmodule_helper.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "board.h" + +void EXTERNAL_MODULE_ON() +{ + GPIO_SetBits(EXTMODULE_PWR_GPIO, EXTMODULE_PWR_GPIO_PIN); +} + +void EXTERNAL_MODULE_OFF() +{ + GPIO_ResetBits(EXTMODULE_PWR_GPIO, EXTMODULE_PWR_GPIO_PIN); +} + +void extModuleInit() +{ + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = EXTMODULE_TX_INVERT_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(EXTMODULE_TX_INVERT_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = EXTMODULE_RX_INVERT_GPIO_PIN; + GPIO_Init(EXTMODULE_RX_INVERT_GPIO, &GPIO_InitStructure); + + EXTMODULE_TX_INVERTED(); + EXTMODULE_RX_INVERTED(); +} diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h new file mode 100644 index 00000000000..4cc54ffc4e6 --- /dev/null +++ b/radio/src/targets/pl18/hal.h @@ -0,0 +1,647 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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. + */ + +#ifndef _HAL_H_ +#define _HAL_H_ + +#define CPU_FREQ 168000000 + +// HSI is at 168Mhz (over-drive is not enabled!) +#define PERI1_FREQUENCY 42000000 +#define PERI2_FREQUENCY 84000000 +#define TIMER_MULT_APB1 2 +#define TIMER_MULT_APB2 2 + +/* Timers Allocation: + * TIM1 = Haptic + * TIM4 = Trainer + * TIM6 = Audio + * TIM7 = 2 MHz counter + * + * + * TIM14 = 5 ms counter + */ + +/* DMA Allocation: + DMA/Stream/Channel + 1/5/7 DAC/Audio + 2/4/0 ADC1 + 2/0/2 ADC3 + 2/3/4 SDIO +*/ + +// Keys +// PL18/PL18EV only has virtual keys via trim buttons +// #define KEYS_GPIO_PIN_PGUP /* for activating PGUP in keys diagnose screen */ + +// Trims +#define TRIMS_GPIO_REG_LHL +#define TRIMS_GPIO_PIN_LHL + +#define TRIMS_GPIO_REG_LHR +#define TRIMS_GPIO_PIN_LHR + +#define TRIMS_GPIO_REG_LVD +#define TRIMS_GPIO_PIN_LVD + +#define TRIMS_GPIO_REG_LVU +#define TRIMS_GPIO_PIN_LVU + +#define TRIMS_GPIO_REG_RHL +#define TRIMS_GPIO_PIN_RHL + +#define TRIMS_GPIO_REG_RHR +#define TRIMS_GPIO_PIN_RHR + +#define TRIMS_GPIO_REG_RVD +#define TRIMS_GPIO_PIN_RVD + +#define TRIMS_GPIO_REG_RVU +#define TRIMS_GPIO_PIN_RVU + +#define TRIMS_GPIO_REG_LSD +#define TRIMS_GPIO_PIN_LSD + +#define TRIMS_GPIO_REG_LSU +#define TRIMS_GPIO_PIN_LSU + +#define TRIMS_GPIO_REG_RSD +#define TRIMS_GPIO_PIN_RSD + +#define TRIMS_GPIO_REG_RSU +#define TRIMS_GPIO_PIN_RSU + +#define TRIMS_GPIO_REG_T7L +#define TRIMS_GPIO_PIN_T7L + +#define TRIMS_GPIO_REG_T7R +#define TRIMS_GPIO_PIN_T7R + +#define TRIMS_GPIO_REG_T8D +#define TRIMS_GPIO_PIN_T8D + +#define TRIMS_GPIO_REG_T8U +#define TRIMS_GPIO_PIN_T8U + +#define TRIMS_GPIO_REG_TR1U GPIOH->IDR +#define TRIMS_GPIO_PIN_TR1U LL_GPIO_PIN_8 // PH.08 +#define TRIMS_GPIO_REG_TR1D GPIOH->IDR +#define TRIMS_GPIO_PIN_TR1D LL_GPIO_PIN_9 // PH.09 +#define TRIMS_GPIO_REG_TR2U GPIOH->IDR +#define TRIMS_GPIO_PIN_TR2U LL_GPIO_PIN_10 // PH.10 +#define TRIMS_GPIO_REG_TR2D GPIOH->IDR +#define TRIMS_GPIO_PIN_TR2D LL_GPIO_PIN_11 // PH.11 + +// active 4x4 column/row based key-matrix to support up to 16 buttons with only 8 GPIOs +#define TRIMS_GPIO_OUT1 GPIOG +#define TRIMS_GPIO_OUT1_PIN LL_GPIO_PIN_2 // PG.02 +#define TRIMS_GPIO_OUT2 GPIOG +#define TRIMS_GPIO_OUT2_PIN LL_GPIO_PIN_10 // PG.10 +#define TRIMS_GPIO_OUT3 GPIOG +#define TRIMS_GPIO_OUT3_PIN LL_GPIO_PIN_11 // PG.11 +// OUT4 routed on MCU PCB, but not attached to any physical buttons, free to use for extensions +#define TRIMS_GPIO_OUT4 GPIOH +#define TRIMS_GPIO_OUT4_PIN LL_GPIO_PIN_7 // PH.07 + +#define TRIMS_GPIO_REG_IN1 GPIOB->IDR +#define TRIMS_GPIO_PIN_IN1 LL_GPIO_PIN_15 // PB.15 +#define TRIMS_GPIO_REG_IN2 GPIOC->IDR +#define TRIMS_GPIO_PIN_IN2 LL_GPIO_PIN_13 // PC.13 +#define TRIMS_GPIO_REG_IN3 GPIOD->IDR +#define TRIMS_GPIO_PIN_IN3 LL_GPIO_PIN_7 // PD.07 +#define TRIMS_GPIO_REG_IN4 GPIOJ->IDR +#define TRIMS_GPIO_PIN_IN4 LL_GPIO_PIN_12 // PJ.12 + +// Monitor pin +// #define MONITOR_RCC_AHB1Periph (RCC_AHB1Periph_GPIOJ) +// #define VBUS_MONITOR_GPIO (GPIOJ) +// #define VBUS_MONITOR_PIN (LL_GPIO_PIN_14) + +// Switches +// Switches A and C on PL18/PL18EV are 2-position switches, so there is no NEED to configure two pins for Switches A and C. +// Especially, as on current dev. state, using PC8 for SDIO D0 - happy coincidence ;) +// #define SWITCHES_GPIO_REG_A_H GPIOC +// #define SWITCHES_GPIO_PIN_A_H LL_GPIO_PIN_8 // PC.08 +// #define SWITCHES_GPIO_REG_A_L GPIOC +// #define SWITCHES_GPIO_PIN_A_L LL_GPIO_PIN_9 // PC.09 + +#define SWITCHES_GPIO_REG_A GPIOC +#define SWITCHES_GPIO_PIN_A LL_GPIO_PIN_9 // PC.09 + +// High rail of Switch C is not required and thus PC10 is free to use for customizations. +// #define SWITCHES_GPIO_REG_C_H GPIOC +// #define SWITCHES_GPIO_PIN_C_H LL_GPIO_PIN_10 // PC.10 +// #define SWITCHES_GPIO_REG_C_L GPIOC +// #define SWITCHES_GPIO_PIN_C_L LL_GPIO_PIN_11 // PC.11 + +#define SWITCHES_GPIO_REG_C GPIOC +#define SWITCHES_GPIO_PIN_C LL_GPIO_PIN_11 // PC.11 + +// Index of all switches / trims + +#define KEYS_GPIOB_PINS (LL_GPIO_PIN_15) + // PC8 allocated to SDIO D0, is not required to sample SWA ! +#define KEYS_GPIOC_PINS (LL_GPIO_PIN_9 | LL_GPIO_PIN_11 | LL_GPIO_PIN_13) +#define KEYS_GPIOD_PINS (LL_GPIO_PIN_7) +#define KEYS_GPIOH_PINS \ + (LL_GPIO_PIN_8 | LL_GPIO_PIN_9 | LL_GPIO_PIN_10 | LL_GPIO_PIN_11) +#define KEYS_GPIOJ_PINS (LL_GPIO_PIN_12) +#define KEYS_OUT_GPIOG_PINS (LL_GPIO_PIN_2 | LL_GPIO_PIN_10 | LL_GPIO_PIN_11) +#define KEYS_OUT_GPIOH_PINS (LL_GPIO_PIN_7) + + +// ADC + +#define ADC_GPIO_PIN_STICK_LH +#define ADC_GPIO_PIN_STICK_LV +#define ADC_GPIO_PIN_STICK_RV +#define ADC_GPIO_PIN_STICK_RH + +#define ADC_GPIO_PIN_POT1 LL_GPIO_PIN_6 // PA.06 VRA +#define ADC_GPIO_PIN_POT2 LL_GPIO_PIN_4 // PC.04 VRB +#define ADC_GPIO_PIN_POT3 LL_GPIO_PIN_8 // PF.08 VRC +#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 +#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 +#define ADC_GPIO_PIN_SLIDER1 LL_GPIO_PIN_9 // PF.09 VRD/LS +#define ADC_GPIO_PIN_SLIDER2 LL_GPIO_PIN_7 // PA.07 VRE/RS + +// #define ADC_GPIO_PIN_SWA LL_GPIO_PIN_1 // PB.01 +#define ADC_GPIO_PIN_SWB LL_GPIO_PIN_1 // PC.01 +// #define ADC_GPIO_PIN_SWC LL_GPIO_PIN_0 // PB.00 +#define ADC_GPIO_PIN_SWD LL_GPIO_PIN_0 // PC.00 +#define ADC_GPIO_PIN_SWE LL_GPIO_PIN_2 // PC.02 +#define ADC_GPIO_PIN_SWF LL_GPIO_PIN_0 // PB.00 +#define ADC_GPIO_PIN_SWG LL_GPIO_PIN_1 // PB.01 +#define ADC_GPIO_PIN_SWH LL_GPIO_PIN_10 // PF.10 + +#define ADC_GPIO_PIN_BATT LL_GPIO_PIN_5 // PC.05 + +#define ADC_GPIOA_PINS \ + (ADC_GPIO_PIN_POT1 | ADC_GPIO_PIN_EXT1 | ADC_GPIO_PIN_SLIDER2) +#define ADC_GPIOB_PINS (ADC_GPIO_PIN_SWF | ADC_GPIO_PIN_SWG) +#define ADC_GPIOC_PINS \ + (ADC_GPIO_PIN_POT2 | ADC_GPIO_PIN_SWB | ADC_GPIO_PIN_SWD | ADC_GPIO_PIN_SWE | ADC_GPIO_PIN_BATT) +#define ADC_GPIOF_PINS \ + (ADC_GPIO_PIN_POT3 | ADC_GPIO_PIN_EXT2 | ADC_GPIO_PIN_SLIDER1 | ADC_GPIO_PIN_SWH) + +#define ADC_CHANNEL_STICK_LH +#define ADC_CHANNEL_STICK_LV +#define ADC_CHANNEL_STICK_RV +#define ADC_CHANNEL_STICK_RH + +#define ADC_CHANNEL_POT1 LL_ADC_CHANNEL_6 // ADC12_IN6 -> ADC1_IN6 +#define ADC_CHANNEL_POT2 LL_ADC_CHANNEL_14 // ADC12_IN14 -> ADC1_IN14 +#define ADC_CHANNEL_POT3 LL_ADC_CHANNEL_6 // ADC3_IN6 -> ADC3_IN6 +#define ADC_CHANNEL_EXT1 LL_ADC_CHANNEL_2 // ADC123_IN2 -> ADC3_IN2 (Right stick end pot on PL18EV) +#define ADC_CHANNEL_EXT2 LL_ADC_CHANNEL_4 // ADC3_IN4 -> ADC3_IN4 (Left stick end pot on PL18EV) +#define ADC_CHANNEL_SLIDER1 LL_ADC_CHANNEL_7 // ADC3_IN7 -> ADC3_IN7 +#define ADC_CHANNEL_SLIDER2 LL_ADC_CHANNEL_7 // ADC12_IN7 -> ADC1_IN7 + +#define ADC_CHANNEL_SWB LL_ADC_CHANNEL_11 // ADC123_IN11 -> ADC1_IN11 +#define ADC_CHANNEL_SWD LL_ADC_CHANNEL_10 // ADC123_IN10 -> ADC1_IN10 +#define ADC_CHANNEL_SWE LL_ADC_CHANNEL_12 // ADC123_IN12 -> ADC1_IN12 +#define ADC_CHANNEL_SWF LL_ADC_CHANNEL_8 // ADC12_IN8 -> ADC1_IN8 +#define ADC_CHANNEL_SWG LL_ADC_CHANNEL_9 // ADC12_IN9 -> ADC1_IN9 +#define ADC_CHANNEL_SWH LL_ADC_CHANNEL_8 // ADC3_IN8 -> ADC3_IN8 + +#define ADC_CHANNEL_BATT LL_ADC_CHANNEL_15 // ADC12_IN15 -> ADC1_IN15 +#define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 + +#define ADC_MAIN ADC1 +#define ADC_EXT ADC3 +#define ADC_EXT_CHANNELS \ + { ADC_CHANNEL_POT3, ADC_CHANNEL_EXT1, ADC_CHANNEL_EXT2, ADC_CHANNEL_SLIDER1, ADC_CHANNEL_SWH } + +#define ADC_SAMPTIME LL_ADC_SAMPLINGTIME_28CYCLES +#define ADC_DMA DMA2 +#define ADC_DMA_CHANNEL LL_DMA_CHANNEL_0 +#define ADC_DMA_STREAM LL_DMA_STREAM_4 +#define ADC_DMA_STREAM_IRQ DMA2_Stream4_IRQn +#define ADC_DMA_STREAM_IRQHandler DMA2_Stream4_IRQHandler + +#define ADC_EXT_DMA DMA2 +#define ADC_EXT_DMA_CHANNEL LL_DMA_CHANNEL_2 +#define ADC_EXT_DMA_STREAM LL_DMA_STREAM_0 +#define ADC_EXT_DMA_STREAM_IRQ DMA2_Stream0_IRQn +#define ADC_EXT_DMA_STREAM_IRQHandler DMA2_Stream0_IRQHandler +#define ADC_EXT_SAMPTIME LL_ADC_SAMPLINGTIME_28CYCLES +#define ADC_VREF_PREC2 330 + +#define ADC_DIRECTION { \ + 0,0,0,0, /* gimbals */ \ + 0,0,0, /* pots */ \ + 0,0, /* sliders */ \ + 0,0, /* ext1 & 2 */ \ + 0,0, /* vbat, rtc_bat */ \ + -1, /* SWB */ \ + -1, /* SWD */ \ + 0, /* SWE */ \ + 0, /* SWF */ \ + 0, /* SWG */ \ + 0 /* SWH */ \ + } + +// Power +#define PWR_RCC_AHB1Periph RCC_AHB1Periph_GPIOI +#define PWR_ON_GPIO GPIOI +#define PWR_SWITCH_GPIO GPIOI +#define PWR_SWITCH_GPIO_PIN GPIO_Pin_11 // PI.11 +#define PWR_ON_GPIO_PIN GPIO_Pin_14 // PI.14 + +// Chargers (USB and wireless) +#define CHARGER_RCC_AHB1Periph ( RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOH | RCC_AHB1Periph_GPIOI ) + +#define UCHARGER_GPIO GPIOB +#define UCHARGER_GPIO_PIN GPIO_Pin_14 // PB.14 input + +#define UCHARGER_CHARGE_END_GPIO GPIOB +#define UCHARGER_CHARGE_END_GPIO_PIN GPIO_Pin_13 // PB.13 input + +#define UCHARGER_EN_GPIO GPIOG +#define UCHARGER_EN_GPIO_PIN GPIO_Pin_3 // PG.03 output + +#if defined (WIRELESS_CHARGER) + + #define WCHARGER_GPIO GPIOI + #define WCHARGER_GPIO_PIN GPIO_Pin_9 // PI.09 input + + #define WCHARGER_CHARGE_END_GPIO GPIOI + #define WCHARGER_CHARGE_END_GPIO_PIN GPIO_Pin_10 // PI.10 input + + #define WCHARGER_EN_GPIO GPIOH + #define WCHARGER_EN_GPIO_PIN GPIO_Pin_4 // PH.04 output + + #define WCHARGER_I_CONTROL_GPIO GPIOH + #define WCHARGER_I_CONTROL_GPIO_PIN GPIO_Pin_13 // PH.13 output + +#endif + +// TODO! Check IOLL1 to PI.01 connectivity! + +// S.Port update connector +#define SPORT_MAX_BAUDRATE 400000 +#define SPORT_UPDATE_RCC_AHB1Periph 0 +#define HAS_SPORT_UPDATE_CONNECTOR() (false) + +// Led +// #define STATUS_LEDS +#define LED_RCC_AHB1Periph RCC_AHB1Periph_GPIOI +#define LED_GPIO GPIOI +#define LED_GPIO_PIN GPIO_Pin_5 // PI.05 + +// Serial Port (DEBUG) +// We will temporarily used the PPM and the HEARTBEAT PINS +#define AUX_SERIAL_RCC_AHB1Periph (RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOE) +#define AUX_SERIAL_RCC_APB1Periph 0 +#define AUX_SERIAL_RCC_APB2Periph RCC_APB2Periph_USART6 +#define AUX_SERIAL_GPIO GPIOC +#define AUX_SERIAL_GPIO_PIN_TX GPIO_Pin_6 // PC.06 +#define AUX_SERIAL_GPIO_PIN_RX GPIO_Pin_7 // PC.07 +#define AUX_SERIAL_GPIO_PinSource_TX GPIO_PinSource6 +#define AUX_SERIAL_GPIO_PinSource_RX GPIO_PinSource7 +#define AUX_SERIAL_GPIO_AF GPIO_AF_USART6 +#define AUX_SERIAL_USART USART6 +#define AUX_SERIAL_USART_IRQHandler USART6_IRQHandler +#define AUX_SERIAL_USART_IRQn USART6_IRQn +#define AUX_SERIAL_TX_INVERT_GPIO GPIOE +#define AUX_SERIAL_TX_INVERT_GPIO_PIN GPIO_Pin_3 // PE.03 +#define AUX_SERIAL_RX_INVERT_GPIO GPIOI +#define AUX_SERIAL_RX_INVERT_GPIO_PIN GPIO_Pin_15 // PI.15 + +//used in BOOTLOADER +#define SERIAL_RCC_AHB1Periph 0 +#define SERIAL_RCC_APB1Periph 0 +#define AUX2_SERIAL_RCC_AHB1Periph 0 +#define AUX2_SERIAL_RCC_APB1Periph 0 +#define AUX2_SERIAL_RCC_APB2Periph 0 +#define KEYS_BACKLIGHT_RCC_AHB1Periph 0 + +// Telemetry +#define TELEMETRY_RCC_AHB1Periph (RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOJ | RCC_AHB1Periph_DMA1) +#define TELEMETRY_RCC_APB1Periph RCC_APB1Periph_USART2 +#define TELEMETRY_REV_GPIO GPIOJ +#define TELEMETRY_RX_REV_GPIO_PIN GPIO_Pin_8 // PJ.08 +#define TELEMETRY_TX_REV_GPIO_PIN GPIO_Pin_7 // PJ.07 +#define TELEMETRY_DIR_GPIO GPIOJ +#define TELEMETRY_DIR_GPIO_PIN GPIO_Pin_13 // PJ.13 +#define TELEMETRY_SET_INPUT 1 +#define TELEMETRY_GPIO GPIOD +#define TELEMETRY_TX_GPIO_PIN GPIO_Pin_5 // PD.05 +#define TELEMETRY_RX_GPIO_PIN GPIO_Pin_6 // PD.06 +#define TELEMETRY_GPIO_PinSource_TX GPIO_PinSource5 +#define TELEMETRY_GPIO_PinSource_RX GPIO_PinSource6 +#define TELEMETRY_GPIO_AF GPIO_AF_USART2 +#define TELEMETRY_USART USART2 +#define TELEMETRY_DMA DMA1 +#define TELEMETRY_DMA_Stream_TX LL_DMA_STREAM_6 +#define TELEMETRY_DMA_Channel_TX DMA_Channel_4 +#define TELEMETRY_DMA_TX_Stream_IRQ DMA1_Stream6_IRQn +#define TELEMETRY_DMA_TX_IRQHandler DMA1_Stream6_IRQHandler +#define TELEMETRY_DMA_TX_FLAG_TC DMA_IT_TCIF6 +// #define TELEMETRY_DMA_Stream_RX LL_DMA_STREAM_5 +// #define TELEMETRY_DMA_Channel_RX LL_DMA_CHANNEL_4 +#define TELEMETRY_USART_IRQHandler USART2_IRQHandler +#define TELEMETRY_USART_IRQn USART2_IRQn + +#define TELEMETRY_DIR_OUTPUT() TELEMETRY_DIR_GPIO->BSRRH = TELEMETRY_DIR_GPIO_PIN +#define TELEMETRY_DIR_INPUT() TELEMETRY_DIR_GPIO->BSRRL = TELEMETRY_DIR_GPIO_PIN +#define TELEMETRY_TX_POL_NORM() TELEMETRY_REV_GPIO->BSRRH = TELEMETRY_TX_REV_GPIO_PIN +#define TELEMETRY_TX_POL_INV() TELEMETRY_REV_GPIO->BSRRL = TELEMETRY_TX_REV_GPIO_PIN +#define TELEMETRY_RX_POL_NORM() TELEMETRY_REV_GPIO->BSRRH = TELEMETRY_RX_REV_GPIO_PIN +#define TELEMETRY_RX_POL_INV() TELEMETRY_REV_GPIO->BSRRL = TELEMETRY_RX_REV_GPIO_PIN +// USB +#define USB_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOA +#define USB_GPIO GPIOA +// #define USB_GPIO_PIN_VBUS GPIO_Pin_9 // PA.09 +#define USB_GPIO_PIN_ID GPIO_Pin_10 // PA.10 +#define USB_GPIO_PIN_DM GPIO_Pin_11 // PA.11 +#define USB_GPIO_PIN_DP GPIO_Pin_12 // PA.12 +#define USB_GPIO_PinSource_DM GPIO_PinSource11 +#define USB_GPIO_PinSource_DP GPIO_PinSource12 +#define USB_GPIO_AF GPIO_AF_OTG1_FS + +// LCD +#define LCD_RCC_AHB1Periph (RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_GPIOJ | RCC_AHB1Periph_GPIOK | RCC_AHB1Periph_DMA2D) +#define LCD_RCC_APB1Periph 0 +#define LCD_RCC_APB2Periph RCC_APB2Periph_LTDC +#define LCD_NRST_GPIO GPIOG +#define LCD_NRST_GPIO_PIN GPIO_Pin_9 // PG.09 +#define LCD_SPI_GPIO GPIOE +#define LCD_SPI_CS_GPIO_PIN GPIO_Pin_4 // PE.04 +#define LCD_SPI_SCK_GPIO_PIN GPIO_Pin_2 // PE.02 +#define LCD_SPI_MISO_GPIO_PIN GPIO_Pin_5 // PE.05 +#define LCD_SPI_MOSI_GPIO_PIN GPIO_Pin_6 // PE.06 +#define LTDC_IRQ_PRIO 4 +#define DMA_SCREEN_IRQ_PRIO 6 + +// Backlight +// TODO TIM3, TIM8, TIM14, review the channel in backlight_driver.cpp according to the chosen timer +#define BACKLIGHT_RCC_AHB1Periph RCC_AHB1Periph_GPIOA +#define BACKLIGHT_RCC_APB1Periph RCC_APB1Periph_TIM2 +#define BACKLIGHT_RCC_APB2Periph 0 +#define BACKLIGHT_GPIO GPIOA +#define BACKLIGHT_GPIO_PIN GPIO_Pin_15 +#define BACKLIGHT_GPIO_PinSource GPIO_PinSource15 +#define BACKLIGHT_TIMER TIM2 +#define BACKLIGHT_GPIO_AF GPIO_AF_TIM2 +#define BACKLIGHT_TIMER_FREQ (PERI1_FREQUENCY * TIMER_MULT_APB1) + +//used in BOOTLOADER +#define SERIAL_RCC_AHB1Periph 0 +#define SERIAL_RCC_APB1Periph 0 +#define ROTARY_ENCODER_RCC_APB1Periph 0 + +// SPI NOR Flash +#define FLASH_SPI SPI6 +#define FLASH_SPI_CS_GPIO GPIOG +#define FLASH_SPI_CS_GPIO_PIN LL_GPIO_PIN_6 // PG.06 +#define FLASH_SPI_GPIO GPIOG +#define FLASH_SPI_SCK_GPIO_PIN LL_GPIO_PIN_13 // PG.13 +#define FLASH_SPI_MISO_GPIO_PIN LL_GPIO_PIN_12 // PG.12 +#define FLASH_SPI_MOSI_GPIO_PIN LL_GPIO_PIN_14 // PG.14 +// #define FLASH_SPI_DMA DMA2 +// #define FLASH_SPI_DMA_CHANNEL LL_DMA_CHANNEL_1 +// #define FLASH_SPI_DMA_TX_STREAM LL_DMA_STREAM_5 +// #define FLASH_SPI_DMA_TX_IRQn DMA2_Stream5_IRQn +// #define FLASH_SPI_DMA_TX_IRQHandler DMA2_Stream5_IRQHandler +// #define FLASH_SPI_DMA_RX_STREAM LL_DMA_STREAM_6 +// #define FLASH_SPI_DMA_RX_IRQn DMA2_Stream6_IRQn +// #define FLASH_SPI_DMA_RX_IRQHandler DMA2_Stream6_IRQHandler +#define STORAGE_USE_SPI_FLASH + +// SDRAM +#define SDRAM_RCC_AHB1Periph (RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOH) +#define SDRAM_RCC_AHB3Periph RCC_AHB3Periph_FMC + +// Audio +#define AUDIO_RCC_APB1Periph (RCC_APB1Periph_TIM6 | RCC_APB1Periph_DAC) +#define AUDIO_RCC_AHB1Periph (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_DMA1) +#define AUDIO_OUTPUT_GPIO GPIOA +#define AUDIO_OUTPUT_GPIO_PIN GPIO_Pin_4 // PA.04 +#define AUDIO_GPIO_PinSource GPIO_PinSource4 +#define AUDIO_DMA_Stream DMA1_Stream5 +#define AUDIO_DMA_Stream_IRQn DMA1_Stream5_IRQn +#define AUDIO_TIM_IRQn TIM6_DAC_IRQn +#define AUDIO_TIM_IRQHandler TIM6_DAC_IRQHandler +#define AUDIO_DMA_Stream_IRQHandler DMA1_Stream5_IRQHandler +#define AUDIO_TIMER TIM6 +#define AUDIO_DMA DMA1 + +// I2C Bus - Touch +#define I2C_B1_RCC_AHB1Periph RCC_AHB1Periph_GPIOB +#define I2C_B1_RCC_APB1Periph RCC_APB1Periph_I2C1 +#define I2C_B1 I2C1 +#define I2C_B1_GPIO GPIOB +#define I2C_B1_SDA_GPIO_PIN LL_GPIO_PIN_7 // PB.07 +#define I2C_B1_SCL_GPIO_PIN LL_GPIO_PIN_8 // PB.08 +#define I2C_B1_GPIO_AF GPIO_AF4_I2C1 +//#define I2C_B1_SDA_GPIO_PinSource GPIO_PinSource7 +//#define I2C_B1_SCL_GPIO_PinSource GPIO_PinSource8 +//#define I2C_B1_CLK_RATE 100000 + +#define TOUCH_I2C_BUS I2C_Bus_1 +#define TOUCH_I2C_CLK_RATE 400000 + + +#define TOUCH_RST_RCC_AHB1Periph RCC_AHB1Periph_GPIOB +#define TOUCH_RST_GPIO GPIOB +#define TOUCH_RST_GPIO_PIN LL_GPIO_PIN_12 // PB.12 + +#define TOUCH_INT_RCC_AHB1Periph RCC_AHB1Periph_GPIOB +#define TOUCH_INT_GPIO GPIOB +#define TOUCH_INT_GPIO_PIN LL_GPIO_PIN_9 // PB.09 +#define TOUCH_INT_EXTI_LINE1 LL_EXTI_LINE_9 + +#define TOUCH_INT_EXTI_Line LL_EXTI_LINE_9 +#define TOUCH_INT_EXTI_Port LL_SYSCFG_EXTI_PORTB +#define TOUCH_INT_EXTI_SysCfgLine LL_SYSCFG_EXTI_LINE9 +#define TOUCH_INT_EXTI_IRQn EXTI9_5_IRQn +#define TOUCH_INT_EXTI_IRQHandler EXTI9_5_IRQHandler + +// Haptic: TIM1_CH1 +#define HAPTIC_PWM +#define HAPTIC_RCC_AHB1Periph RCC_AHB1Periph_GPIOA +#define HAPTIC_RCC_APB2Periph RCC_APB2ENR_TIM1EN +#define HAPTIC_GPIO GPIOA +#define HAPTIC_GPIO_PIN GPIO_Pin_8 +#define HAPTIC_GPIO_TIMER TIM1 +#define HAPTIC_GPIO_AF GPIO_AF_TIM1 +#define HAPTIC_GPIO_PinSource GPIO_PinSource8 +#define HAPTIC_TIMER_OUTPUT_ENABLE TIM_CCER_CC1E | TIM_CCER_CC1NE; +#define HAPTIC_TIMER_MODE TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE +#define HAPTIC_TIMER_COMPARE_VALUE HAPTIC_GPIO_TIMER->CCR1 + +// Flysky Hall Stick +#define FLYSKY_HALL_SERIAL_USART UART4 +#define FLYSKY_HALL_SERIAL_GPIO GPIOA +#define FLYSKY_HALL_DMA_Channel LL_DMA_CHANNEL_4 +#define FLYSKY_HALL_SERIAL_TX_GPIO_PIN LL_GPIO_PIN_0 // PA.00 +#define FLYSKY_HALL_SERIAL_RX_GPIO_PIN LL_GPIO_PIN_1 // PA.01 +#define FLYSKY_HALL_SERIAL_GPIO_AF LL_GPIO_AF_8 + +#define FLYSKY_HALL_RCC_AHB1Periph RCC_AHB1Periph_DMA1 +#define FLYSKY_HALL_RCC_APB1Periph RCC_APB1Periph_UART4 + +#define FLYSKY_HALL_SERIAL_USART_IRQHandler UART4_IRQHandler +#define FLYSKY_HALL_SERIAL_USART_IRQn UART4_IRQn +#define FLYSKY_HALL_SERIAL_DMA DMA1 +#define FLYSKY_HALL_DMA_Stream_RX LL_DMA_STREAM_2 +#define FLYSKY_HALL_DMA_Stream_TX LL_DMA_STREAM_4 + +// Internal Module +#define INTMODULE_RCC_AHB1Periph (RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOH | RCC_AHB1Periph_DMA1) +#define INTMODULE_PWR_GPIO GPIOI +#define INTMODULE_PWR_GPIO_PIN GPIO_Pin_0 // PI.00 +#define INTMODULE_GPIO GPIOF +#define INTMODULE_TX_GPIO_PIN LL_GPIO_PIN_7 // PF.07 +#define INTMODULE_RX_GPIO_PIN LL_GPIO_PIN_6 // PF.06 +#define INTMODULE_USART UART7 +#define INTMODULE_GPIO_AF GPIO_AF_UART7 +#define INTMODULE_GPIO_AF_LL LL_GPIO_AF_8 +#define INTMODULE_USART_IRQn UART7_IRQn +#define INTMODULE_USART_IRQHandler UART7_IRQHandler +#define INTMODULE_DMA DMA1 +#define INTMODULE_DMA_STREAM LL_DMA_STREAM_1 +#define INTMODULE_DMA_STREAM_IRQ DMA1_Stream1_IRQn +#define INTMODULE_DMA_FLAG_TC DMA_IT_TCIF1 +#define INTMODULE_DMA_CHANNEL LL_DMA_CHANNEL_5 + +#define INTMODULE_RX_DMA DMA1 +#define INTMODULE_RX_DMA_STREAM LL_DMA_STREAM_3 +#define INTMODULE_RX_DMA_CHANNEL LL_DMA_CHANNEL_5 +// #define INTMODULE_RX_DMA_Stream_IRQn DMA1_Stream3_IRQn +// #define INTMODULE_RX_DMA_Stream_IRQHandler DMA1_Stream_IRQHandler + +#define INTMODULE_TIMER TIM3 +#define INTMODULE_TIMER_IRQn TIM3_IRQn +#define INTMODULE_TIMER_IRQHandler TIM3_IRQHandler +#define INTMODULE_TIMER_FREQ (PERI1_FREQUENCY * TIMER_MULT_APB1) + +// External Module +#define EXTMODULE +#define EXTMODULE_PULSES +#define EXTMODULE_PWR_GPIO GPIOD +#define EXTMODULE_PWR_GPIO_PIN GPIO_Pin_11 +#define EXTMODULE_RCC_AHB1Periph \ + (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOC | \ + RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_DMA2) +#define EXTMODULE_TX_GPIO GPIOC +#define EXTMODULE_TX_GPIO_PIN LL_GPIO_PIN_6 // PC.06 +#define EXTMODULE_TX_GPIO_AF LL_GPIO_AF_3 // TIM8_CH1 +#define EXTMODULE_TX_GPIO_AF_USART GPIO_AF_USART6 +#define EXTMODULE_RX_GPIO GPIOC +#define EXTMODULE_RX_GPIO_PIN LL_GPIO_PIN_7 // PC.07 +#define EXTMODULE_RX_GPIO_AF_USART GPIO_AF_USART6 +#define EXTMODULE_TIMER TIM8 +#define EXTMODULE_TIMER_Channel LL_TIM_CHANNEL_CH1 +#define EXTMODULE_TIMER_IRQn TIM8_UP_TIM13_IRQn +#define EXTMODULE_TIMER_IRQHandler TIM8_UP_TIM13_IRQHandler +#define EXTMODULE_TIMER_FREQ (PERI2_FREQUENCY * TIMER_MULT_APB2) +#define EXTMODULE_TIMER_TX_GPIO_AF LL_GPIO_AF_3 +//USART +#define EXTMODULE_USART USART6 +#define EXTMODULE_USART_GPIO GPIOC +#define EXTMODULE_USART_GPIO_AF GPIO_AF_USART6 +#define EXTMODULE_USART_GPIO_AF_LL LL_GPIO_AF_8 +#define EXTMODULE_USART_TX_DMA DMA2 +#define EXTMODULE_USART_TX_DMA_CHANNEL LL_DMA_CHANNEL_5 +#define EXTMODULE_USART_TX_DMA_STREAM DMA2_Stream7 +#define EXTMODULE_USART_TX_DMA_STREAM_LL LL_DMA_STREAM_7 + +#define EXTMODULE_USART_RX_DMA_CHANNEL LL_DMA_CHANNEL_5 +#define EXTMODULE_USART_RX_DMA_STREAM DMA2_Stream2 +#define EXTMODULE_USART_RX_DMA_STREAM_LL LL_DMA_STREAM_2 + +#define EXTMODULE_USART_IRQHandler USART6_IRQHandler +#define EXTMODULE_USART_IRQn USART6_IRQn + +//TIMER +#define EXTMODULE_TIMER_DMA_CHANNEL LL_DMA_CHANNEL_7 +#define EXTMODULE_TIMER_DMA_STREAM DMA2_Stream1 +#define EXTMODULE_TIMER_DMA DMA2 +#define EXTMODULE_TIMER_DMA_STREAM_LL LL_DMA_STREAM_1 +#define EXTMODULE_TIMER_DMA_STREAM_IRQn DMA2_Stream1_IRQn +#define EXTMODULE_TIMER_DMA_IRQHandler DMA2_Stream1_IRQHandler + +#define EXTMODULE_TX_INVERT_GPIO GPIOE +#define EXTMODULE_TX_INVERT_GPIO_PIN GPIO_Pin_3 // PE.03 +#define EXTMODULE_RX_INVERT_GPIO GPIOI +#define EXTMODULE_RX_INVERT_GPIO_PIN GPIO_Pin_15 // PI.15 + +#define EXTMODULE_TX_NORMAL() EXTMODULE_TX_INVERT_GPIO->BSRRH = EXTMODULE_TX_INVERT_GPIO_PIN +#define EXTMODULE_TX_INVERTED() EXTMODULE_TX_INVERT_GPIO->BSRRL = EXTMODULE_TX_INVERT_GPIO_PIN +#define EXTMODULE_RX_NORMAL() EXTMODULE_RX_INVERT_GPIO->BSRRH = EXTMODULE_RX_INVERT_GPIO_PIN +#define EXTMODULE_RX_INVERTED() EXTMODULE_RX_INVERT_GPIO->BSRRL = EXTMODULE_RX_INVERT_GPIO_PIN + +// Trainer Port +#define TRAINER_RCC_AHB1Periph (RCC_AHB1Periph_GPIOD) +#define TRAINER_GPIO GPIOD + +#define TRAINER_IN_GPIO_PIN LL_GPIO_PIN_12 // PD.12 +#define TRAINER_IN_TIMER_Channel LL_TIM_CHANNEL_CH1 + +#define TRAINER_OUT_GPIO_PIN LL_GPIO_PIN_13 // PD.13 +#define TRAINER_OUT_TIMER_Channel LL_TIM_CHANNEL_CH2 + +#define TRAINER_TIMER TIM4 +#define TRAINER_TIMER_IRQn TIM4_IRQn +#define TRAINER_TIMER_IRQHandler TIM4_IRQHandler +#define TRAINER_GPIO_AF LL_GPIO_AF_2 +#define TRAINER_TIMER_FREQ (PERI1_FREQUENCY * TIMER_MULT_APB1) + + +//BLUETOOTH +#define BLUETOOTH_ON_RCC_AHB1Periph RCC_AHB1Periph_GPIOI +#define BT_EN_GPIO GPIOI +#define BT_EN_GPIO_PIN GPIO_Pin_8 // PI.8 + +#define BT_RCC_AHB1Periph (RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_GPIOH) +#define BT_RCC_APB1Periph (RCC_APB1Periph_USART3) +#define BT_RCC_APB2Periph 0 + +#define BT_USART USART3 +#define BT_GPIO_AF GPIO_AF_USART3 +#define BT_USART_IRQn USART3_IRQn +#define BT_GPIO_TXRX GPIOB +#define BT_TX_GPIO_PIN GPIO_Pin_10 // PB.10 +#define BT_RX_GPIO_PIN GPIO_Pin_11 // PB.11 +#define BT_TX_GPIO_PinSource GPIO_PinSource10 +#define BT_RX_GPIO_PinSource GPIO_PinSource11 +#define BT_USART_IRQHandler USART3_IRQHandler + +#define BT_CONNECTED_GPIO GPIOJ +#define BT_CONNECTED_GPIO_PIN GPIO_Pin_1 // PJ.01 + +#define BT_CMD_MODE_GPIO GPIOH +#define BT_CMD_MODE_GPIO_PIN GPIO_Pin_6 // PH.6 + +// Millisecond timer +#define MS_TIMER TIM14 +#define MS_TIMER_IRQn TIM8_TRG_COM_TIM14_IRQn +#define MS_TIMER_IRQHandler TIM8_TRG_COM_TIM14_IRQHandler + +// Mixer scheduler timer +#define MIXER_SCHEDULER_TIMER TIM12 +#define MIXER_SCHEDULER_TIMER_FREQ (PERI1_FREQUENCY * TIMER_MULT_APB1) +#define MIXER_SCHEDULER_TIMER_IRQn TIM8_BRK_TIM12_IRQn +#define MIXER_SCHEDULER_TIMER_IRQHandler TIM8_BRK_TIM12_IRQHandler + +#endif // _HAL_H_ diff --git a/radio/src/targets/pl18/haptic_driver.cpp b/radio/src/targets/pl18/haptic_driver.cpp new file mode 100644 index 00000000000..d8fc8f809da --- /dev/null +++ b/radio/src/targets/pl18/haptic_driver.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "board.h" + +void hapticOff(void) +{ + HAPTIC_TIMER_COMPARE_VALUE = 0; +} + +void hapticOn(uint32_t pwmPercent) +{ + if (pwmPercent > 100) { + pwmPercent = 100; + } + HAPTIC_TIMER_COMPARE_VALUE = pwmPercent; +} + +void hapticInit(void) +{ + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Pin = HAPTIC_GPIO_PIN; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_Init(HAPTIC_GPIO, &GPIO_InitStructure); + + GPIO_PinAFConfig(HAPTIC_GPIO, HAPTIC_GPIO_PinSource, HAPTIC_GPIO_AF); + + HAPTIC_GPIO_TIMER->ARR = 100; + HAPTIC_GPIO_TIMER->PSC = (PERI2_FREQUENCY * TIMER_MULT_APB2) / 10000 - 1; + HAPTIC_GPIO_TIMER->CCMR1 = HAPTIC_TIMER_MODE; // PWM + HAPTIC_GPIO_TIMER->CCER = HAPTIC_TIMER_OUTPUT_ENABLE; + HAPTIC_GPIO_TIMER->CCR1 = 0; + HAPTIC_GPIO_TIMER->EGR = TIM_EGR_UG; + HAPTIC_GPIO_TIMER->CR1 = TIM_CR1_CEN; // counter enable + HAPTIC_GPIO_TIMER->BDTR |= TIM_BDTR_MOE; +} + +void hapticDone(void) +{ + hapticOff(); + RCC_AHB1PeriphClockCmd(HAPTIC_RCC_AHB1Periph, DISABLE); +} diff --git a/radio/src/targets/pl18/key_driver.cpp b/radio/src/targets/pl18/key_driver.cpp new file mode 100644 index 00000000000..7c768fe6ddf --- /dev/null +++ b/radio/src/targets/pl18/key_driver.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "hal/key_driver.h" + +#include "stm32_hal_ll.h" +#include "stm32_gpio_driver.h" + +#include "hal.h" +#include "delays_driver.h" +#include "keys.h" + +/* The output bit-order has to be: + 0 LHL TR7L (Left equals down) + 1 LHR TR7R + 2 LVD TR5D + 3 LVU TR5U + 4 RVD TR6D + 5 RVU TR6U + 6 RHL TR8L + 7 RHR TR8R + 8 LSD TR1D + 9 LSU TR1U + 10 RSD TR2D + 11 RSU TR2U + 12 EX1D TR3D + 13 EX1U TR3U + 14 EX2D TR4D + 15 EX2U TR4U +*/ + +enum PhysicalTrims +{ + TR7L = 0, + TR7R, + TR5D = 2, + TR5U, + TR6D = 4, + TR6U, + TR8L = 6, + TR8R, + TR1D = 8, + TR1U, + TR2D = 10, + TR2U, + TR3D = 12, + TR3U, + TR4D = 14, + TR4U, +}; + +void keysInit() +{ + stm32_gpio_enable_clock(GPIOB); + stm32_gpio_enable_clock(GPIOC); + stm32_gpio_enable_clock(GPIOD); + stm32_gpio_enable_clock(GPIOF); + stm32_gpio_enable_clock(GPIOH); + stm32_gpio_enable_clock(GPIOJ); + + LL_GPIO_InitTypeDef pinInit; + LL_GPIO_StructInit(&pinInit); + pinInit.Mode = LL_GPIO_MODE_INPUT; + pinInit.Pull = LL_GPIO_PULL_DOWN; + + pinInit.Pin = KEYS_GPIOB_PINS; + LL_GPIO_Init(GPIOB, &pinInit); + + pinInit.Pin = KEYS_GPIOC_PINS; + LL_GPIO_Init(GPIOC, &pinInit); + + pinInit.Pin = KEYS_GPIOD_PINS; + LL_GPIO_Init(GPIOD, &pinInit); + + pinInit.Pin = KEYS_GPIOH_PINS; + LL_GPIO_Init(GPIOH, &pinInit); + + pinInit.Pin = KEYS_GPIOJ_PINS; + LL_GPIO_Init(GPIOJ, &pinInit); + + // Matrix outputs + pinInit.Mode = LL_GPIO_MODE_OUTPUT; + pinInit.Pull = LL_GPIO_PULL_NO; + + pinInit.Pin = KEYS_OUT_GPIOG_PINS; + LL_GPIO_Init(GPIOG, &pinInit); + + pinInit.Pin = KEYS_OUT_GPIOH_PINS; + LL_GPIO_Init(GPIOH, &pinInit); +} + +static uint32_t _readKeyMatrix() +{ + // This function avoids concurrent matrix agitation + + uint32_t result = 0; + /* Bit 0 - TR3 down + * Bit 1 - TR3 up + * Bit 2 - TR4 down + * Bit 3 - TR4 up + * Bit 4 - TR5 down + * Bit 5 - TR5 up + * Bit 6 - TR6 down + * Bit 7 - TR6 up + * Bit 8 - TR7 left + * Bit 9 - TR7 right + * Bit 10 - TR8 left + * Bit 11 - TR8 right + */ + + volatile static struct + { + uint32_t oldResult = 0; + uint8_t ui8ReadInProgress = 0; + } syncelem; + + if (syncelem.ui8ReadInProgress != 0) return syncelem.oldResult; + + // ui8ReadInProgress was 0, increment it + syncelem.ui8ReadInProgress++; + // Double check before continuing, as non-atomic, non-blocking so far + // If ui8ReadInProgress is above 1, then there was concurrent task calling it, exit + if (syncelem.ui8ReadInProgress > 1) return syncelem.oldResult; + + // If we land here, we have exclusive access to Matrix + LL_GPIO_ResetOutputPin(TRIMS_GPIO_OUT1, TRIMS_GPIO_OUT1_PIN); + LL_GPIO_SetOutputPin(TRIMS_GPIO_OUT2, TRIMS_GPIO_OUT2_PIN); + LL_GPIO_SetOutputPin(TRIMS_GPIO_OUT3, TRIMS_GPIO_OUT3_PIN); + LL_GPIO_SetOutputPin(TRIMS_GPIO_OUT4, TRIMS_GPIO_OUT4_PIN); + delay_us(10); + if (~TRIMS_GPIO_REG_IN1 & TRIMS_GPIO_PIN_IN1) + result |= 1 << TR7L; + if (~TRIMS_GPIO_REG_IN2 & TRIMS_GPIO_PIN_IN2) + result |= 1 << TR7R; + if (~TRIMS_GPIO_REG_IN3 & TRIMS_GPIO_PIN_IN3) + result |= 1 << TR5D; + if (~TRIMS_GPIO_REG_IN4 & TRIMS_GPIO_PIN_IN4) + result |= 1 << TR5U; + + LL_GPIO_SetOutputPin(TRIMS_GPIO_OUT1, TRIMS_GPIO_OUT1_PIN); + LL_GPIO_ResetOutputPin(TRIMS_GPIO_OUT2, TRIMS_GPIO_OUT2_PIN); + delay_us(10); + if (~TRIMS_GPIO_REG_IN1 & TRIMS_GPIO_PIN_IN1) + result |= 1 << TR3D; + if (~TRIMS_GPIO_REG_IN2 & TRIMS_GPIO_PIN_IN2) + result |= 1 << TR3U; + if (~TRIMS_GPIO_REG_IN3 & TRIMS_GPIO_PIN_IN3) + result |= 1 << TR4U; + if (~TRIMS_GPIO_REG_IN4 & TRIMS_GPIO_PIN_IN4) + result |= 1 << TR4D; + + LL_GPIO_SetOutputPin(TRIMS_GPIO_OUT2, TRIMS_GPIO_OUT2_PIN); + LL_GPIO_ResetOutputPin(TRIMS_GPIO_OUT3, TRIMS_GPIO_OUT3_PIN); + delay_us(10); + if (~TRIMS_GPIO_REG_IN1 & TRIMS_GPIO_PIN_IN1) + result |= 1 << TR6U; + if (~TRIMS_GPIO_REG_IN2 & TRIMS_GPIO_PIN_IN2) + result |= 1 << TR6D; + if (~TRIMS_GPIO_REG_IN3 & TRIMS_GPIO_PIN_IN3) + result |= 1 << TR8L; + if (~TRIMS_GPIO_REG_IN4 & TRIMS_GPIO_PIN_IN4) + result |= 1 << TR8R; + + LL_GPIO_SetOutputPin(TRIMS_GPIO_OUT3, TRIMS_GPIO_OUT3_PIN); + + syncelem.oldResult = result; + syncelem.ui8ReadInProgress = 0; + + return result; +} + +uint32_t readKeys() +{ + uint32_t result = 0; + + if (getTrimsAsButtons()) { + uint32_t mkeys = _readKeyMatrix(); + if (mkeys & (1 << TR4D)) result |= 1 << KEY_ENTER; + if (mkeys & (1 << TR4U)) result |= 1 << KEY_EXIT; + } + + return result; +} + +uint32_t readTrims() +{ + uint32_t result = 0; + + result |= _readKeyMatrix(); + + if (~TRIMS_GPIO_REG_TR1U & TRIMS_GPIO_PIN_TR1U) + result |= 1 << (TR1U); + if (~TRIMS_GPIO_REG_TR1D & TRIMS_GPIO_PIN_TR1D) + result |= 1 << (TR1D); + + if (~TRIMS_GPIO_REG_TR2U & TRIMS_GPIO_PIN_TR2U) + result |= 1 << (TR2U); + if (~TRIMS_GPIO_REG_TR2D & TRIMS_GPIO_PIN_TR2D) + result |= 1 << (TR2D); + + return result; +} diff --git a/radio/src/targets/pl18/lcd_driver.cpp b/radio/src/targets/pl18/lcd_driver.cpp new file mode 100644 index 00000000000..4f623a64259 --- /dev/null +++ b/radio/src/targets/pl18/lcd_driver.cpp @@ -0,0 +1,3099 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "opentx_types.h" +#include "lcd.h" +#include "lcd_driver.h" + +// StdPeriph +#include "stm32f4xx_ltdc.h" +#include "stm32f4xx_dma2d.h" + +#define LCD_PHYS_W 480 +#define LCD_PHYS_H 320 + +static volatile uint8_t _frame_addr_reloaded = 0; + +static void startLcdRefresh(lv_disp_drv_t *disp_drv, uint16_t *buffer, + const rect_t ©_area) +{ + (void)disp_drv; + (void)copy_area; + + LTDC_Layer1->CFBAR = (uint32_t)buffer; + + // reload shadow registers on vertical blank + _frame_addr_reloaded = 0; + LTDC->SRCR = LTDC_SRCR_VBR; + + // wait for reload + // TODO: replace through some smarter mechanism without busy wait + while(_frame_addr_reloaded == 0); +} + +lcdSpiInitFucPtr lcdInitFunction; +lcdSpiInitFucPtr lcdOffFunction; +lcdSpiInitFucPtr lcdOnFunction; +uint32_t lcdPixelClock; + +volatile uint8_t LCD_ReadBuffer[24] = { 0, 0 }; + +static void LCD_Delay(void) { + volatile unsigned int i; + + for (i = 0; i < 20; i++) { + ; + } +} + +enum ENUM_IO_SPEED +{ + IO_SPEED_LOW, + IO_SPEED_MID, + IO_SPEED_QUICK, + IO_SPEED_HIGH +}; + +enum ENUM_IO_MODE +{ + IO_MODE_INPUT, + IO_MODE_OUTPUT, + IO_MODE_ALTERNATE, + IO_MODE_ANALOG +}; + + +void GPIO_SetDirection( GPIO_TypeDef *GPIOx, unsigned char Pin, unsigned char IsInput ) +{ + unsigned int Mask; + unsigned int Position; + unsigned int Register; + + + Position = Pin << 1; + Mask = ~( 0x03UL << Position ); + + //EnterCritical(); + Register = GPIOx->OSPEEDR & Mask; + Register |= IO_SPEED_HIGH << Position; + GPIOx->OSPEEDR = Register; + //ExitCritical(); + + //EnterCritical(); + Register = GPIOx->MODER & Mask; + if( !IsInput ) + { + Register |= IO_MODE_OUTPUT << Position; + } + + GPIOx->MODER = Register; + //ExitCritical(); +} +static void LCD_AF_GPIOConfig(void) { + /* + ----------------------------------------------------------------------------- + LCD_CLK <-> PG.07 | LCD_HSYNC <-> PI.12 | LCD_R3 <-> PJ.02 | LCD_G5 <-> PK.00 + | LCD VSYNC <-> PI.13 | LCD_R4 <-> PJ.03 | LCD_G6 <-> PK.01 + | | LCD_R5 <-> PJ.04 | LCD_G7 <-> PK.02 + | | LCD_R6 <-> PJ.05 | LCD_B4 <-> PK.03 + | | LCD_R7 <-> PJ.06 | LCD_B5 <-> PK.04 + | | LCD_G2 <-> PJ.09 | LCD_B6 <-> PK.05 + | | LCD_G3 <-> PJ.10 | LCD_B7 <-> PK.06 + | | LCD_G4 <-> PJ.11 | LCD_DE <-> PK.07 + | | LCD_B3 <-> PJ.15 | + */ + + // GPIOG configuration + GPIO_PinAFConfig(GPIOG, GPIO_PinSource7, GPIO_AF_LTDC); + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; + GPIO_Init(GPIOG, &GPIO_InitStructure); + + // GPIOI configuration + GPIO_PinAFConfig(GPIOI, GPIO_PinSource12, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOI, GPIO_PinSource13, GPIO_AF_LTDC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13; + GPIO_Init(GPIOI, &GPIO_InitStructure); + + // GPIOJ configuration + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource2, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource3, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource4, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource5, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource6, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource9, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource10, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource11, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOJ, GPIO_PinSource15, GPIO_AF_LTDC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_15; + GPIO_Init(GPIOJ, &GPIO_InitStructure); + + // GPIOK configuration + GPIO_PinAFConfig(GPIOK, GPIO_PinSource0, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource1, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource2, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource3, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource4, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource5, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource6, GPIO_AF_LTDC); + GPIO_PinAFConfig(GPIOK, GPIO_PinSource7, GPIO_AF_LTDC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; + GPIO_Init(GPIOK, &GPIO_InitStructure); +} + +static void lcdSpiConfig(void) { + GPIO_InitTypeDef GPIO_InitStructure; + + GPIO_InitStructure.GPIO_Pin = LCD_SPI_SCK_GPIO_PIN | LCD_SPI_MOSI_GPIO_PIN; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = LCD_SPI_CS_GPIO_PIN; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; + GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.GPIO_Pin = LCD_NRST_GPIO_PIN; + GPIO_Init(LCD_NRST_GPIO, &GPIO_InitStructure); + + /* Set the chip select pin aways low */ + SET_LCD_CS(); +} + +void lcdDelay() { + delay_01us(1); +} + +static void lcdReset() { + LCD_NRST_HIGH(); + delay_ms(1); + + LCD_NRST_LOW(); // RESET(); + delay_ms(100); + + LCD_NRST_HIGH(); + delay_ms(100); +} + +unsigned char LCD_ReadByteOnFallingEdge(void) { + unsigned int i; + unsigned char ReceiveData = 0; + + SET_LCD_DATA(); + SET_LCD_DATA_INPUT(); + + for (i = 0; i < 8; i++) { + LCD_DELAY(); + SET_LCD_CLK(); + LCD_DELAY(); + LCD_DELAY(); + ReceiveData <<= 1; + + CLR_LCD_CLK(); + LCD_DELAY(); + LCD_DELAY(); + if (READ_LCD_DATA_PIN()) { + ReceiveData |= 0x01; + } + } + + SET_LCD_DATA_OUTPUT(); + + return (ReceiveData); +} + +static void lcdWriteByte(uint8_t data_enable, uint8_t byte) { + + LCD_SCK_LOW(); + lcdDelay(); + + if (data_enable) { + LCD_MOSI_HIGH(); + } else { + LCD_MOSI_LOW(); + } + + LCD_SCK_HIGH(); + lcdDelay(); + + for (int i = 0; i < 8; i++) { + LCD_SCK_LOW(); + lcdDelay(); + + if (byte & 0x80) { + LCD_MOSI_HIGH(); + } else { + LCD_MOSI_LOW(); + } + + LCD_SCK_HIGH(); + byte <<= 1; + + lcdDelay(); + } + + LCD_SCK_LOW(); +} + +unsigned char LCD_ReadByte(void) { + unsigned int i; + unsigned char ReceiveData = 0; + + SET_LCD_DATA(); + SET_LCD_DATA_INPUT(); + for (i = 0; i < 8; i++) { + CLR_LCD_CLK(); + lcdDelay(); + ReceiveData <<= 1; + SET_LCD_CLK(); + lcdDelay(); + if (READ_LCD_DATA_PIN()) { + ReceiveData |= 0x01; + } + } + CLR_LCD_CLK(); + SET_LCD_DATA_OUTPUT(); + + return (ReceiveData); +} + +unsigned char LCD_ReadRegister(unsigned char Register) { + unsigned char ReadData = 0; + + CLR_LCD_CS(); + lcdWriteByte(0, Register); + lcdDelay(); + lcdDelay(); + ReadData = LCD_ReadByte(); + SET_LCD_CS(); + return (ReadData); +} + +void lcdWriteCommand(uint8_t command) { + CLR_LCD_CS(); + lcdWriteByte(0, command); + SET_LCD_CS(); +} + +void lcdWriteData(uint8_t data) { + CLR_LCD_CS(); + lcdWriteByte(1, data); + SET_LCD_CS(); +} + +void LCD_HX8357D_Init(void) { +#if 0 + lcdWriteCommand(0x11); + delay_ms(200); + + lcdWriteCommand(0xB9); + lcdWriteData(0xFF); + lcdWriteData(0x83); + lcdWriteData(0x57); + + lcdWriteCommand(0xB1); + lcdWriteData(0x00); + lcdWriteData(0x14); + lcdWriteData(0x1C); + lcdWriteData(0x1C); + lcdWriteData(0xC7); + lcdWriteData(0x21); + + lcdWriteCommand(0xB3); + lcdWriteData(0x83); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x06); + + lcdWriteCommand(0xB4); + lcdWriteData(0x11); + lcdWriteData(0x40); + lcdWriteData(0x00); + lcdWriteData(0x2A); + lcdWriteData(0x2A); + lcdWriteData(0x20); + lcdWriteData(0x4E); + + lcdWriteCommand(0xB5); + lcdWriteData(0x03); + lcdWriteData(0x03); + + lcdWriteCommand(0xB6); + lcdWriteData(0x38); + + lcdWriteCommand(0xC0); + lcdWriteData(0x24); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0xc8); + lcdWriteData(0x08); + + lcdWriteCommand(0xC2); + lcdWriteData(0x00); + lcdWriteData(0x08); + lcdWriteData(0x04); + + lcdWriteCommand(0xCC); + lcdWriteData(0x00); + +//GAMMA 2.5" + lcdWriteCommand(0xE0); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x0D); + lcdWriteData(0x18); + lcdWriteData(0x23); + lcdWriteData(0x3B); + lcdWriteData(0x45); + lcdWriteData(0x4D); + lcdWriteData(0x4D); + lcdWriteData(0x46); + lcdWriteData(0x40); + lcdWriteData(0x37); + lcdWriteData(0x34); + lcdWriteData(0x2F); + lcdWriteData(0x2B); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x0D); + lcdWriteData(0x18); + lcdWriteData(0x23); + lcdWriteData(0x3B); + lcdWriteData(0x45); + lcdWriteData(0x4D); + lcdWriteData(0x4D); + lcdWriteData(0x46); + lcdWriteData(0x40); + lcdWriteData(0x37); + lcdWriteData(0x34); + lcdWriteData(0x2F); + lcdWriteData(0x2B); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x01); + + lcdWriteCommand(0x3A); + lcdWriteData(0x66); + + lcdWriteCommand(0x36); + lcdWriteData(0x28); + lcdWriteCommand( 0x2A ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0xDF ); + lcdWriteCommand( 0x2B ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0x3F ); + + lcdWriteCommand(0x29); + delay_ms(10); +#elif 0 + delay_ms(50); + lcdWriteCommand(0xB9); //EXTC + lcdWriteData(0xFF); //EXTC + lcdWriteData(0x83); //EXTC + lcdWriteData(0x57); //EXTC + delay_ms(5); + + lcdWriteCommand(0x3A); + lcdWriteData(0x65); //262k + + lcdWriteCommand(0xB3); //COLOR FORMAT + lcdWriteData(0x83); //SDO_EN,BYPASS,EPF[1:0],0,0,RM,DM //43 + + lcdWriteCommand(0xB6); // + lcdWriteData(0x5a); //VCOMDC + + lcdWriteCommand(0x35); // TE ON + lcdWriteData(0x01); + + lcdWriteCommand(0xB0); + lcdWriteData(0x68); //70Hz + + lcdWriteCommand(0xCC); // Set Panel + lcdWriteData(0x00); // + + lcdWriteCommand(0xB1); // + lcdWriteData(0x00); // + lcdWriteData(0x11); //BT + lcdWriteData(0x1C); //VSPR + lcdWriteData(0x1C); //VSNR + lcdWriteData(0x83); //AP + lcdWriteData(0x48); //FS 0xAA + + lcdWriteCommand(0xB4); // + lcdWriteData(0x02); //NW + lcdWriteData(0x40); //RTN + lcdWriteData(0x00); //DIV + lcdWriteData(0x2A); //DUM + lcdWriteData(0x2A); //DUM + lcdWriteData(0x0D); //GDON + lcdWriteData(0x78); //GDOFF 0x4F + lcdWriteCommand(0xC0); //STBA + lcdWriteData(0x50); //OPON + lcdWriteData(0x50); //OPON + lcdWriteData(0x01); // + lcdWriteData(0x3C); // + lcdWriteData(0x1E); // + lcdWriteData(0x08); //GEN + + /* + lcdWriteCommand(0xE0); // + lcdWriteData(0x02); //1 + lcdWriteData(0x06); //2 + lcdWriteData(0x09); //3 + lcdWriteData(0x1C); //4 + lcdWriteData(0x27); //5 + lcdWriteData(0x3C); //6 + lcdWriteData(0x48); //7 + lcdWriteData(0x50); //8 + lcdWriteData(0x49); //9 + lcdWriteData(0x42); //10 + lcdWriteData(0x3E); //11 + lcdWriteData(0x35); //12 + lcdWriteData(0x31); //13 + lcdWriteData(0x2A); //14 + lcdWriteData(0x28); //15 + lcdWriteData(0x03); //16 + lcdWriteData(0x02); //17 v1 + lcdWriteData(0x06); //18 + lcdWriteData(0x09); //19 + lcdWriteData(0x1C); //20 + lcdWriteData(0x27); //21 + lcdWriteData(0x3C); //22 + lcdWriteData(0x48); //23 + lcdWriteData(0x50); //24 + lcdWriteData(0x49); //25 + lcdWriteData(0x42); //26 + lcdWriteData(0x3E); //27 + lcdWriteData(0x35); //28 + lcdWriteData(0x31); //29 + lcdWriteData(0x2A); //30 + lcdWriteData(0x28); //31 + lcdWriteData(0x03); //32 + lcdWriteData(0x44); //33 + lcdWriteData(0x01); //34 + */ + lcdWriteCommand(0xE0); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x0D); + lcdWriteData(0x18); + lcdWriteData(0x23); + lcdWriteData(0x3B); + lcdWriteData(0x45); + lcdWriteData(0x4D); + lcdWriteData(0x4D); + lcdWriteData(0x46); + lcdWriteData(0x40); + lcdWriteData(0x37); + lcdWriteData(0x34); + lcdWriteData(0x2F); + lcdWriteData(0x2B); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x0D); + lcdWriteData(0x18); + lcdWriteData(0x23); + lcdWriteData(0x3B); + lcdWriteData(0x45); + lcdWriteData(0x4D); + lcdWriteData(0x4D); + lcdWriteData(0x46); + lcdWriteData(0x40); + lcdWriteData(0x37); + lcdWriteData(0x34); + lcdWriteData(0x2F); + lcdWriteData(0x2B); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x01); + lcdWriteCommand(0x36); + lcdWriteData(0x38); + + lcdWriteCommand(0x11); // SLPOUT + delay_ms(200); + + lcdWriteCommand(0x29); // Display On + delay_ms(25); + lcdWriteCommand(0x2C); +#else + lcdWriteCommand(0x11); + delay_ms(200); + + lcdWriteCommand(0xB9); + lcdWriteData(0xFF); + lcdWriteData(0x83); + lcdWriteData(0x57); + delay_ms(5); + +// lcdWriteCommand(0x36); +// lcdWriteData(0x10); + + lcdWriteCommand(0xB1); + lcdWriteData(0x00); + lcdWriteData(0x14); + lcdWriteData(0x1C); + lcdWriteData(0x1C); + lcdWriteData(0xC7); + lcdWriteData(0x21); + lcdWriteCommand(0xB3); + lcdWriteData(0x83); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x06); + lcdWriteCommand(0xB4); + lcdWriteData(0x11); + lcdWriteData(0x40); + lcdWriteData(0x00); + lcdWriteData(0x2A); + lcdWriteData(0x2A); + lcdWriteData(0x20); + lcdWriteData(0x4E); + lcdWriteCommand(0xB5); + lcdWriteData(0x03); + lcdWriteData(0x03); + + lcdWriteCommand(0xB6); + lcdWriteData(0x38); + + lcdWriteCommand(0xC0); + lcdWriteData(0x24); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0xc8); + lcdWriteData(0x08); + lcdWriteCommand(0xC2); + lcdWriteData(0x00); + lcdWriteData(0x08); + lcdWriteData(0x04); + //GAMMA 2.5" + lcdWriteCommand(0xE0); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x0D); + lcdWriteData(0x18); + lcdWriteData(0x23); + lcdWriteData(0x3B); + lcdWriteData(0x45); + lcdWriteData(0x4D); + lcdWriteData(0x4D); + lcdWriteData(0x46); + lcdWriteData(0x40); + lcdWriteData(0x37); + lcdWriteData(0x34); + lcdWriteData(0x2F); + lcdWriteData(0x2B); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x0D); + lcdWriteData(0x18); + lcdWriteData(0x23); + lcdWriteData(0x3B); + lcdWriteData(0x45); + lcdWriteData(0x4D); + lcdWriteData(0x4D); + lcdWriteData(0x46); + lcdWriteData(0x40); + lcdWriteData(0x37); + lcdWriteData(0x34); + lcdWriteData(0x2F); + lcdWriteData(0x2B); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x01); + // lcdWriteCommand(0x2A); + // lcdWriteData(0); + // lcdWriteData(0); + // lcdWriteData(480 >> 8); + // lcdWriteData(480); + // lcdWriteCommand(0x2B); + // lcdWriteData(0); + // lcdWriteData(0); + // lcdWriteData(320 >> 8); + // lcdWriteData(320); + lcdWriteCommand(0x3A); + lcdWriteData(0x66); + + lcdWriteCommand(0xCC); + lcdWriteData(0x01); + + lcdWriteCommand( 0x2A ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0xDF ); + lcdWriteCommand( 0x2B ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0x3F ); + + lcdWriteCommand(0x36); + lcdWriteData(0x20); + + lcdWriteCommand(0xB9); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + delay_ms(5); + lcdWriteCommand(0x29); +#endif +} + +void LCD_HX8357D_On(void) { + lcdWriteCommand(0x28); + lcdWriteCommand(0x29); +} + +void LCD_HX8357D_Off(void) { + lcdWriteCommand(0x28); +} + +unsigned int LCD_HX8357D_ReadID(void) { + lcdReset(); + int ID = 0; + + lcdWriteCommand( 0xB9 ); + lcdWriteData( 0xff ); + lcdWriteData( 0x83 ); + lcdWriteData( 0x57 ); + + lcdWriteCommand( 0xFE ); + lcdWriteData( 0xd0 ); + ID = LCD_ReadRegister( 0xff ); + + lcdWriteCommand( 0xB9 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + + return (ID); +} + +void LCD_ILI9481_Init(void) { + lcdWriteCommand(0x11); + delay_ms(120); + + lcdWriteCommand(0xE4); + lcdWriteData(0x0A); + + lcdWriteCommand(0xF0); + lcdWriteData(0x01); + + lcdWriteCommand(0xF3); + lcdWriteData(0x02); + lcdWriteData(0x1A); + + lcdWriteCommand(0xD0); + lcdWriteData(0x07); + lcdWriteData(0x42); + lcdWriteData(0x1B); + + lcdWriteCommand(0xD1); + lcdWriteData(0x00); + lcdWriteData(0x00); //04 + lcdWriteData(0x1A); + + lcdWriteCommand(0xD2); + lcdWriteData(0x01); + lcdWriteData(0x00); //11 + + lcdWriteCommand(0xC0); + lcdWriteData(0x10); + lcdWriteData(0x3B); // + lcdWriteData(0x00); // + lcdWriteData(0x02); + lcdWriteData(0x11); + + lcdWriteCommand(0xC5); + lcdWriteData(0x03); + + lcdWriteCommand(0xC8); + lcdWriteData(0x00); + lcdWriteData(0x01); + lcdWriteData(0x47); + lcdWriteData(0x60); + lcdWriteData(0x04); + lcdWriteData(0x16); + lcdWriteData(0x03); + lcdWriteData(0x67); + lcdWriteData(0x67); + lcdWriteData(0x06); + lcdWriteData(0x0F); + lcdWriteData(0x00); + + lcdWriteCommand(0x36); + lcdWriteData(0x08); + + lcdWriteCommand(0x3A); + lcdWriteData(0x66); //0x55=65k color, 0x66=262k color. + + lcdWriteCommand(0x2A); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x01); + lcdWriteData(0x3F); + + lcdWriteCommand(0x2B); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x01); + lcdWriteData(0xE0); + + lcdWriteCommand(0xB4); + lcdWriteData(0x11); + + lcdWriteCommand(0xc6); + lcdWriteData(0x82); + + delay_ms(120); + + lcdWriteCommand(0x21); + lcdWriteCommand(0x29); + lcdWriteCommand(0x2C); + +} + +void LCD_ILI9481_On(void) { + lcdWriteCommand(0x29); +} + +void LCD_ILI9481_Off(void) { + lcdWriteCommand(0x28); +} + +unsigned int LCD_ILI9481_ReadID(void) { +#if 1 + /* Have a issue here */ + return 0; +#else + int ID = 0; + int Data; + + + lcdWriteByte(0, 0xBF); + + Data = LCD_ReadByteOnFallingEdge(); + Data = LCD_ReadByteOnFallingEdge(); + ID = LCD_ReadByteOnFallingEdge(); + ID <<= 8; + ID |= LCD_ReadByteOnFallingEdge(); + Data = LCD_ReadByteOnFallingEdge(); + Data = LCD_ReadByteOnFallingEdge(); + + LCD_DELAY(); + LCD_DELAY(); + LCD_DELAY(); + + lcdWriteCommand(0xC6); + lcdWriteData(0x82); + //lcdWriteData( 0x9b ); + return (ID); +#endif +} + +void LCD_ILI9486_On(void) { + lcdWriteCommand(0x29); +} + +void LCD_ILI9486_Init(void) { + lcdWriteCommand(0XFB); + lcdWriteData(0x00); + + lcdWriteCommand(0xf2); + lcdWriteData(0x18); + lcdWriteData(0xa3); + lcdWriteData(0x12); + lcdWriteData(0x02); + lcdWriteData(0xb2); + lcdWriteData(0x12); + lcdWriteData(0xff); + lcdWriteData(0x13); + lcdWriteData(0x00); + lcdWriteCommand(0xf1); + lcdWriteData(0x36); + lcdWriteData(0x04); + lcdWriteData(0x00); + lcdWriteData(0x3c); + lcdWriteData(0x0f); + lcdWriteData(0x8f); + lcdWriteCommand(0xf8); + lcdWriteData(0x21); + lcdWriteData(0x04); + lcdWriteCommand(0xf9); + lcdWriteData(0x00); + lcdWriteData(0x08); + lcdWriteCommand(0x36); + lcdWriteData(0x18); + lcdWriteCommand(0x3a); + lcdWriteData(0x65); + lcdWriteCommand(0xc0); + lcdWriteData(0x0f); + lcdWriteData(0x0f); + lcdWriteCommand(0xc1); + lcdWriteData(0x41); + + lcdWriteCommand(0xc5); + lcdWriteData(0x00); + lcdWriteData(0x27); + lcdWriteData(0x80); + lcdWriteCommand(0xb6); + lcdWriteData(0xb2); + lcdWriteData(0x42); + lcdWriteData(0x3b); + lcdWriteCommand(0xb1); + lcdWriteData(0xb0); + lcdWriteData(0x11); + lcdWriteCommand(0xb4); + lcdWriteData(0x02); + lcdWriteCommand(0xb7); + lcdWriteData(0xC6); + + lcdWriteCommand(0xe0); + lcdWriteData(0x0f); + lcdWriteData(0x1C); + lcdWriteData(0x18); + lcdWriteData(0x0B); + lcdWriteData(0x0D); + lcdWriteData(0x06); + lcdWriteData(0x48); + lcdWriteData(0x87); + lcdWriteData(0x3A); + lcdWriteData(0x09); + lcdWriteData(0x15); + lcdWriteData(0x08); + lcdWriteData(0x0D); + lcdWriteData(0x04); + lcdWriteData(0x00); + + lcdWriteCommand(0xe1); + lcdWriteData(0x0f); + lcdWriteData(0x37); + lcdWriteData(0x34); + lcdWriteData(0x0A); + lcdWriteData(0x0B); + lcdWriteData(0x03); + lcdWriteData(0x4B); + lcdWriteData(0x31); + lcdWriteData(0x39); + lcdWriteData(0x03); + lcdWriteData(0x0F); + lcdWriteData(0x03); + lcdWriteData(0x22); + lcdWriteData(0x1D); + lcdWriteData(0x00); + + lcdWriteCommand(0x21); + lcdWriteCommand(0x11); + delay_ms(120); + lcdWriteCommand(0x28); + + LCD_ILI9486_On(); +} + +void LCD_ILI9486_Off(void) { + lcdWriteCommand(0x28); +} + +unsigned int LCD_ILI9486_ReadID(void) { + int ID = 0; + + lcdWriteCommand(0XF7); + lcdWriteData(0xA9); + lcdWriteData(0x51); + lcdWriteData(0x2C); + lcdWriteData(0x82); + lcdWriteCommand(0XB0); + lcdWriteData(0X80); + + lcdWriteCommand(0XFB); + lcdWriteData(0x10 | 0x00); + ID = LCD_ReadRegister(0xd3); + + lcdWriteCommand(0XFB); + lcdWriteData(0x10 | 0x01); + ID = LCD_ReadRegister(0xd3); + + lcdWriteCommand(0XFB); + lcdWriteData(0x10 | 0x02); + ID = LCD_ReadRegister(0xd3); + ID <<= 8; + lcdWriteCommand(0XFB); + lcdWriteData(0x10 | 0x03); + ID |= LCD_ReadRegister(0xd3); + + lcdWriteCommand(0XFB); + lcdWriteData(0x00); + + return (ID); +} + +void LCD_ILI9488_On(void) { + // Display ON + lcdWriteCommand(0x29); +} + +void LCD_ILI9488_Init(void) { + + // lcdWriteCommand(0xFB); + // lcdWriteData(0x00); + + // Adjust Control 3: + // -> DSI write DCS command, use stream packet RGB 666 + lcdWriteCommand(0xF7); + lcdWriteData(0xA9); + lcdWriteData(0x51); + lcdWriteData(0x2C); + lcdWriteData(0x82); + + // Power Control 1: + // -> VREG1OUT = 4.6250 + // -> VREG2OUT = -4.1250 + lcdWriteCommand(0xC0); + lcdWriteData(0x11); + lcdWriteData(0x09); + + // Power Control 2: + // -> VGH = VCI x 6, VGL = VCI x 4 + lcdWriteCommand(0xC1); + lcdWriteData(0x41); + + // VCOM Control + lcdWriteCommand(0xC5); + lcdWriteData(0x00); // NV memory not programmed + lcdWriteData(0x0A); // VCM_REG + lcdWriteData(0x80); // VCM_REG_EN + + // Frame Rate Control + lcdWriteCommand(0xB1); + lcdWriteData(0xB0); + lcdWriteData(0x11); + + // Display Inversion Control + lcdWriteCommand(0xB4); + lcdWriteData(0x01); + + lcdWriteCommand(0xB5); + lcdWriteData(VFP); + lcdWriteData(VBP); + lcdWriteData(HFP); + lcdWriteData(HBP); + + // Display Function Control + lcdWriteCommand(0xB6); + // !DM | RM | RCM + lcdWriteData(0x20 | 0x40); + lcdWriteData(0x02); + lcdWriteData(0x3B); // (59 + 1) x 8 = 480 lines + + // Entry Set Mode + lcdWriteCommand(0xB7); + lcdWriteData(0xc6); + + lcdWriteCommand(0xBE); + lcdWriteData(0x00); + // lcdWriteData(0x04); // ???? + + lcdWriteCommand(0xE9); + lcdWriteData(0x00); + + // Column Address Set + // -> SC=0, EC=479 + lcdWriteCommand( 0x2A ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0xDF ); + + // Page Address Set + // -> SP=0, EP=319 + lcdWriteCommand( 0x2B ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0x3F ); + + // Memory Access Control + lcdWriteCommand(0x36); + lcdWriteData(0x28); // 0x20 -> swap col/rows + + lcdWriteCommand(0x3A); + lcdWriteData(0x66); + + lcdWriteCommand(0xE0); + lcdWriteData(0x00); + lcdWriteData(0x07); + lcdWriteData(0x10); + lcdWriteData(0x09); + lcdWriteData(0x17); + lcdWriteData(0x0B); + lcdWriteData(0x41); + lcdWriteData(0x89); + lcdWriteData(0x4B); + lcdWriteData(0x0A); + lcdWriteData(0x0C); + lcdWriteData(0x0E); + lcdWriteData(0x18); + lcdWriteData(0x1B); + lcdWriteData(0x0F); + + lcdWriteCommand(0xE1); + lcdWriteData(0x00); + lcdWriteData(0x17); + lcdWriteData(0x1A); + lcdWriteData(0x04); + lcdWriteData(0x0E); + lcdWriteData(0x06); + lcdWriteData(0x2F); + lcdWriteData(0x45); + lcdWriteData(0x43); + lcdWriteData(0x02); + lcdWriteData(0x0A); + lcdWriteData(0x09); + lcdWriteData(0x32); + lcdWriteData(0x36); + lcdWriteData(0x0F); + + // Sleep OUT + lcdWriteCommand(0x11); + delay_ms(120); + + // Display OFF + lcdWriteCommand(0x28); + + LCD_ILI9488_On(); +} + +void LCD_ILI9488_Off(void) { + lcdWriteCommand(0x28); +} + +void LCD_ILI9488_ReadDevice(void) { + int Index = 0; + int Parameter = 0x80; + + lcdWriteCommand(0xF7); + lcdWriteData(0xA9); + lcdWriteData(0x51); + lcdWriteData(0x2C); + lcdWriteData(0x82); + + lcdWriteCommand(0xB0); + lcdWriteData(0x80); + + lcdWriteCommand(0xFB); + lcdWriteData(Parameter | 0x00); + LCD_ReadBuffer[Index++] = LCD_ReadRegister(0xd3); + + //lcdWriteCommand(0X2E); + lcdWriteCommand(0xFB); + lcdWriteData(Parameter | 0x01); //Parameter2=0x88 + LCD_ReadBuffer[Index++] = LCD_ReadRegister(0xd3); + + lcdWriteCommand(0XFB); + lcdWriteData(Parameter | 0x02); //Parameter2=0x88 + LCD_ReadBuffer[Index++] = LCD_ReadRegister(0xd3); + + lcdWriteCommand(0XFB); + lcdWriteData(Parameter | 0x03); //Parameter2=0x88 + LCD_ReadBuffer[Index++] = LCD_ReadRegister(0xd3); +} + +unsigned int LCD_ILI9488_ReadID(void) { + int ID = 0; + + // Adjust Control 3: + // -> DSI write DCS command, use stream packet RGB 666 + lcdWriteCommand(0xF7); + lcdWriteData(0xA9); + lcdWriteData(0x51); + lcdWriteData(0x2C); + lcdWriteData(0x82); + + // Interface Mode Control: + // SDA_EN = 1, DIN/SDA pin is used for 3/4 wire serial + // interface and SDO pin is not used. + lcdWriteCommand(0xB0); + lcdWriteData(0x80); + + // first byte is dummy one + lcdWriteCommand(0xFB); + lcdWriteData(0x80 | 0x00); + ID = LCD_ReadRegister(0xd3); + + lcdWriteCommand(0xFB); + lcdWriteData(0x80 | 0x01); + ID = LCD_ReadRegister(0xd3); + + lcdWriteCommand(0xFB); + lcdWriteData(0x80 | 0x02); + ID = LCD_ReadRegister(0xd3); + ID <<= 8; + + lcdWriteCommand(0xFB); + lcdWriteData(0x80 | 0x03); + ID |= LCD_ReadRegister(0xd3); + + lcdWriteCommand(0xFB); + lcdWriteData(0x00); + + return (ID); +} + +void LCD_ST7796S_On(void) { + lcdWriteCommand(0x29); +} + +void LCD_ST7796S_Init(void) { + delay_ms(120); + + lcdWriteCommand( 0x11 ); + delay_ms(120); + + lcdWriteCommand( 0xF0 ); + lcdWriteData( 0xC3 ); + + lcdWriteCommand( 0xF0 ); + lcdWriteData( 0x96 ); + + lcdWriteCommand( 0x36 ); + lcdWriteData( 0x28 ); + + lcdWriteCommand( 0x2A ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0xDF ); + lcdWriteCommand( 0x2B ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x01 ); + lcdWriteData( 0x3F ); + + lcdWriteCommand( 0x3A ); + lcdWriteData( 0x66 ); + + //SET RGB STRAT + lcdWriteCommand (0xB0 ); //SET HS VS DE CLK 上升还是下降有效 + lcdWriteData( 0x80 ); + + lcdWriteCommand( 0xB4 ); + lcdWriteData( 0x01 ); + + lcdWriteCommand( 0xB6 ); + // lcdWriteData( 0x20 ); + // lcdWriteData( 0x02 ); + // lcdWriteData( 0x3B ); + lcdWriteData( 0x20 ); + lcdWriteData( 0x02 ); + lcdWriteData( 0x3B ); + //SET RGB END + + lcdWriteCommand( 0xB7); + lcdWriteData( 0xC6); + + lcdWriteCommand( 0xB9 ); + lcdWriteData( 0x02 ); + lcdWriteData( 0xE0 ); + + lcdWriteCommand( 0xC0 ); + lcdWriteData( 0x80 ); + lcdWriteData( 0x65 ); + + lcdWriteCommand( 0xC1 ); + lcdWriteData( 0x0D ); + + lcdWriteCommand( 0xC2 ); + lcdWriteData( 0xA7 ); + + lcdWriteCommand( 0xC5 ); + lcdWriteData( 0x14 ); + + lcdWriteCommand( 0xE8 ); + lcdWriteData( 0x40 ); + lcdWriteData( 0x8A ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x29 ); + lcdWriteData( 0x19 ); + lcdWriteData( 0xA5 ); + lcdWriteData( 0x33 ); + + lcdWriteCommand( 0xE0 ); + lcdWriteData( 0xD0 ); + lcdWriteData( 0x00 ); + lcdWriteData( 0x04 ); + lcdWriteData( 0x05 ); + lcdWriteData( 0x04 ); + lcdWriteData( 0x21 ); + lcdWriteData( 0x25 ); + lcdWriteData( 0x43 ); + lcdWriteData( 0x3F ); + lcdWriteData( 0x37 ); + lcdWriteData( 0x13 ); + lcdWriteData( 0x13 ); + lcdWriteData( 0x29 ); + lcdWriteData( 0x32 ); + + lcdWriteCommand( 0xE1 ); + lcdWriteData( 0xD0 ); + lcdWriteData( 0x04 ); + lcdWriteData( 0x06 ); + lcdWriteData( 0x09 ); + lcdWriteData( 0x06 ); + lcdWriteData( 0x03 ); + lcdWriteData( 0x25 ); + lcdWriteData( 0x32 ); + lcdWriteData( 0x3E ); + lcdWriteData( 0x18 ); + lcdWriteData( 0x15 ); + lcdWriteData( 0x15 ); + lcdWriteData( 0x2B ); + lcdWriteData( 0x30 ); + + lcdWriteCommand( 0xF0 ); + lcdWriteData( 0x3C ); + + lcdWriteCommand( 0xF0 ); + lcdWriteData( 0x69 ); + + delay_ms(120); + + lcdWriteCommand( 0x21 ); + + LCD_ST7796S_On(); +} + +void LCD_ST7796S_Off(void) { + lcdWriteCommand(0x28); +} + +unsigned int LCD_ST7796S_ReadID(void) { + lcdReset(); + unsigned int ID = 0; + + lcdWriteCommand( 0XF0 ); + lcdWriteData( 0XC3 ); + lcdWriteCommand( 0XF0 ); + lcdWriteData( 0X96 ); + + lcdWriteCommand( 0XB0 ); + lcdWriteData( 0X80 ); + + lcdWriteCommand( 0XD3 ); + + SET_LCD_CLK_OUTPUT(); + SET_LCD_DATA_INPUT(); + CLR_LCD_CLK(); + lcdDelay(); + lcdDelay(); + SET_LCD_CLK(); + lcdDelay(); + lcdDelay(); + + LCD_ReadByte(); + ID += (uint16_t)(LCD_ReadByte())<<8; + ID += LCD_ReadByte(); + + return (ID); + } + + +unsigned int LCD_NT35310_ReadID( void ) +{ + unsigned int ID = 0x3531; + + return( ID ); + +} + +void LCD_NT35310_Init( void ) +{ +#if 1 + lcdWriteCommand(0xED); + lcdWriteData(0x01); + lcdWriteData(0xFE); + + lcdWriteCommand(0xEE); + lcdWriteData(0xDE); + lcdWriteData(0x21); + + lcdWriteCommand(0x11); + delay_ms(120); + lcdWriteCommand(0xB3); + lcdWriteData(0x21); + + + + lcdWriteCommand(0xC0); + lcdWriteData(0x33); + lcdWriteData(0x33); + lcdWriteData(0x10); + lcdWriteData(0x10); + + + lcdWriteCommand(0xC4); + lcdWriteData(0x56); //3a + + lcdWriteCommand(0xBF); + lcdWriteData(0xAA); + + lcdWriteCommand(0xB0); + lcdWriteData(0x0D); + lcdWriteData(0x00); + lcdWriteData(0x0D); + lcdWriteData(0x00); + lcdWriteData(0x11); + lcdWriteData(0x00); + lcdWriteData(0x19); + lcdWriteData(0x00); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x2D); + lcdWriteData(0x00); + lcdWriteData(0x3D); + lcdWriteData(0x00); + lcdWriteData(0x5D); + lcdWriteData(0x00); + lcdWriteData(0x5D); + lcdWriteData(0x00); + + lcdWriteCommand(0xB1); + lcdWriteData(0x80); + lcdWriteData(0x00); + lcdWriteData(0x8B); + lcdWriteData(0x00); + lcdWriteData(0x96); + lcdWriteData(0x00); + + lcdWriteCommand(0xB2); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x02); + lcdWriteData(0x00); + lcdWriteData(0x03); + lcdWriteData(0x00); + + lcdWriteCommand(0xB3); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xB4); + lcdWriteData(0x8B); + lcdWriteData(0x00); + lcdWriteData(0x96); + lcdWriteData(0x00); + lcdWriteData(0xA1); + lcdWriteData(0x00); + + lcdWriteCommand(0xB5); + lcdWriteData(0x02); + lcdWriteData(0x00); + lcdWriteData(0x03); + lcdWriteData(0x00); + lcdWriteData(0x04); + lcdWriteData(0x00); + lcdWriteCommand(0xB6); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xB7); + lcdWriteData(0x3E); + lcdWriteData(0x00); + lcdWriteData(0x5E); + lcdWriteData(0x00); + lcdWriteData(0x9E); + lcdWriteData(0x00); + lcdWriteData(0x74); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0xAC); + lcdWriteData(0x00); + lcdWriteData(0xDC); + lcdWriteData(0x00); + lcdWriteData(0x70); + lcdWriteData(0x00); + lcdWriteData(0xB9); + lcdWriteData(0x00); + lcdWriteData(0xEC); + lcdWriteData(0x00); + lcdWriteData(0xDC); + lcdWriteData(0x00); + + lcdWriteCommand(0xB8); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xBA); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xC1); + lcdWriteData(0x20); + lcdWriteData(0x00); + lcdWriteData(0x54); + lcdWriteData(0x00); + lcdWriteData(0xFF); + lcdWriteData(0x00); + + lcdWriteCommand(0xC2); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x04); + lcdWriteData(0x00); + + lcdWriteCommand(0xC3); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x3A); + lcdWriteData(0x00); + lcdWriteData(0x39); + lcdWriteData(0x00); + lcdWriteData(0x37); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x32); + lcdWriteData(0x00); + lcdWriteData(0x2F); + lcdWriteData(0x00); + lcdWriteData(0x2C); + lcdWriteData(0x00); + lcdWriteData(0x29); + lcdWriteData(0x00); + lcdWriteData(0x26); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x32); + lcdWriteData(0x00); + lcdWriteData(0x2F); + lcdWriteData(0x00); + lcdWriteData(0x2C); + lcdWriteData(0x00); + lcdWriteData(0x29); + lcdWriteData(0x00); + lcdWriteData(0x26); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + + lcdWriteCommand(0xC4); + lcdWriteData(0x62); + lcdWriteData(0x00); + lcdWriteData(0x05); + lcdWriteData(0x00); + lcdWriteData(0x84); + lcdWriteData(0x00); + lcdWriteData(0xF0); + lcdWriteData(0x00); + lcdWriteData(0x18); + lcdWriteData(0x00); + lcdWriteData(0xA4); + lcdWriteData(0x00); + lcdWriteData(0x18); + lcdWriteData(0x00); + lcdWriteData(0x50); + lcdWriteData(0x00); + lcdWriteData(0x0C); + lcdWriteData(0x00); + lcdWriteData(0x17); + lcdWriteData(0x00); + lcdWriteData(0x95); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + lcdWriteData(0xE6); + lcdWriteData(0x00); + + lcdWriteCommand(0xC5); + lcdWriteData(0x32); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + lcdWriteData(0x76); + lcdWriteData(0x00); + lcdWriteData(0x88); + lcdWriteData(0x00); + + lcdWriteCommand(0xC6); + lcdWriteData(0x20); + lcdWriteData(0x00); + lcdWriteData(0x17); + lcdWriteData(0x00); + lcdWriteData(0x01); + lcdWriteData(0x00); + + lcdWriteCommand(0xC7); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xC8); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xC9); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xE0); + lcdWriteData(0x02); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0x00); + lcdWriteData(0x25); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x6F); + lcdWriteData(0x00); + lcdWriteData(0x7F); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0x98); + lcdWriteData(0x00); + lcdWriteData(0xA6); + lcdWriteData(0x00); + lcdWriteData(0xAE); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBB); + lcdWriteData(0x00); + lcdWriteData(0xC0); + lcdWriteData(0x00); + lcdWriteData(0xC9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE1); + lcdWriteData(0x01); + lcdWriteData(0x00); + lcdWriteData(0x05); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0x00); + lcdWriteData(0x25); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x6F); + lcdWriteData(0x00); + lcdWriteData(0x7F); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0x98); + lcdWriteData(0x00); + lcdWriteData(0xA6); + lcdWriteData(0x00); + lcdWriteData(0xAE); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBB); + lcdWriteData(0x00); + lcdWriteData(0xC0); + lcdWriteData(0x00); + lcdWriteData(0xC9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE2); + lcdWriteData(0x02); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0x00); + lcdWriteData(0x25); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x6F); + lcdWriteData(0x00); + lcdWriteData(0x7F); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0x98); + lcdWriteData(0x00); + lcdWriteData(0xA6); + lcdWriteData(0x00); + lcdWriteData(0xAE); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBB); + lcdWriteData(0x00); + lcdWriteData(0xC0); + lcdWriteData(0x00); + lcdWriteData(0xC9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE3); + lcdWriteData(0x01); + lcdWriteData(0x00); + lcdWriteData(0x05); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0x00); + lcdWriteData(0x25); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x6F); + lcdWriteData(0x00); + lcdWriteData(0x7F); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0x98); + lcdWriteData(0x00); + lcdWriteData(0xA6); + lcdWriteData(0x00); + lcdWriteData(0xAE); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBB); + lcdWriteData(0x00); + lcdWriteData(0xC0); + lcdWriteData(0x00); + lcdWriteData(0xC9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE4); + lcdWriteData(0x02); + lcdWriteData(0x00); + lcdWriteData(0x06); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0x00); + lcdWriteData(0x25); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x6F); + lcdWriteData(0x00); + lcdWriteData(0x7F); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0x98); + lcdWriteData(0x00); + lcdWriteData(0xA6); + lcdWriteData(0x00); + lcdWriteData(0xAE); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBB); + lcdWriteData(0x00); + lcdWriteData(0xC0); + lcdWriteData(0x00); + lcdWriteData(0xC9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE5); + lcdWriteData(0x01); + lcdWriteData(0x00); + lcdWriteData(0x05); + lcdWriteData(0x00); + lcdWriteData(0x10); + lcdWriteData(0x00); + lcdWriteData(0x25); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x6F); + lcdWriteData(0x00); + lcdWriteData(0x7F); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0x98); + lcdWriteData(0x00); + lcdWriteData(0xA6); + lcdWriteData(0x00); + lcdWriteData(0xAE); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBB); + lcdWriteData(0x00); + lcdWriteData(0xC0); + lcdWriteData(0x00); + lcdWriteData(0xC9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE6); + lcdWriteData(0x55); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x56); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x77); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + + lcdWriteCommand(0xE7); + lcdWriteData(0x55); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x56); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x77); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + + lcdWriteCommand(0xE8); + lcdWriteData(0x55); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x56); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x77); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + + lcdWriteCommand(0xE9); + lcdWriteData(0xAA); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0x00); + lcdWriteData(0xAA); + + lcdWriteCommand(0xCF); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xF0); + lcdWriteData(0x00); + lcdWriteData(0x50); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xF1); + lcdWriteData(0x01); + + lcdWriteCommand(0xF9); + lcdWriteData(0x06); + lcdWriteData(0x10); + lcdWriteData(0x29); + lcdWriteData(0x00); + + lcdWriteCommand(0xDF); + lcdWriteData(0x10); + delay_ms(20); + lcdWriteCommand(0x36); +// if( IsHorizontal ) +// lcdWriteData(0x00);//需修改 +// else + lcdWriteData(0x14); + + lcdWriteCommand(0x3A); + lcdWriteData(0x66); + + lcdWriteCommand(0x21); + + lcdWriteCommand(0x35); + lcdWriteData(0x00); + + lcdWriteCommand(0x29); +#else + lcdWriteCommand(0xED); + lcdWriteData(0x01); + lcdWriteData(0xFE); + + lcdWriteCommand(0xEE); + lcdWriteData(0xDE); + lcdWriteData(0x21); + + lcdWriteCommand(0x11); + SYSTEM_DelayMS(120); + lcdWriteCommand(0xB3); + lcdWriteData(0x21); + + + lcdWriteCommand(0xc0); + lcdWriteData(0x56); + lcdWriteData(0x56); + lcdWriteData(0x24); + lcdWriteData(0x24); + + lcdWriteCommand(0xC4); + lcdWriteData(0x30); //3a + + lcdWriteCommand(0xBF); + lcdWriteData(0xAA); + + lcdWriteCommand(0xB0); + lcdWriteData(0x0D); + lcdWriteData(0x00); + lcdWriteData(0x0D); + lcdWriteData(0x00); + lcdWriteData(0x11); + lcdWriteData(0x00); + lcdWriteData(0x19); + lcdWriteData(0x00); + lcdWriteData(0x21); + lcdWriteData(0x00); + lcdWriteData(0x2D); + lcdWriteData(0x00); + lcdWriteData(0x3D); + lcdWriteData(0x00); + lcdWriteData(0x5D); + lcdWriteData(0x00); + lcdWriteData(0x5D); + lcdWriteData(0x00); + + lcdWriteCommand(0xB1); + lcdWriteData(0x80); + lcdWriteData(0x00); + lcdWriteData(0x8B); + lcdWriteData(0x00); + lcdWriteData(0x96); + lcdWriteData(0x00); + + lcdWriteCommand(0xB2); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x02); + lcdWriteData(0x00); + lcdWriteData(0x03); + lcdWriteData(0x00); + + lcdWriteCommand(0xB3); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xB4); + lcdWriteData(0x8B); + lcdWriteData(0x00); + lcdWriteData(0x96); + lcdWriteData(0x00); + lcdWriteData(0xA1); + lcdWriteData(0x00); + + lcdWriteCommand(0xB5); + lcdWriteData(0x02); + lcdWriteData(0x00); + lcdWriteData(0x03); + lcdWriteData(0x00); + lcdWriteData(0x04); + lcdWriteData(0x00); + lcdWriteCommand(0xB6); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xB7); + lcdWriteData(0x3E); + lcdWriteData(0x00); + lcdWriteData(0x5E); + lcdWriteData(0x00); + lcdWriteData(0x9E); + lcdWriteData(0x00); + lcdWriteData(0x74); + lcdWriteData(0x00); + lcdWriteData(0x8C); + lcdWriteData(0x00); + lcdWriteData(0xAC); + lcdWriteData(0x00); + lcdWriteData(0xDC); + lcdWriteData(0x00); + lcdWriteData(0x70); + lcdWriteData(0x00); + lcdWriteData(0xB9); + lcdWriteData(0x00); + lcdWriteData(0xEC); + lcdWriteData(0x00); + lcdWriteData(0xDC); + lcdWriteData(0x00); + + lcdWriteCommand(0xB8); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xBA); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xC1); + lcdWriteData(0x20); + lcdWriteData(0x00); + lcdWriteData(0x54); + lcdWriteData(0x00); + lcdWriteData(0xFF); + lcdWriteData(0x00); + + lcdWriteCommand(0xC2); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x04); + lcdWriteData(0x00); + + lcdWriteCommand(0xC3); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x3A); + lcdWriteData(0x00); + lcdWriteData(0x39); + lcdWriteData(0x00); + lcdWriteData(0x37); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x32); + lcdWriteData(0x00); + lcdWriteData(0x2F); + lcdWriteData(0x00); + lcdWriteData(0x2C); + lcdWriteData(0x00); + lcdWriteData(0x29); + lcdWriteData(0x00); + lcdWriteData(0x26); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x36); + lcdWriteData(0x00); + lcdWriteData(0x32); + lcdWriteData(0x00); + lcdWriteData(0x2F); + lcdWriteData(0x00); + lcdWriteData(0x2C); + lcdWriteData(0x00); + lcdWriteData(0x29); + lcdWriteData(0x00); + lcdWriteData(0x26); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x24); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + + lcdWriteCommand(0xC4); + lcdWriteData(0x62); + lcdWriteData(0x00); + lcdWriteData(0x05); + lcdWriteData(0x00); + lcdWriteData(0x84); + lcdWriteData(0x00); + lcdWriteData(0xF0); + lcdWriteData(0x00); + lcdWriteData(0x18); + lcdWriteData(0x00); + lcdWriteData(0xA4); + lcdWriteData(0x00); + lcdWriteData(0x18); + lcdWriteData(0x00); + lcdWriteData(0x50); + lcdWriteData(0x00); + lcdWriteData(0x0C); + lcdWriteData(0x00); + lcdWriteData(0x17); + lcdWriteData(0x00); + lcdWriteData(0x95); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + lcdWriteData(0xE6); + lcdWriteData(0x00); + + lcdWriteCommand(0xC5); + lcdWriteData(0x32); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + lcdWriteData(0x76); + lcdWriteData(0x00); + lcdWriteData(0x88); + lcdWriteData(0x00); + + lcdWriteCommand(0xC6); + lcdWriteData(0x20); + lcdWriteData(0x00); + lcdWriteData(0x17); + lcdWriteData(0x00); + lcdWriteData(0x01); + lcdWriteData(0x00); + + lcdWriteCommand(0xC7); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xC8); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xC9); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xE0); + lcdWriteData(0x01); + lcdWriteData(0x00); + lcdWriteData(0x1F); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x59); + lcdWriteData(0x00); + lcdWriteData(0x67); + lcdWriteData(0x00); + lcdWriteData(0x72); + lcdWriteData(0x00); + lcdWriteData(0x82); + lcdWriteData(0x00); + lcdWriteData(0x93); + lcdWriteData(0x00); + lcdWriteData(0xA0); + lcdWriteData(0x00); + lcdWriteData(0xAB); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBF); + lcdWriteData(0x00); + lcdWriteData(0xC6); + lcdWriteData(0x00); + lcdWriteData(0xCA); + lcdWriteData(0x00); + lcdWriteData(0xCF); + lcdWriteData(0x00); + lcdWriteData(0xD3); + lcdWriteData(0x00); + lcdWriteData(0xDA); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE1); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x1F); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x59); + lcdWriteData(0x00); + lcdWriteData(0x67); + lcdWriteData(0x00); + lcdWriteData(0x72); + lcdWriteData(0x00); + lcdWriteData(0x82); + lcdWriteData(0x00); + lcdWriteData(0x93); + lcdWriteData(0x00); + lcdWriteData(0xA0); + lcdWriteData(0x00); + lcdWriteData(0xAB); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBF); + lcdWriteData(0x00); + lcdWriteData(0xC6); + lcdWriteData(0x00); + lcdWriteData(0xCA); + lcdWriteData(0x00); + lcdWriteData(0xD0); + lcdWriteData(0x00); + lcdWriteData(0xD4); + lcdWriteData(0x00); + lcdWriteData(0xD9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE2); + lcdWriteData(0x10); + lcdWriteData(0x00); + lcdWriteData(0x1F); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x59); + lcdWriteData(0x00); + lcdWriteData(0x67); + lcdWriteData(0x00); + lcdWriteData(0x72); + lcdWriteData(0x00); + lcdWriteData(0x82); + lcdWriteData(0x00); + lcdWriteData(0x93); + lcdWriteData(0x00); + lcdWriteData(0xA0); + lcdWriteData(0x00); + lcdWriteData(0xAB); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBF); + lcdWriteData(0x00); + lcdWriteData(0xC6); + lcdWriteData(0x00); + lcdWriteData(0xCA); + lcdWriteData(0x00); + lcdWriteData(0xCF); + lcdWriteData(0x00); + lcdWriteData(0xD3); + lcdWriteData(0x00); + lcdWriteData(0xDA); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE3); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x1F); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x59); + lcdWriteData(0x00); + lcdWriteData(0x67); + lcdWriteData(0x00); + lcdWriteData(0x72); + lcdWriteData(0x00); + lcdWriteData(0x82); + lcdWriteData(0x00); + lcdWriteData(0x93); + lcdWriteData(0x00); + lcdWriteData(0xA0); + lcdWriteData(0x00); + lcdWriteData(0xAB); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBF); + lcdWriteData(0x00); + lcdWriteData(0xC6); + lcdWriteData(0x00); + lcdWriteData(0xCA); + lcdWriteData(0x00); + lcdWriteData(0xD0); + lcdWriteData(0x00); + lcdWriteData(0xD4); + lcdWriteData(0x00); + lcdWriteData(0xD9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE4); + lcdWriteData(0x01); + lcdWriteData(0x00); + lcdWriteData(0x1F); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x59); + lcdWriteData(0x00); + lcdWriteData(0x67); + lcdWriteData(0x00); + lcdWriteData(0x72); + lcdWriteData(0x00); + lcdWriteData(0x82); + lcdWriteData(0x00); + lcdWriteData(0x93); + lcdWriteData(0x00); + lcdWriteData(0xA0); + lcdWriteData(0x00); + lcdWriteData(0xAB); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBF); + lcdWriteData(0x00); + lcdWriteData(0xC6); + lcdWriteData(0x00); + lcdWriteData(0xCA); + lcdWriteData(0x00); + lcdWriteData(0xCF); + lcdWriteData(0x00); + lcdWriteData(0xD3); + lcdWriteData(0x00); + lcdWriteData(0xDA); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE5); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x1F); + lcdWriteData(0x00); + lcdWriteData(0x3C); + lcdWriteData(0x00); + lcdWriteData(0x59); + lcdWriteData(0x00); + lcdWriteData(0x67); + lcdWriteData(0x00); + lcdWriteData(0x72); + lcdWriteData(0x00); + lcdWriteData(0x82); + lcdWriteData(0x00); + lcdWriteData(0x93); + lcdWriteData(0x00); + lcdWriteData(0xA0); + lcdWriteData(0x00); + lcdWriteData(0xAB); + lcdWriteData(0x00); + lcdWriteData(0xB4); + lcdWriteData(0x00); + lcdWriteData(0xBF); + lcdWriteData(0x00); + lcdWriteData(0xC6); + lcdWriteData(0x00); + lcdWriteData(0xCA); + lcdWriteData(0x00); + lcdWriteData(0xD0); + lcdWriteData(0x00); + lcdWriteData(0xD4); + lcdWriteData(0x00); + lcdWriteData(0xD9); + lcdWriteData(0x00); + lcdWriteData(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xE6); + lcdWriteData(0x55); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x56); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x77); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + + lcdWriteCommand(0xE7); + lcdWriteData(0x55); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x56); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x77); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + + lcdWriteCommand(0xE8); + lcdWriteData(0x55); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x56); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x57); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x77); + lcdWriteData(0x00); + lcdWriteData(0x66); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x44); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x33); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x23); + lcdWriteData(0x00); + lcdWriteData(0x65); + lcdWriteData(0x00); + + lcdWriteCommand(0xE9); + lcdWriteData(0xAA); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0x00); + lcdWriteData(0xAA); + + lcdWriteCommand(0xCF); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xF0); + lcdWriteData(0x00); + lcdWriteData(0x50); + lcdWriteData(0x00); + lcdWriteData(0x00); + lcdWriteData(0x00); + + lcdWriteCommand(0xF1); + lcdWriteData(0x01); + + lcdWriteCommand(0xee); + lcdWriteData(0xde); + lcdWriteData(0x21); + + lcdWriteCommand(0xF3); + lcdWriteData(0x00); + + lcdWriteCommand(0xF9); + lcdWriteData(0x06); + lcdWriteData(0x10); + lcdWriteData(0x29); + lcdWriteData(0x00); + + lcdWriteCommand(0xDF); + lcdWriteData(0x10); + SYSTEM_DelayMS(20); + lcdWriteCommand(0x36); + if( IsHorizontal ) + lcdWriteData(0x14);//需修改 + else + lcdWriteData(0x14); + + lcdWriteCommand(0x3A); + lcdWriteData(0x66); + + lcdWriteCommand(0x21); + + lcdWriteCommand(0x35); + lcdWriteData(0x00); + + lcdWriteCommand(0x28); +#endif +} + +void LCD_NT35310_On( void ) +{ + lcdWriteCommand( 0x29 ); +} + +void LCD_NT35310_Off( void ) +{ + lcdWriteCommand( 0x28 ); +} + +void LCD_Init_LTDC() { + LTDC_InitTypeDef LTDC_InitStruct; + + /* Configure PLLSAI prescalers for LCD */ + /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */ + /* PLLSAI_VCO Output = PLLSAI_VCO Input * lcdPixelclock * 16 = XX Mhz */ + /* PLLLCDCLK = PLLSAI_VCO Output/PLL_LTDC = PLLSAI_VCO/4 = YY Mhz */ + /* LTDC clock frequency = PLLLCDCLK / RCC_PLLSAIDivR = YY/4 = lcdPixelClock Mhz */ + uint32_t clock = (lcdPixelClock*16) / 1000000; // clock*16 in MHz + RCC_PLLSAIConfig(clock, 6, 4); + RCC_LTDCCLKDivConfig (RCC_PLLSAIDivR_Div4); + + /* Enable PLLSAI Clock */ + RCC_PLLSAICmd(ENABLE); + + /* Wait for PLLSAI activation */ + while (RCC_GetFlagStatus(RCC_FLAG_PLLSAIRDY) == RESET); + + /* LTDC Configuration *********************************************************/ + /* Polarity configuration */ + /* Initialize the horizontal synchronization polarity as active low */ + LTDC_InitStruct.LTDC_HSPolarity = LTDC_HSPolarity_AL; + /* Initialize the vertical synchronization polarity as active low */ + LTDC_InitStruct.LTDC_VSPolarity = LTDC_VSPolarity_AL; + /* Initialize the data enable polarity as active low */ + LTDC_InitStruct.LTDC_DEPolarity = LTDC_DEPolarity_AL; + /* Initialize the pixel clock polarity as input pixel clock */ +// LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IPC; + LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IIPC; + + /* Configure R,G,B component values for LCD background color */ + LTDC_InitStruct.LTDC_BackgroundRedValue = 0; + LTDC_InitStruct.LTDC_BackgroundGreenValue = 0; + LTDC_InitStruct.LTDC_BackgroundBlueValue = 0; + + /* Configure horizontal synchronization width */ + LTDC_InitStruct.LTDC_HorizontalSync = HSW; + /* Configure vertical synchronization height */ + LTDC_InitStruct.LTDC_VerticalSync = VSH; + /* Configure accumulated horizontal back porch */ + LTDC_InitStruct.LTDC_AccumulatedHBP = HBP; + /* Configure accumulated vertical back porch */ + LTDC_InitStruct.LTDC_AccumulatedVBP = VBP; + /* Configure accumulated active width */ + LTDC_InitStruct.LTDC_AccumulatedActiveW = LCD_PHYS_W + HBP; + /* Configure accumulated active height */ + LTDC_InitStruct.LTDC_AccumulatedActiveH = LCD_PHYS_H + VBP; + /* Configure total width */ + LTDC_InitStruct.LTDC_TotalWidth = LCD_PHYS_W + HBP + HFP; + /* Configure total height */ + LTDC_InitStruct.LTDC_TotalHeigh = LCD_PHYS_H + VBP + VFP; + + LTDC_Init(<DC_InitStruct); + + // Configure IRQ (line) + NVIC_SetPriority(LTDC_IRQn, LTDC_IRQ_PRIO); + NVIC_EnableIRQ(LTDC_IRQn); + + + // Trigger on last line + LTDC_LIPConfig(LCD_PHYS_H); + LTDC_ITConfig(LTDC_IER_LIE, ENABLE); +} + +void LCD_LayerInit() { + LTDC_Layer_InitTypeDef LTDC_Layer_InitStruct; + + /* Windowing configuration */ + /* In this case all the active display area is used to display a picture then : + Horizontal start = horizontal synchronization + Horizontal back porch = 30 + Horizontal stop = Horizontal start + window width -1 = 30 + 240 -1 + Vertical start = vertical synchronization + vertical back porch = 4 + Vertical stop = Vertical start + window height -1 = 4 + 320 -1 */ + LTDC_Layer_InitStruct.LTDC_HorizontalStart = HBP + 1; + LTDC_Layer_InitStruct.LTDC_HorizontalStop = (LCD_PHYS_W + HBP); + LTDC_Layer_InitStruct.LTDC_VerticalStart = VBP + 1; + LTDC_Layer_InitStruct.LTDC_VerticalStop = (LCD_PHYS_H + VBP); + + /* Pixel Format configuration*/ + LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_RGB565; + /* Alpha constant (255 totally opaque) */ + LTDC_Layer_InitStruct.LTDC_ConstantAlpha = 255; + /* Default Color configuration (configure A,R,G,B component values) */ + LTDC_Layer_InitStruct.LTDC_DefaultColorBlue = 0; + LTDC_Layer_InitStruct.LTDC_DefaultColorGreen = 0; + LTDC_Layer_InitStruct.LTDC_DefaultColorRed = 0; + LTDC_Layer_InitStruct.LTDC_DefaultColorAlpha = 0; + + /* Configure blending factors */ + LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_CA; + LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_CA; + + /* the length of one line of pixels in bytes + 3 then : + Line Lenth = Active high width x number of bytes per pixel + 3 + Active high width = LCD_W + number of bytes per pixel = 2 (pixel_format : RGB565) + */ + LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((LCD_PHYS_W * 2) + 3); + /* the pitch is the increment from the start of one line of pixels to the + start of the next line in bytes, then : + Pitch = Active high width x number of bytes per pixel */ + LTDC_Layer_InitStruct.LTDC_CFBPitch = (LCD_PHYS_W * 2); + + /* Configure the number of lines */ + LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_PHYS_H; + + /* Start Address configuration : the LCD Frame buffer is defined on SDRAM w/ Offset */ + uint32_t layer_address = (uint32_t)lcdFront->getData(); + LTDC_Layer_InitStruct.LTDC_CFBStartAdress = layer_address; + + /* Initialize LTDC layer 1 */ + LTDC_LayerInit(LTDC_Layer1, <DC_Layer_InitStruct); + + /* LTDC configuration reload */ + LTDC_ReloadConfig(LTDC_IMReload); + + LTDC_LayerCmd(LTDC_Layer1, ENABLE); + LTDC_LayerAlpha(LTDC_Layer1, 255); + + LTDC_ReloadConfig(LTDC_IMReload); + + /* dithering activation */ + LTDC_DitherCmd(ENABLE); +} + +const char* boardLcdType = ""; + +void lcdInit(void) +{ + /* Configure the LCD SPI+RESET pins */ + lcdSpiConfig(); + + // TODO: init lVGL + + /* Reset the LCD --------------------------------------------------------*/ + lcdReset(); + + /* Configure the LCD Control pins */ + LCD_AF_GPIOConfig(); + + /* Send LCD initialization commands */ + if (LCD_ILI9481_ReadID() == LCD_ILI9481_ID) { + TRACE("LCD INIT: ILI9481"); + boardLcdType = "ILI9481"; + lcdInitFunction = LCD_ILI9481_Init; + lcdOffFunction = LCD_ILI9481_Off; + lcdOnFunction = LCD_ILI9481_On; + lcdPixelClock = 12000000; + } else if (LCD_ILI9486_ReadID() == LCD_ILI9486_ID) { + TRACE("LCD INIT: ILI9486"); + boardLcdType = "ILI9486"; + lcdInitFunction = LCD_ILI9486_Init; + lcdOffFunction = LCD_ILI9486_Off; + lcdOnFunction = LCD_ILI9486_On; + lcdPixelClock = 12000000; + } else if (LCD_ILI9488_ReadID() == LCD_ILI9488_ID) { + TRACE("LCD INIT: ILI9488"); + boardLcdType = "ILI9488"; + lcdInitFunction = LCD_ILI9488_Init; + lcdOffFunction = LCD_ILI9488_Off; + lcdOnFunction = LCD_ILI9488_On; + lcdPixelClock = 12000000; + } else if (LCD_HX8357D_ReadID() == LCD_HX8357D_ID) { + TRACE("LCD INIT: HX8357D"); + boardLcdType = "HX8357D"; + lcdInitFunction = LCD_HX8357D_Init; + lcdOffFunction = LCD_HX8357D_Off; + lcdOnFunction = LCD_HX8357D_On; + lcdPixelClock = 12000000; + } else if (LCD_ST7796S_ReadID() == LCD_ST7796S_ID || 1) { + TRACE("LCD INIT (default): ST7796S"); + boardLcdType = "ST7796S"; + lcdInitFunction = LCD_ST7796S_Init; + lcdOffFunction = LCD_ST7796S_Off; + lcdOnFunction = LCD_ST7796S_On; + lcdPixelClock = 14500000; + /*} else { // NT35310 can not be detected + TRACE("LCD INIT (default): NT35310"); + boardLcdType = "NT35310"; + lcdInitFunction = LCD_NT35310_Init; + lcdOffFunction = LCD_NT35310_Off; + lcdOnFunction = LCD_NT35310_On; + lcdPixelClock = 12500000;*/ +/* } else { + TRACE("LCD INIT: unknown LCD controller"); + boardLcdType = "unknown";*/ + } + + lcdInitFunction(); + + LCD_Init_LTDC(); + LCD_LayerInit(); + LTDC_Cmd(ENABLE); + LTDC_ReloadConfig(LTDC_IMReload); + + lcdSetFlushCb(startLcdRefresh); +} + +void DMAWait() +{ + while(DMA2D->CR & DMA2D_CR_START); +} + +void DMACopyBitmap(uint16_t *dest, uint16_t destw, uint16_t desth, uint16_t x, + uint16_t y, const uint16_t *src, uint16_t srcw, + uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, + uint16_t h) +{ + DMAWait(); + DMA2D_DeInit(); + + DMA2D_InitTypeDef DMA2D_InitStruct; + DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M; + DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest + y*destw + x); + DMA2D_InitStruct.DMA2D_OutputGreen = 0; + DMA2D_InitStruct.DMA2D_OutputBlue = 0; + DMA2D_InitStruct.DMA2D_OutputRed = 0; + DMA2D_InitStruct.DMA2D_OutputAlpha = 0; + DMA2D_InitStruct.DMA2D_OutputOffset = destw - w; + DMA2D_InitStruct.DMA2D_NumberOfLine = h; + DMA2D_InitStruct.DMA2D_PixelPerLine = w; + DMA2D_Init(&DMA2D_InitStruct); + + DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; + DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); + DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src + srcy*srcw + srcx); + DMA2D_FG_InitStruct.DMA2D_FGO = srcw - w; + DMA2D_FG_InitStruct.DMA2D_FGCM = CM_RGB565; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; + DMA2D_FGConfig(&DMA2D_FG_InitStruct); + + /* Start Transfer */ + DMA2D_StartTransfer(); +} + +void DMACopyAlphaBitmap(uint16_t *dest, uint16_t destw, uint16_t desth, + uint16_t x, uint16_t y, const uint16_t *src, + uint16_t srcw, uint16_t srch, uint16_t srcx, + uint16_t srcy, uint16_t w, uint16_t h) +{ + DMAWait(); + DMA2D_DeInit(); + + DMA2D_InitTypeDef DMA2D_InitStruct; + DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M_BLEND; + DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest + y*destw + x); + DMA2D_InitStruct.DMA2D_OutputGreen = 0; + DMA2D_InitStruct.DMA2D_OutputBlue = 0; + DMA2D_InitStruct.DMA2D_OutputRed = 0; + DMA2D_InitStruct.DMA2D_OutputAlpha = 0; + DMA2D_InitStruct.DMA2D_OutputOffset = destw - w; + DMA2D_InitStruct.DMA2D_NumberOfLine = h; + DMA2D_InitStruct.DMA2D_PixelPerLine = w; + DMA2D_Init(&DMA2D_InitStruct); + + DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; + DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); + DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src + srcy*srcw + srcx); + DMA2D_FG_InitStruct.DMA2D_FGO = srcw - w; + DMA2D_FG_InitStruct.DMA2D_FGCM = CM_ARGB4444; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; + DMA2D_FGConfig(&DMA2D_FG_InitStruct); + + DMA2D_BG_InitTypeDef DMA2D_BG_InitStruct; + DMA2D_BG_StructInit(&DMA2D_BG_InitStruct); + DMA2D_BG_InitStruct.DMA2D_BGMA = CONVERT_PTR_UINT(dest + y*destw + x); + DMA2D_BG_InitStruct.DMA2D_BGO = destw - w; + DMA2D_BG_InitStruct.DMA2D_BGCM = CM_RGB565; + DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; + DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_VALUE = 0; + DMA2D_BGConfig(&DMA2D_BG_InitStruct); + + /* Start Transfer */ + DMA2D_StartTransfer(); +} + +// same as DMACopyAlphaBitmap(), but with an 8 bit mask for each pixel (used by fonts) +void DMACopyAlphaMask(uint16_t *dest, uint16_t destw, uint16_t desth, + uint16_t x, uint16_t y, const uint8_t *src, uint16_t srcw, + uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, + uint16_t h, uint16_t bg_color) +{ + DMAWait(); + DMA2D_DeInit(); + + DMA2D_InitTypeDef DMA2D_InitStruct; + DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M_BLEND; + DMA2D_InitStruct.DMA2D_CMode = CM_RGB565; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest + y*destw + x); + DMA2D_InitStruct.DMA2D_OutputBlue = 0; + DMA2D_InitStruct.DMA2D_OutputGreen = 0; + DMA2D_InitStruct.DMA2D_OutputRed = 0; + DMA2D_InitStruct.DMA2D_OutputAlpha = 0; + DMA2D_InitStruct.DMA2D_OutputOffset = destw - w; + DMA2D_InitStruct.DMA2D_NumberOfLine = h; + DMA2D_InitStruct.DMA2D_PixelPerLine = w; + DMA2D_Init(&DMA2D_InitStruct); + + DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; + DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); + DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src + srcy*srcw + srcx); + DMA2D_FG_InitStruct.DMA2D_FGO = srcw - w; + DMA2D_FG_InitStruct.DMA2D_FGCM = CM_A8; // 8 bit inputs every time + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; + DMA2D_FG_InitStruct.DMA2D_FGC_RED = GET_RED(bg_color); // 8 bit red + DMA2D_FG_InitStruct.DMA2D_FGC_GREEN = GET_GREEN(bg_color); // 8 bit green + DMA2D_FG_InitStruct.DMA2D_FGC_BLUE = GET_BLUE(bg_color); // 8 bit blue + + DMA2D_FGConfig(&DMA2D_FG_InitStruct); + + DMA2D_BG_InitTypeDef DMA2D_BG_InitStruct; + DMA2D_BG_StructInit(&DMA2D_BG_InitStruct); + DMA2D_BG_InitStruct.DMA2D_BGMA = CONVERT_PTR_UINT(dest + y*destw + x); + DMA2D_BG_InitStruct.DMA2D_BGO = destw - w; + DMA2D_BG_InitStruct.DMA2D_BGCM = CM_RGB565; + DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; + DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_VALUE = 0; + DMA2D_BGConfig(&DMA2D_BG_InitStruct); + + /* Start Transfer */ + DMA2D_StartTransfer(); +} + +void DMABitmapConvert(uint16_t * dest, const uint8_t * src, uint16_t w, uint16_t h, uint32_t format) +{ + DMAWait(); + DMA2D_DeInit(); + + DMA2D_InitTypeDef DMA2D_InitStruct; + DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M_PFC; + DMA2D_InitStruct.DMA2D_CMode = format; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest); + DMA2D_InitStruct.DMA2D_OutputGreen = 0; + DMA2D_InitStruct.DMA2D_OutputBlue = 0; + DMA2D_InitStruct.DMA2D_OutputRed = 0; + DMA2D_InitStruct.DMA2D_OutputAlpha = 0; + DMA2D_InitStruct.DMA2D_OutputOffset = 0; + DMA2D_InitStruct.DMA2D_NumberOfLine = h; + DMA2D_InitStruct.DMA2D_PixelPerLine = w; + DMA2D_Init(&DMA2D_InitStruct); + + DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; + DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); + DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src); + DMA2D_FG_InitStruct.DMA2D_FGO = 0; + DMA2D_FG_InitStruct.DMA2D_FGCM = CM_ARGB8888; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = REPLACE_ALPHA_VALUE; + DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; + DMA2D_FGConfig(&DMA2D_FG_InitStruct); + + /* Start Transfer */ + DMA2D_StartTransfer(); +} + +extern "C" void LTDC_IRQHandler(void) +{ + // clear interrupt flag + LTDC->ICR = LTDC_ICR_CLIF; + + _frame_addr_reloaded = 1; +} diff --git a/radio/src/targets/pl18/lcd_driver.h b/radio/src/targets/pl18/lcd_driver.h new file mode 100644 index 00000000000..27519c68607 --- /dev/null +++ b/radio/src/targets/pl18/lcd_driver.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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. + */ + +#ifndef __LCD_DRIVER_H__ +#define __LCD_DRIVER_H__ + +#include "board.h" + +#define HBP ( 24 ) // TODO use names from FlySky +#define VBP ( 10 ) + +#define HSW ( 4 ) +#define VSH ( 2 ) + +#define HFP ( 140 - HBP ) +#define VFP ( 22 - VBP ) + + +#define PORT_LCD_CS ( GPIOE ) +#define LCD_CS_PIN ( GPIO_Pin_4 ) +#define PIN_LCD_CS ( 4 ) + +#define PORT_LCD_CLK ( GPIOE ) +#define LCD_CLK_PIN ( GPIO_Pin_2 ) +#define PIN_LCD_CLK ( 2 ) + +#define PORT_LCD_MOSI ( GPIOE ) +#define LCD_MOSI_PIN ( GPIO_Pin_6 ) +#define PIN_LCD_MOSI ( 6 ) + +#define PORT_LCD_MISO ( GPIOE ) +#define LCD_MISO_PIN ( GPIO_Pin_5 ) +#define PIN_LCD_MISO ( 5 ) + +#define PORT_LCD_DE ( GPIOK ) +#define LCD_DE_PIN ( GPIO_Pin_7 ) +#define PIN_LCD_DE ( 7 ) + +#define PORT_LCD_RESET ( GPIOG ) +#define LCD_RESET_PIN ( GPIO_Pin_9 ) +#define PIN_LCD_RESET ( 9 ) + +#define PORT_LCD_HSYNC ( GPIOI ) +#define LCD_HSYNC_PIN ( GPIO_Pin_12 ) +#define PIN_LCD_HSYNC ( 12 ) + +#define PORT_LCD_VSYNC ( GPIOI ) +#define LCD_VSYNC_PIN ( GPIO_Pin_13 ) +#define PIN_LCD_VSYNC ( 13 ) + +#define PORT_LCD_DOTCLK ( GPIOG ) +#define LCD_DOTCLK_PIN ( GPIO_Pin_7 ) +#define PIN_LCD_DOTCLK ( 7 ) + +#define SUPPORTED_LCD_CNT ( 5 ) + +#define LCD_ST7796S_ID ( 0x7796 ) +#define LCD_ILI9481_ID ( 0x9481 ) +#define LCD_ILI9486_ID ( 0x9486 ) +#define LCD_ILI9488_ID ( 0x9488 ) +#define LCD_HX8357D_ID ( 0x99 ) + +#define LCD_DELAY() LCD_Delay() + +typedef void (*lcdSpiInitFucPtr)(void); +typedef unsigned int LcdReadIDFucPtr( void ); + +extern void GPIO_SetDirection( GPIO_TypeDef *GPIOx, unsigned char Pin, unsigned char IsInput ); + +extern lcdSpiInitFucPtr lcdInitFunction; +extern lcdSpiInitFucPtr lcdOffFunction; +extern lcdSpiInitFucPtr lcdOnFunction; + +#define SET_IO_INPUT( PORT, PIN ) GPIO_SetDirection( PORT, PIN, 1 ) +#define SET_IO_OUTPUT( PORT, PIN ) GPIO_SetDirection( PORT, PIN, 0 ) + +#define LCD_NRST_HIGH() GPIO_WriteBit(LCD_NRST_GPIO, LCD_NRST_GPIO_PIN, Bit_SET) +#define LCD_NRST_LOW() GPIO_WriteBit(LCD_NRST_GPIO, LCD_NRST_GPIO_PIN, Bit_RESET) + +#define LCD_CS_HIGH() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_CS_GPIO_PIN, Bit_SET) +#define LCD_CS_LOW() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_CS_GPIO_PIN, Bit_RESET) + +#define LCD_SCK_HIGH() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_SCK_GPIO_PIN, Bit_SET) +#define LCD_SCK_LOW() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_SCK_GPIO_PIN, Bit_RESET) + +#define LCD_MOSI_HIGH() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN, Bit_SET) +#define LCD_MOSI_LOW() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN, Bit_RESET) + +#define SET_LCD_CS() GPIO_WriteBit(PORT_LCD_CS, LCD_CS_PIN, Bit_SET) +#define CLR_LCD_CS() GPIO_WriteBit(PORT_LCD_CS, LCD_CS_PIN, Bit_RESET) +#define SET_LCD_CS_OUTPUT() SET_IO_OUTPUT( PORT_LCD_CS, PIN_LCD_CS ) + +#define SET_LCD_CLK() GPIO_WriteBit( PORT_LCD_CLK, LCD_CLK_PIN, Bit_SET ) +#define CLR_LCD_CLK() GPIO_WriteBit( PORT_LCD_CLK, LCD_CLK_PIN, Bit_RESET ) +#define SET_LCD_CLK_OUTPUT() SET_IO_OUTPUT( PORT_LCD_CLK, PIN_LCD_CLK ) + +#define SET_LCD_DATA() GPIO_WriteBit( PORT_LCD_MOSI, LCD_MOSI_PIN, Bit_SET ) +#define CLR_LCD_DATA() GPIO_WriteBit( PORT_LCD_MOSI, LCD_MOSI_PIN, Bit_RESET ) +#define SET_LCD_DATA_INPUT() SET_IO_INPUT( PORT_LCD_MOSI, PIN_LCD_MOSI ) +#define SET_LCD_DATA_OUTPUT() SET_IO_OUTPUT( PORT_LCD_MOSI, PIN_LCD_MOSI ) + +#define READ_LCD_DATA_PIN() GPIO_ReadInputDataBit(PORT_LCD_MOSI, LCD_MOSI_PIN) + + + +#if 1 +#define HORIZONTAL_SYNC_WIDTH ( 4 ) +#define HORIZONTAL_BACK_PORCH ( 24 ) +#define HORIZONTAL_FRONT_PORCH ( 140 - HORIZONTAL_BACK_PORCH ) +#define VERTICAL_SYNC_HEIGHT ( 2 ) +#define VERTICAL_BACK_PORCH ( 10 ) +#define VERTICAL_FRONT_PORCH ( 22 - VERTICAL_BACK_PORCH ) +#else +#define HORIZONTAL_SYNC_WIDTH ( 4 ) +#define HORIZONTAL_BACK_PORCH ( 20 ) +#define HORIZONTAL_FRONT_PORCH ( 60 - HORIZONTAL_BACK_PORCH ) +#define VERTICAL_SYNC_HEIGHT ( 2 ) +#define VERTICAL_BACK_PORCH ( 6 ) +#define VERTICAL_FRONT_PORCH ( 14 - VERTICAL_BACK_PORCH ) +#endif + + + +#endif + + + + diff --git a/radio/src/targets/pl18/libopenui_config.h b/radio/src/targets/pl18/libopenui_config.h new file mode 100644 index 00000000000..4c0e71b2e0e --- /dev/null +++ b/radio/src/targets/pl18/libopenui_config.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 + +constexpr uint32_t ALERT_FRAME_TOP = 70; +constexpr uint32_t ALERT_FRAME_HEIGHT = (LCD_H - 2 * ALERT_FRAME_TOP); +constexpr uint32_t ALERT_BITMAP_TOP = ALERT_FRAME_TOP + 15; +constexpr uint32_t ALERT_BITMAP_LEFT = 15; +constexpr uint32_t ALERT_TITLE_TOP = ALERT_FRAME_TOP + 10; +constexpr uint32_t ALERT_TITLE_LEFT = 186; +constexpr uint32_t ALERT_MESSAGE_TOP = ALERT_TITLE_TOP + 90; +constexpr uint32_t ALERT_MESSAGE_LEFT = ALERT_TITLE_LEFT; + +constexpr coord_t INPUT_EDIT_CURVE_WIDTH = 132; +constexpr coord_t INPUT_EDIT_CURVE_HEIGHT = INPUT_EDIT_CURVE_WIDTH; +constexpr coord_t MENUS_LINE_HEIGHT = 30; +constexpr coord_t MENUS_WIDTH = 200; +constexpr coord_t MENUS_MAX_HEIGHT = LCD_H * 0.9; + +constexpr rect_t MENUS_TOOLBAR_RECT = { 100, 51, 30, 209 }; + +// Disable rotary encoder, as the PL18 does not have one +#define ROTARY_ENCODER_SPEED() 0 diff --git a/radio/src/targets/pl18/sdram_driver.c b/radio/src/targets/pl18/sdram_driver.c new file mode 100644 index 00000000000..2cc88ae84a5 --- /dev/null +++ b/radio/src/targets/pl18/sdram_driver.c @@ -0,0 +1,257 @@ +/* + * Copyright (C) OpenTX + * + * 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 "board.h" +#include "stm32f4xx_fmc.h" + +#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000) +#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001) +#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002) +#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004) +#define SDRAM_MODEREG_BURST_FULL_PAGE ((uint16_t)0x0007) +#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000) +#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008) +#define SDRAM_MODEREG_CAS_LATENCY_1 ((uint16_t)0x0010) +#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020) +#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030) +#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000) +#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000) +#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200) + +void SDRAM_GPIOConfig(void) +{ + /* + ------------------------------------------------------------------------------------------------------------------------------------------------ + PC3 <-> FMC_SDCKE0 | PD0 <-> FMC_D2 | PE0 <-> FMC_NBL0 | PF0 <-> FMC_A0 | PG0 <-> FMC_A10 | PH3 <-> FMC_SDNE0 | PI0 <-> FMC_D24 + | PD1 <-> FMC_D3 | PE1 <-> FMC_NBL1 | PF1 <-> FMC_A1 | PG1 <-> FMC_A11 | PH5 <-> FMC_SDNWE | PI1 <-> FMC_D25 + | PD8 <-> FMC_D13 | PE7 <-> FMC_D4 | PF2 <-> FMC_A2 | PG4 <-> FMC_BA0 | PH8 <-> FMC_D16 | PI2 <-> FMC_D26 + | PD9 <-> FMC_D14 | PE8 <-> FMC_D5 | PF3 <-> FMC_A3 | PG5 <-> FMC_BA1 | PH9 <-> FMC_D17 | PI3 <-> FMC_D27 + | PD10 <-> FMC_D15 | PE9 <-> FMC_D6 | PF4 <-> FMC_A4 | PG8 <-> FMC_SDCLK | PH10 <-> FMC_D18 | PI4 <-> FMC_NBL2 + | PD14 <-> FMC_D0 | PE10 <-> FMC_D7 | PF5 <-> FMC_A5 | PG15 <-> FMC_NCAS | PH11 <-> FMC_D19 | PI5 <-> FMC_NBL3 + | PD15 <-> FMC_D1 | PE11 <-> FMC_D8 | PF11 <-> FMC_NRAS | | PH12 <-> FMC_D20 | PI6 <-> FMC_D28 + | | PE12 <-> FMC_D9 | PF12 <-> FMC_A6 | | PH13 <-> FMC_D21 | PI7 <-> FMC_D29 + | | PE13 <-> FMC_D10 | PF13 <-> FMC_A7 | | PH14 <-> FMC_D22 | PI9 <-> FMC_D30 + | | PE14 <-> FMC_D11 | PF14 <-> FMC_A8 | | PH15 <-> FMC_D23 | PI10 <-> FMC_D31 + | | PE15 <-> FMC_D12 | PF15 <-> FMC_A9 | | | + */ + + GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; + GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; + GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; + + /* GPIOC configuration */ + GPIO_PinAFConfig(GPIOC, GPIO_PinSource3 , GPIO_AF_FMC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ; + GPIO_Init(GPIOC, &GPIO_InitStructure); + + /* GPIOD configuration */ + GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource10, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_FMC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 | GPIO_Pin_15; + GPIO_Init(GPIOD, &GPIO_InitStructure); + + /* GPIOE configuration */ + GPIO_PinAFConfig(GPIOE, GPIO_PinSource0, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource1, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource7, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource8, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource9, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource10, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource11, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource12, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource13, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource14, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOE, GPIO_PinSource15, GPIO_AF_FMC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; + GPIO_Init(GPIOE, &GPIO_InitStructure); + + /* GPIOF configuration */ + GPIO_PinAFConfig(GPIOF, GPIO_PinSource0, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource1, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource2, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource3, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource4, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource5, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource11, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource12, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource13, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource14, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOF, GPIO_PinSource15, GPIO_AF_FMC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; + GPIO_Init(GPIOF, &GPIO_InitStructure); + + /* GPIOG configuration */ + GPIO_PinAFConfig(GPIOG, GPIO_PinSource0 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource1 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource4 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource5 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource8 , GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOG, GPIO_PinSource15 , GPIO_AF_FMC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_8 | GPIO_Pin_15; + GPIO_Init(GPIOG, &GPIO_InitStructure); + + /* GPIOH configuration */ + GPIO_PinAFConfig(GPIOH, GPIO_PinSource3, GPIO_AF_FMC); + GPIO_PinAFConfig(GPIOH, GPIO_PinSource5, GPIO_AF_FMC); + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5; + GPIO_Init(GPIOH, &GPIO_InitStructure); +} + +void SDRAM_InitSequence(void) +{ + FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStructure; + uint32_t tmpr = 0; + + /* Step 3 --------------------------------------------------------------------*/ + /* Configure a clock configuration enable command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) { + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Step 4 --------------------------------------------------------------------*/ + /* Insert 100 ms delay */ + delay_ms(100); + + /* Step 5 --------------------------------------------------------------------*/ + /* Configure a PALL (precharge all) command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) { + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Step 6 --------------------------------------------------------------------*/ + /* Configure a Auto-Refresh command */ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 4; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) { + } + /* Send the first command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) { + } + /* Send the second command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Step 7 --------------------------------------------------------------------*/ + /* Program the external memory mode register */ + tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 | + SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | + SDRAM_MODEREG_CAS_LATENCY_3 | + SDRAM_MODEREG_OPERATING_MODE_STANDARD | + SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; + + /* Configure a load Mode register command*/ + FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode; + FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_Command_Target_bank1; + FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1; + FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr; + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) { + } + /* Send the command */ + FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure); + + /* Step 8 --------------------------------------------------------------------*/ + /* Set the refresh rate counter */ + /* (15.62 us x Freq) - 20 */ + /* Set the device refresh counter */ + FMC_SetRefreshCount(683);//904 + /* Wait until the SDRAM controller is ready */ + while(FMC_GetFlagStatus(FMC_Bank1_SDRAM, FMC_FLAG_Busy) != RESET) { + } +} + + +void SDRAM_Init(void) +{ + //delay funcion needed + delaysInit(); + // Clocks must be enabled here, because the sdramInit is called before main + RCC_AHB1PeriphClockCmd(SDRAM_RCC_AHB1Periph, ENABLE); + RCC_AHB3PeriphClockCmd(SDRAM_RCC_AHB3Periph, ENABLE); + + /* GPIO configuration for FMC SDRAM bank */ + SDRAM_GPIOConfig(); + + /* FMC Configuration ---------------------------------------------------------*/ + FMC_SDRAMInitTypeDef FMC_SDRAMInitStructure; + FMC_SDRAMTimingInitTypeDef FMC_SDRAMTimingInitStructure; + + /* FMC SDRAM Bank configuration */ + /* Timing configuration for 90 Mhz of SD clock frequency (168Mhz/2) */ + /* TMRD: 2 Clock cycles */ + FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2; + /* TXSR: min=70ns (7x11.11ns) */ + FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7; + /* TRAS: min=42ns (4x11.11ns) max=120k (ns) */ + FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4; + /* TRC: min=70 (7x11.11ns) */ + FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 7; + /* TWR: min=1+ 7ns (1+1x11.11ns) */ + FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2; + /* TRP: 20ns => 2x11.11ns */ + FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2; + /* TRCD: 20ns => 2x11.11ns */ + FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2; + + /* FMC SDRAM control configuration */ + FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank1_SDRAM; + /* Row addressing: [7:0] */ + FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b; + /* Column addressing: [11:0] */ + FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_12b; + FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = FMC_SDMemory_Width_16b; + FMC_SDRAMInitStructure.FMC_InternalBankNumber = FMC_InternalBank_Number_4; + FMC_SDRAMInitStructure.FMC_CASLatency = FMC_CAS_Latency_3; + FMC_SDRAMInitStructure.FMC_WriteProtection = FMC_Write_Protection_Disable; + FMC_SDRAMInitStructure.FMC_SDClockPeriod = FMC_SDClock_Period_2; + FMC_SDRAMInitStructure.FMC_ReadBurst = FMC_Read_Burst_Enable; + FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_1; + FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct = &FMC_SDRAMTimingInitStructure; + + /* FMC SDRAM bank initialization */ + FMC_SDRAMInit(&FMC_SDRAMInitStructure); + + /* FMC SDRAM device initialization sequence */ + SDRAM_InitSequence(); +} diff --git a/radio/src/targets/pl18/startup_stm32f42_43xxx.s b/radio/src/targets/pl18/startup_stm32f42_43xxx.s new file mode 100644 index 00000000000..68671dc34cf --- /dev/null +++ b/radio/src/targets/pl18/startup_stm32f42_43xxx.s @@ -0,0 +1,565 @@ +/** + ****************************************************************************** + * @file startup_stm32f40_41xxx.s + * @author MCD Application Team + * @version V1.3.0 + * @date 08-November-2013 + * @brief STM32F40xxx/41xxx Devices vector table for RIDE7 toolchain. + * This module performs: + * - Set the initial SP + * - Set the initial PC == Reset_Handler, + * - Set the vector table entries with the exceptions ISR address + * - Configure the clock system and the external SRAM mounted on + * STM324xG-EVAL board to be used as data memory (optional, + * to be enabled by user) + * - Branches to main in the C library (which eventually + * calls main()). + * After Reset the Cortex-M4 processor is in Thread mode, + * priority is Privileged, and the Stack is set to Main. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT 2013 STMicroelectronics

+ * + * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.st.com/software_license_agreement_liberty_v2 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ****************************************************************************** + */ + + .syntax unified + .cpu cortex-m3 + .fpu softvfp + .thumb + +.global g_pfnVectors +.global Default_Handler + +/* start address for the initialization values of the .data section. +defined in linker script */ +.word _sidata +/* start address for the .data section. defined in linker script */ +.word _sdata +/* end address for the .data section. defined in linker script */ +.word _edata +/* start address for the .bss section. defined in linker script */ +.word _sbss +/* end address for the .bss section. defined in linker script */ +.word _ebss +/* stack used for SystemInit_ExtMemCtl; always internal RAM used */ + +/** + * @brief This is the code that gets called when the processor first + * starts execution following a reset event. Only the absolutely + * necessary set is performed, after which the application + * supplied main() routine is called. + * @param None + * @retval : None +*/ + + .section .text.Reset_Handler + .weak Reset_Handler + .type Reset_Handler, %function +Reset_Handler: + + bl pwrResetHandler /*jump to WDT reset handler where soft power control pin is turned on as soon as possible */ + +/* Copy the data segment initializers from flash to SRAM */ + movs r1, #0 + b LoopCopyDataInit + +CopyDataInit: + ldr r3, =_sidata + ldr r3, [r3, r1] + str r3, [r0, r1] + adds r1, r1, #4 + +LoopCopyDataInit: + ldr r0, =_sdata + ldr r3, =_edata + adds r2, r0, r1 + cmp r2, r3 + bcc CopyDataInit + ldr r2, =_sbss + b LoopFillZerobss +/* Zero fill the bss segment. */ +FillZerobss: + movs r3, #0 + str r3, [r2], #4 + +LoopFillZerobss: + ldr r3, = _ebss + cmp r2, r3 + bcc FillZerobss + +/*Paint Main Stack */ + ldr r2, = _main_stack_start +PaintMainStack: + movs r3, #0x55555555 + str r3, [r2], #4 +LoopPaintMainStack: + ldr r3, = _estack + cmp r2, r3 + bcc PaintMainStack + +/* Call the clock system intitialization function.*/ + bl SystemInit +/* Call C++ constructors for static objects */ + bl __libc_init_array +/* Call the application's entry point.*/ + bl main + bx lr +.size Reset_Handler, .-Reset_Handler + +/** + * @brief This is the code that gets called when the processor receives an + * unexpected interrupt. This simply enters an infinite loop, preserving + * the system state for examination by a debugger. + * @param None + * @retval None +*/ + .section .text.Default_Handler,"ax",%progbits +Default_Handler: +Infinite_Loop: + b Infinite_Loop + .size Default_Handler, .-Default_Handler +/****************************************************************************** +* +* The minimal vector table for a Cortex M3. Note that the proper constructs +* must be placed on this to ensure that it ends up at physical address +* 0x0000.0000. +* +*******************************************************************************/ + .section .isr_vector,"a",%progbits + .type g_pfnVectors, %object + .size g_pfnVectors, .-g_pfnVectors + + +g_pfnVectors: + .word _estack + .word Reset_Handler + .word NMI_Handler + .word HardFault_Handler + .word MemManage_Handler + .word BusFault_Handler + .word UsageFault_Handler + .word 0 + .word 0 + .word 0 + .word 0 + .word SVC_Handler + .word DebugMon_Handler + .word 0 + .word PendSV_Handler + .word SysTick_Handler + + /* External Interrupts */ + .word WWDG_IRQHandler /* Window WatchDog */ + .word PVD_IRQHandler /* PVD through EXTI Line detection */ + .word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */ + .word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */ + .word FLASH_IRQHandler /* FLASH */ + .word RCC_IRQHandler /* RCC */ + .word EXTI0_IRQHandler /* EXTI Line0 */ + .word EXTI1_IRQHandler /* EXTI Line1 */ + .word EXTI2_IRQHandler /* EXTI Line2 */ + .word EXTI3_IRQHandler /* EXTI Line3 */ + .word EXTI4_IRQHandler /* EXTI Line4 */ + .word DMA1_Stream0_IRQHandler /* DMA1 Stream 0 */ + .word DMA1_Stream1_IRQHandler /* DMA1 Stream 1 */ + .word DMA1_Stream2_IRQHandler /* DMA1 Stream 2 */ + .word DMA1_Stream3_IRQHandler /* DMA1 Stream 3 */ + .word DMA1_Stream4_IRQHandler /* DMA1 Stream 4 */ + .word DMA1_Stream5_IRQHandler /* DMA1 Stream 5 */ + .word DMA1_Stream6_IRQHandler /* DMA1 Stream 6 */ + .word ADC_IRQHandler /* ADC1, ADC2 and ADC3s */ + .word CAN1_TX_IRQHandler /* CAN1 TX */ + .word CAN1_RX0_IRQHandler /* CAN1 RX0 */ + .word CAN1_RX1_IRQHandler /* CAN1 RX1 */ + .word CAN1_SCE_IRQHandler /* CAN1 SCE */ + .word EXTI9_5_IRQHandler /* External Line[9:5]s */ + .word TIM1_BRK_TIM9_IRQHandler /* TIM1 Break and TIM9 */ + .word TIM1_UP_TIM10_IRQHandler /* TIM1 Update and TIM10 */ + .word TIM1_TRG_COM_TIM11_IRQHandler /* TIM1 Trigger and Commutation and TIM11 */ + .word TIM1_CC_IRQHandler /* TIM1 Capture Compare */ + .word TIM2_IRQHandler /* TIM2 */ + .word TIM3_IRQHandler /* TIM3 */ + .word TIM4_IRQHandler /* TIM4 */ + .word I2C1_EV_IRQHandler /* I2C1 Event */ + .word I2C1_ER_IRQHandler /* I2C1 Error */ + .word I2C2_EV_IRQHandler /* I2C2 Event */ + .word I2C2_ER_IRQHandler /* I2C2 Error */ + .word SPI1_IRQHandler /* SPI1 */ + .word SPI2_IRQHandler /* SPI2 */ + .word USART1_IRQHandler /* USART1 */ + .word USART2_IRQHandler /* USART2 */ + .word USART3_IRQHandler /* USART3 */ + .word EXTI15_10_IRQHandler /* External Line[15:10]s */ + .word RTC_Alarm_IRQHandler /* RTC Alarm (A and B) through EXTI Line */ + .word OTG_FS_WKUP_IRQHandler /* USB OTG FS Wakeup through EXTI line */ + .word TIM8_BRK_TIM12_IRQHandler /* TIM8 Break and TIM12 */ + .word TIM8_UP_TIM13_IRQHandler /* TIM8 Update and TIM13 */ + .word TIM8_TRG_COM_TIM14_IRQHandler /* TIM8 Trigger and Commutation and TIM14 */ + .word TIM8_CC_IRQHandler /* TIM8 Capture Compare */ + .word DMA1_Stream7_IRQHandler /* DMA1 Stream7 */ + .word FSMC_IRQHandler /* FSMC */ + .word SDIO_IRQHandler /* SDIO */ + .word TIM5_IRQHandler /* TIM5 */ + .word SPI3_IRQHandler /* SPI3 */ + .word UART4_IRQHandler /* UART4 */ + .word UART5_IRQHandler /* UART5 */ + .word TIM6_DAC_IRQHandler /* TIM6 and DAC1&2 underrun errors */ + .word TIM7_IRQHandler /* TIM7 */ + .word DMA2_Stream0_IRQHandler /* DMA2 Stream 0 */ + .word DMA2_Stream1_IRQHandler /* DMA2 Stream 1 */ + .word DMA2_Stream2_IRQHandler /* DMA2 Stream 2 */ + .word DMA2_Stream3_IRQHandler /* DMA2 Stream 3 */ + .word DMA2_Stream4_IRQHandler /* DMA2 Stream 4 */ + .word ETH_IRQHandler /* Ethernet */ + .word ETH_WKUP_IRQHandler /* Ethernet Wakeup through EXTI line */ + .word CAN2_TX_IRQHandler /* CAN2 TX */ + .word CAN2_RX0_IRQHandler /* CAN2 RX0 */ + .word CAN2_RX1_IRQHandler /* CAN2 RX1 */ + .word CAN2_SCE_IRQHandler /* CAN2 SCE */ + .word OTG_FS_IRQHandler /* USB OTG FS */ + .word DMA2_Stream5_IRQHandler /* DMA2 Stream 5 */ + .word DMA2_Stream6_IRQHandler /* DMA2 Stream 6 */ + .word DMA2_Stream7_IRQHandler /* DMA2 Stream 7 */ + .word USART6_IRQHandler /* USART6 */ + .word I2C3_EV_IRQHandler /* I2C3 event */ + .word I2C3_ER_IRQHandler /* I2C3 error */ + .word OTG_HS_EP1_OUT_IRQHandler /* USB OTG HS End Point 1 Out */ + .word OTG_HS_EP1_IN_IRQHandler /* USB OTG HS End Point 1 In */ + .word OTG_HS_WKUP_IRQHandler /* USB OTG HS Wakeup through EXTI */ + .word OTG_HS_IRQHandler /* USB OTG HS */ + .word DCMI_IRQHandler /* DCMI */ + .word CRYP_IRQHandler /* CRYP crypto */ + .word HASH_RNG_IRQHandler /* Hash and Rng */ + .word FPU_IRQHandler /* FPU */ + .word UART7_IRQHandler /* UART7 */ + .word UART8_IRQHandler /* UART8 */ + .word SPI4_IRQHandler /* SPI4 */ + .word SPI5_IRQHandler /* SPI5 */ + .word SPI6_IRQHandler /* SPI6 */ + .word SAI1_IRQHandler /* SAI1 */ + .word LTDC_IRQHandler /* LTDC */ + .word LTDC_ER_IRQHandler /* LTDC error */ + .word DMA2D_IRQHandler /* DMA2D */ + +/******************************************************************************* +* +* Provide weak aliases for each Exception handler to the Default_Handler. +* As they are weak aliases, any function with the same name will override +* this definition. +* +*******************************************************************************/ + .weak NMI_Handler + .thumb_set NMI_Handler,Default_Handler + + .weak HardFault_Handler + .thumb_set HardFault_Handler,Default_Handler + + .weak MemManage_Handler + .thumb_set MemManage_Handler,Default_Handler + + .weak BusFault_Handler + .thumb_set BusFault_Handler,Default_Handler + + .weak UsageFault_Handler + .thumb_set UsageFault_Handler,Default_Handler + + .weak SVC_Handler + .thumb_set SVC_Handler,Default_Handler + + .weak DebugMon_Handler + .thumb_set DebugMon_Handler,Default_Handler + + .weak PendSV_Handler + .thumb_set PendSV_Handler,Default_Handler + + .weak SysTick_Handler + .thumb_set SysTick_Handler,Default_Handler + + .weak WWDG_IRQHandler + .thumb_set WWDG_IRQHandler,Default_Handler + + .weak PVD_IRQHandler + .thumb_set PVD_IRQHandler,Default_Handler + + .weak TAMP_STAMP_IRQHandler + .thumb_set TAMP_STAMP_IRQHandler,Default_Handler + + .weak RTC_WKUP_IRQHandler + .thumb_set RTC_WKUP_IRQHandler,Default_Handler + + .weak FLASH_IRQHandler + .thumb_set FLASH_IRQHandler,Default_Handler + + .weak RCC_IRQHandler + .thumb_set RCC_IRQHandler,Default_Handler + + .weak EXTI0_IRQHandler + .thumb_set EXTI0_IRQHandler,Default_Handler + + .weak EXTI1_IRQHandler + .thumb_set EXTI1_IRQHandler,Default_Handler + + .weak EXTI2_IRQHandler + .thumb_set EXTI2_IRQHandler,Default_Handler + + .weak EXTI3_IRQHandler + .thumb_set EXTI3_IRQHandler,Default_Handler + + .weak EXTI4_IRQHandler + .thumb_set EXTI4_IRQHandler,Default_Handler + + .weak DMA1_Stream0_IRQHandler + .thumb_set DMA1_Stream0_IRQHandler,Default_Handler + + .weak DMA1_Stream1_IRQHandler + .thumb_set DMA1_Stream1_IRQHandler,Default_Handler + + .weak DMA1_Stream2_IRQHandler + .thumb_set DMA1_Stream2_IRQHandler,Default_Handler + + .weak DMA1_Stream3_IRQHandler + .thumb_set DMA1_Stream3_IRQHandler,Default_Handler + + .weak DMA1_Stream4_IRQHandler + .thumb_set DMA1_Stream4_IRQHandler,Default_Handler + + .weak DMA1_Stream5_IRQHandler + .thumb_set DMA1_Stream5_IRQHandler,Default_Handler + + .weak DMA1_Stream6_IRQHandler + .thumb_set DMA1_Stream6_IRQHandler,Default_Handler + + .weak ADC_IRQHandler + .thumb_set ADC_IRQHandler,Default_Handler + + .weak CAN1_TX_IRQHandler + .thumb_set CAN1_TX_IRQHandler,Default_Handler + + .weak CAN1_RX0_IRQHandler + .thumb_set CAN1_RX0_IRQHandler,Default_Handler + + .weak CAN1_RX1_IRQHandler + .thumb_set CAN1_RX1_IRQHandler,Default_Handler + + .weak CAN1_SCE_IRQHandler + .thumb_set CAN1_SCE_IRQHandler,Default_Handler + + .weak EXTI9_5_IRQHandler + .thumb_set EXTI9_5_IRQHandler,Default_Handler + + .weak TIM1_BRK_TIM9_IRQHandler + .thumb_set TIM1_BRK_TIM9_IRQHandler,Default_Handler + + .weak TIM1_UP_TIM10_IRQHandler + .thumb_set TIM1_UP_TIM10_IRQHandler,Default_Handler + + .weak TIM1_TRG_COM_TIM11_IRQHandler + .thumb_set TIM1_TRG_COM_TIM11_IRQHandler,Default_Handler + + .weak TIM1_CC_IRQHandler + .thumb_set TIM1_CC_IRQHandler,Default_Handler + + .weak TIM2_IRQHandler + .thumb_set TIM2_IRQHandler,Default_Handler + + .weak TIM3_IRQHandler + .thumb_set TIM3_IRQHandler,Default_Handler + + .weak TIM4_IRQHandler + .thumb_set TIM4_IRQHandler,Default_Handler + + .weak I2C1_EV_IRQHandler + .thumb_set I2C1_EV_IRQHandler,Default_Handler + + .weak I2C1_ER_IRQHandler + .thumb_set I2C1_ER_IRQHandler,Default_Handler + + .weak I2C2_EV_IRQHandler + .thumb_set I2C2_EV_IRQHandler,Default_Handler + + .weak I2C2_ER_IRQHandler + .thumb_set I2C2_ER_IRQHandler,Default_Handler + + .weak SPI1_IRQHandler + .thumb_set SPI1_IRQHandler,Default_Handler + + .weak SPI2_IRQHandler + .thumb_set SPI2_IRQHandler,Default_Handler + + .weak USART1_IRQHandler + .thumb_set USART1_IRQHandler,Default_Handler + + .weak USART2_IRQHandler + .thumb_set USART2_IRQHandler,Default_Handler + + .weak USART3_IRQHandler + .thumb_set USART3_IRQHandler,Default_Handler + + .weak EXTI15_10_IRQHandler + .thumb_set EXTI15_10_IRQHandler,Default_Handler + + .weak RTC_Alarm_IRQHandler + .thumb_set RTC_Alarm_IRQHandler,Default_Handler + + .weak OTG_FS_WKUP_IRQHandler + .thumb_set OTG_FS_WKUP_IRQHandler,Default_Handler + + .weak TIM8_BRK_TIM12_IRQHandler + .thumb_set TIM8_BRK_TIM12_IRQHandler,Default_Handler + + .weak TIM8_UP_TIM13_IRQHandler + .thumb_set TIM8_UP_TIM13_IRQHandler,Default_Handler + + .weak TIM8_TRG_COM_TIM14_IRQHandler + .thumb_set TIM8_TRG_COM_TIM14_IRQHandler,Default_Handler + + .weak TIM8_CC_IRQHandler + .thumb_set TIM8_CC_IRQHandler,Default_Handler + + .weak DMA1_Stream7_IRQHandler + .thumb_set DMA1_Stream7_IRQHandler,Default_Handler + + .weak FSMC_IRQHandler + .thumb_set FSMC_IRQHandler,Default_Handler + + .weak SDIO_IRQHandler + .thumb_set SDIO_IRQHandler,Default_Handler + + .weak TIM5_IRQHandler + .thumb_set TIM5_IRQHandler,Default_Handler + + .weak SPI3_IRQHandler + .thumb_set SPI3_IRQHandler,Default_Handler + + .weak UART4_IRQHandler + .thumb_set UART4_IRQHandler,Default_Handler + + .weak UART5_IRQHandler + .thumb_set UART5_IRQHandler,Default_Handler + + .weak TIM6_DAC_IRQHandler + .thumb_set TIM6_DAC_IRQHandler,Default_Handler + + .weak TIM7_IRQHandler + .thumb_set TIM7_IRQHandler,Default_Handler + + .weak DMA2_Stream0_IRQHandler + .thumb_set DMA2_Stream0_IRQHandler,Default_Handler + + .weak DMA2_Stream1_IRQHandler + .thumb_set DMA2_Stream1_IRQHandler,Default_Handler + + .weak DMA2_Stream2_IRQHandler + .thumb_set DMA2_Stream2_IRQHandler,Default_Handler + + .weak DMA2_Stream3_IRQHandler + .thumb_set DMA2_Stream3_IRQHandler,Default_Handler + + .weak DMA2_Stream4_IRQHandler + .thumb_set DMA2_Stream4_IRQHandler,Default_Handler + + .weak ETH_IRQHandler + .thumb_set ETH_IRQHandler,Default_Handler + + .weak ETH_WKUP_IRQHandler + .thumb_set ETH_WKUP_IRQHandler,Default_Handler + + .weak CAN2_TX_IRQHandler + .thumb_set CAN2_TX_IRQHandler,Default_Handler + + .weak CAN2_RX0_IRQHandler + .thumb_set CAN2_RX0_IRQHandler,Default_Handler + + .weak CAN2_RX1_IRQHandler + .thumb_set CAN2_RX1_IRQHandler,Default_Handler + + .weak CAN2_SCE_IRQHandler + .thumb_set CAN2_SCE_IRQHandler,Default_Handler + + .weak OTG_FS_IRQHandler + .thumb_set OTG_FS_IRQHandler,Default_Handler + + .weak DMA2_Stream5_IRQHandler + .thumb_set DMA2_Stream5_IRQHandler,Default_Handler + + .weak DMA2_Stream6_IRQHandler + .thumb_set DMA2_Stream6_IRQHandler,Default_Handler + + .weak DMA2_Stream7_IRQHandler + .thumb_set DMA2_Stream7_IRQHandler,Default_Handler + + .weak USART6_IRQHandler + .thumb_set USART6_IRQHandler,Default_Handler + + .weak I2C3_EV_IRQHandler + .thumb_set I2C3_EV_IRQHandler,Default_Handler + + .weak I2C3_ER_IRQHandler + .thumb_set I2C3_ER_IRQHandler,Default_Handler + + .weak OTG_HS_EP1_OUT_IRQHandler + .thumb_set OTG_HS_EP1_OUT_IRQHandler,Default_Handler + + .weak OTG_HS_EP1_IN_IRQHandler + .thumb_set OTG_HS_EP1_IN_IRQHandler,Default_Handler + + .weak OTG_HS_WKUP_IRQHandler + .thumb_set OTG_HS_WKUP_IRQHandler,Default_Handler + + .weak OTG_HS_IRQHandler + .thumb_set OTG_HS_IRQHandler,Default_Handler + + .weak DCMI_IRQHandler + .thumb_set DCMI_IRQHandler,Default_Handler + + .weak CRYP_IRQHandler + .thumb_set CRYP_IRQHandler,Default_Handler + + .weak HASH_RNG_IRQHandler + .thumb_set HASH_RNG_IRQHandler,Default_Handler + + .weak FPU_IRQHandler + .thumb_set FPU_IRQHandler,Default_Handler + + .weak UART7_IRQHandler + .thumb_set UART7_IRQHandler,Default_Handler + + .weak UART8_IRQHandler + .thumb_set UART8_IRQHandler,Default_Handler + + .weak SPI4_IRQHandler + .thumb_set SPI4_IRQHandler,Default_Handler + + .weak SPI5_IRQHandler + .thumb_set SPI5_IRQHandler,Default_Handler + + .weak SPI6_IRQHandler + .thumb_set SPI6_IRQHandler,Default_Handler + + .weak SAI1_IRQHandler + .thumb_set SAI1_IRQHandler,Default_Handler + + .weak LTDC_IRQHandler + .thumb_set LTDC_IRQHandler,Default_Handler + + .weak LTDC_ER_IRQHandler + .thumb_set LTDC_ER_IRQHandler,Default_Handler + + .weak DMA2D_IRQHandler + .thumb_set DMA2D_IRQHandler,Default_Handler + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/pl18/stm32_ramboot.ld b/radio/src/targets/pl18/stm32_ramboot.ld new file mode 100644 index 00000000000..b15c1aa04c4 --- /dev/null +++ b/radio/src/targets/pl18/stm32_ramboot.ld @@ -0,0 +1,188 @@ +/* +***************************************************************************** +** +** File : stm32f4_flash.ld +** +** Abstract : Linker script for STM32F439 Device with +** 2MByte FLASH, 192KByte SRAM, 64KByte CCM +** +** Set heap size, stack size and stack location according +** to application requirements. +** +** Set memory bank area and size if external memory is used. +** +** Target : STMicroelectronics STM32 +** +***************************************************************************** +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_estack = 0x2002FFF0; /* end of 192K RAM */ +_heap_end = 0xC0800000; /* end of 8192K SDRAM */ + +/* Generate a link error if heap and stack don't fit into RAM */ +_Min_Heap_Size = 0; /* required amount of heap */ +_Main_Stack_Size = 1024; /* required amount of stack for interrupt stack (Main stack) */ + +/* Main stack end */ +_main_stack_start = _estack - _Main_Stack_Size; + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K + CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 65520 + NO_INIT (xrw) : ORIGIN = 0x1000FFF0, LENGTH = 16 + MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K + SDRAM(xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + + /* The program code and other data goes into FLASH */ + .text : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + KEEP(*(.version)) + KEEP(*(.bootversiondata)) + . = ALIGN(4); /* Align the start of the text part */ + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + .ARM.extab : + { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array*)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = .; + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >CCM + + /* Uninitialized data section */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + /* __bss_start__ = _sbss; */ + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + } >CCM + + /* Non-zeroed data section */ + . = ALIGN(4); + .noinit (NOLOAD) : + { + *(.noinit) + } >NO_INIT + + + /* collect all uninitialized .ccm sections */ + .ram (NOLOAD) : + { + . = ALIGN(4); + _sram = .; + *(.ram) + } >RAM + + /* User_heap_stack section, used to check that there is enough RAM left */ + ._user_heap_stack : + { + . = ALIGN(4); + . = . + _Min_Heap_Size; + . = . + _Main_Stack_Size; + . = ALIGN(4); + } >RAM + + /* MEMORY_bank1 section, code must be located here explicitly */ + /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ + .memory_b1_text : + { + *(.mb1text) /* .mb1text sections (code) */ + *(.mb1text*) /* .mb1text* sections (code) */ + *(.mb1rodata) /* read-only data (constants) */ + *(.mb1rodata*) + } >MEMORY_B1 + + .sdram (NOLOAD) : + { + *(.sdram) + *(.sdram*) + *(.sdram_rodata) + *(.sdram_rodata*) + . = ALIGN(4); + _eram = .; + } >SDRAM + + PROVIDE ( end = _eram ); + PROVIDE ( _end = _eram ); + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/radio/src/targets/pl18/stm32f4_flash.ld b/radio/src/targets/pl18/stm32f4_flash.ld new file mode 100644 index 00000000000..ac5c7ef74d5 --- /dev/null +++ b/radio/src/targets/pl18/stm32f4_flash.ld @@ -0,0 +1,197 @@ +/* +***************************************************************************** +** +** File : stm32f4_flash.ld +** +** Abstract : Linker script for STM32F439 Device with +** 2MByte FLASH, 192KByte SRAM, 64KByte CCM +** +** Set heap size, stack size and stack location according +** to application requirements. +** +** Set memory bank area and size if external memory is used. +** +** Target : STMicroelectronics STM32 +** +***************************************************************************** +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_heap_end = 0xC0800000; /* end of 8192K SDRAM */ + +/* Generate a link error if heap and stack don't fit into RAM */ +_Min_Heap_Size = 0; /* required amount of heap */ +_Main_Stack_Size = 1024; /* required amount of stack for interrupt stack (Main stack) */ + +/* Main stack end */ +_main_stack_start = _estack - _Main_Stack_Size; + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K + CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 65520 + NO_INIT (xrw) : ORIGIN = 0x1000FFF0, LENGTH = 16 + MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K + SDRAM(xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K +} + +/* Main stack end */ +_estack = 0x10000000 + LENGTH(CCM); +_main_stack_start = _estack - _Main_Stack_Size; + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + + /* The program code and other data goes into FLASH */ + .text : + { + FILL(0xFFFF) + + CREATE_OBJECT_SYMBOLS + + + + + _stext = .; /* Provide the name for the start of this section */ + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + KEEP(*(.fwversiondata)) + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + .ARM.extab : + { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array*)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = .; + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >CCM + + /* Uninitialized data section */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + /* __bss_start__ = _sbss; */ + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + } >CCM + + /* Non-zeroed data section */ + . = ALIGN(4); + .noinit (NOLOAD) : + { + *(.noinit) + } >NO_INIT + + + /* collect all uninitialized .ccm sections */ + .ram (NOLOAD) : + { + . = ALIGN(4); + _sram = .; + *(.ram) + } >RAM + + /* User_heap_stack section, used to check that there is enough RAM left */ + ._user_heap_stack : + { + . = ALIGN(4); + . = . + _Min_Heap_Size; + . = . + _Main_Stack_Size; + . = ALIGN(4); + } >RAM + + /* MEMORY_bank1 section, code must be located here explicitly */ + /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ + .memory_b1_text : + { + *(.mb1text) /* .mb1text sections (code) */ + *(.mb1text*) /* .mb1text* sections (code) */ + *(.mb1rodata) /* read-only data (constants) */ + *(.mb1rodata*) + } >MEMORY_B1 + + .sdram (NOLOAD) : + { + *(.sdram) + *(.sdram*) + *(.sdram_rodata) + *(.sdram_rodata*) + . = ALIGN(4); + _eram = .; + } >SDRAM + + PROVIDE ( end = _eram ); + PROVIDE ( _end = _eram ); + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/radio/src/targets/pl18/stm32f4_flash_bootloader.ld b/radio/src/targets/pl18/stm32f4_flash_bootloader.ld new file mode 100644 index 00000000000..5160bfd9e73 --- /dev/null +++ b/radio/src/targets/pl18/stm32f4_flash_bootloader.ld @@ -0,0 +1,195 @@ +/* +***************************************************************************** +** +** File : stm32f4_flash.ld +** +** Abstract : Linker script for STM32F439 Device with +** 2MByte FLASH, 192KByte SRAM, 64KByte CCM +** +** Set heap size, stack size and stack location according +** to application requirements. +** +** Set memory bank area and size if external memory is used. +** +** Target : STMicroelectronics STM32 +** +***************************************************************************** +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_estack = 0x10010000; /* end of 64K CCM */ +_heap_end = 0xC0800000; /* end of 8192K SDRAM */ + +/* Generate a link error if heap and stack don't fit into RAM */ +_Min_Heap_Size = 4096K; /* required amount of heap */ +_Main_Stack_Size = 8192; /* required amount of stack for interrupt stack (Main stack) */ + +/* Main stack end */ +_main_stack_start = _estack - _Main_Stack_Size; + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K + CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K + MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K + SDRAM(xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + + /* The program code and other data goes into FLASH */ + .text : + { + FILL(0xFFFF) + + CREATE_OBJECT_SYMBOLS + + KEEP(*(.bootloader)) /* Bootloader code */ + + . = 0x20000; /* Set the start of the main program */ + _stext = .; /* Provide the name for the start of this section */ + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + KEEP(*(.fwversiondata)) + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + .ARM.extab : + { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array*)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = .; + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >RAM AT> FLASH + + /* Uninitialized data section */ + .bss : + { + . = ALIGN(4); + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + /* __bss_start__ = _sbss; */ + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + } >RAM + + /* Non-zeroed data section */ + . = ALIGN(4); + .noinit (NOLOAD) : + { + *(.noinit) + } >RAM + + /* collect all uninitialized .ccm sections */ + .ram (NOLOAD) : + { + . = ALIGN(4); + _sram = .; + *(.ram) + } >RAM + + .ccm (NOLOAD) : + { + . = ALIGN(4); + _sccm = .; + *(.ccm) + . = ALIGN(4); + . = . + _Main_Stack_Size; + _eccm = .; + } >CCM + + /* MEMORY_bank1 section, code must be located here explicitly */ + /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ + .memory_b1_text : + { + *(.mb1text) /* .mb1text sections (code) */ + *(.mb1text*) /* .mb1text* sections (code) */ + *(.mb1rodata) /* read-only data (constants) */ + *(.mb1rodata*) + } >MEMORY_B1 + + .sdram (NOLOAD) : + { + *(.sdram) + *(.sdram*) + *(.sdram_rodata) + *(.sdram_rodata*) + _eram = .; + . = ALIGN(4); + . = . + _Min_Heap_Size; + . = ALIGN(4); + } >SDRAM + + PROVIDE ( end = _eram ); + PROVIDE ( _end = _eram ); + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/radio/src/targets/pl18/tp_cst340.cpp b/radio/src/targets/pl18/tp_cst340.cpp new file mode 100644 index 00000000000..a5ee1b74cbd --- /dev/null +++ b/radio/src/targets/pl18/tp_cst340.cpp @@ -0,0 +1,604 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "stm32_hal_ll.h" +#include "stm32_hal.h" +#include "hal.h" + +#include "stm32_i2c_driver.h" +#include "timers_driver.h" +#include "delays_driver.h" +#include "tp_cst340.h" + +#include "debug.h" + +volatile static bool touchEventOccured; + +#define TOUCH_RCC_AHB1Periph RCC_AHB1Periph_GPIOB +#define TOUCH_RCC_APB1Periph RCC_APB1Periph_I2C1 +#define TOUCH_GPIO I2C_B1_GPIO +#define TOUCH_SCL_GPIO_PIN I2C_B1_SCL_GPIO_PIN // PB.08 +#define TOUCH_SDA_GPIO_PIN I2C_B1_SDA_GPIO_PIN // PB.09 + +#define TOUCH_FT6236_I2C_ADDRESS (0x70>>1) +#define TOUCH_CST340_I2C_ADDRESS 0x1A + +enum TouchControllers {TC_NONE, TC_FT6236, TC_CST340}; +TouchControllers touchController = TC_NONE; + +static tc_handle_TypeDef tc_handle = {0, 0}; + +tmr10ms_t downTime = 0; +tmr10ms_t tapTime = 0; +short tapCount = 0; +#define TAP_TIME 25 + +struct TouchControllerDescriptor +{ + void (*read)(uint16_t * X, uint16_t * Y, uint32_t * event); + uint8_t (*detectTouch)(); + void (*printDebugInfo)(); + uint32_t contactEvent; +}; + +static const TouchControllerDescriptor *tc = nullptr; + +static TouchState internalTouchState = {}; + +void I2C_FreeBus() +{ + LL_GPIO_InitTypeDef gpioInit; + LL_GPIO_StructInit(&gpioInit); + + // reset i2c bus by setting clk as output and sending manual clock pulses + gpioInit.Pin = TOUCH_SCL_GPIO_PIN; + gpioInit.Mode = LL_GPIO_MODE_OUTPUT; + gpioInit.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + gpioInit.Pull = LL_GPIO_PULL_NO; + gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; + LL_GPIO_Init(TOUCH_GPIO, &gpioInit); + + gpioInit.Pin = TOUCH_SDA_GPIO_PIN; + gpioInit.Mode = LL_GPIO_MODE_INPUT; + gpioInit.OutputType = LL_GPIO_OUTPUT_OPENDRAIN; + gpioInit.Pull = LL_GPIO_PULL_UP; + gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; + LL_GPIO_Init(TOUCH_GPIO, &gpioInit); + + //send 100khz clock train for some 100ms + tmr10ms_t until = get_tmr10ms() + 11; + while (get_tmr10ms() < until) { + if (LL_GPIO_IsInputPinSet(TOUCH_GPIO, TOUCH_SDA_GPIO_PIN)) { + TRACE("touch: i2c free again\n"); + break; + } + TRACE("FREEEEE"); + LL_GPIO_ResetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); + delay_us(10); + LL_GPIO_SetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); + delay_us(10); + } + + //send stop condition: + gpioInit.Pin = TOUCH_SDA_GPIO_PIN; + gpioInit.Mode = LL_GPIO_MODE_OUTPUT; + gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; + LL_GPIO_Init(TOUCH_GPIO, &gpioInit); + + //clock is low + LL_GPIO_ResetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); + delay_us(10); + //sda = lo + LL_GPIO_SetOutputPin(TOUCH_GPIO, TOUCH_SDA_GPIO_PIN); + delay_us(10); + //clock goes high + LL_GPIO_ResetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); + delay_us(10); + //sda = hi + LL_GPIO_SetOutputPin(TOUCH_GPIO, TOUCH_SDA_GPIO_PIN); + delay_us(10); + TRACE("FREE BUS"); +} + +// void Touch_DeInit() +// { +// I2C_DeInit(I2C_B1); +// __HAL_RCC_I2C1_FORCE_RESET(); +// delay_ms(150); +// __HAL_RCC_I2C1_RELEASE_RESET(); +// } + +static int _enable_gpio_clock(GPIO_TypeDef *GPIOx) +{ + if (GPIOx == GPIOB) + __HAL_RCC_GPIOB_CLK_ENABLE(); + else + return -1; + + return 0; +} + +void I2C_Init() +{ + stm32_i2c_deinit(TOUCH_I2C_BUS); + + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_SYSCFG_CLK_ENABLE(); + + __HAL_RCC_I2C1_CLK_ENABLE(); + __HAL_RCC_I2C1_CLK_DISABLE(); + + I2C_FreeBus(); + + stm32_i2c_init(TOUCH_I2C_BUS, TOUCH_I2C_CLK_RATE); + + LL_GPIO_InitTypeDef gpioInit; + LL_GPIO_StructInit(&gpioInit); + + _enable_gpio_clock(TOUCH_RST_GPIO); + _enable_gpio_clock(TOUCH_INT_GPIO); + + gpioInit.Pin = TOUCH_RST_GPIO_PIN; + gpioInit.Mode = LL_GPIO_MODE_OUTPUT; + gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; + gpioInit.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + gpioInit.Pull = LL_GPIO_PULL_UP; + LL_GPIO_Init(TOUCH_RST_GPIO, &gpioInit); + + //ext interupt + gpioInit.Pin = TOUCH_INT_GPIO_PIN; + gpioInit.Mode = LL_GPIO_MODE_INPUT; + gpioInit.Pull = LL_GPIO_PULL_UP; + gpioInit.Speed = LL_GPIO_SPEED_FREQ_HIGH; + gpioInit.OutputType = LL_GPIO_OUTPUT_OPENDRAIN; + LL_GPIO_Init(TOUCH_INT_GPIO, &gpioInit); + + LL_SYSCFG_SetEXTISource(TOUCH_INT_EXTI_Port, TOUCH_INT_EXTI_SysCfgLine); + + LL_EXTI_InitTypeDef extiInit; + LL_EXTI_StructInit(&extiInit); + + extiInit.Line_0_31 = TOUCH_INT_EXTI_Line; + extiInit.Mode = LL_EXTI_MODE_IT; + extiInit.Trigger = LL_EXTI_TRIGGER_FALLING; + extiInit.LineCommand = ENABLE; + LL_EXTI_Init(&extiInit); + + NVIC_SetPriority(TOUCH_INT_EXTI_IRQn, 8); + NVIC_EnableIRQ(TOUCH_INT_EXTI_IRQn); +} + +#define I2C_TIMEOUT_MAX 5 // 5 ms + +bool touch_i2c_read(uint8_t addr, uint8_t reg, uint8_t * data, uint8_t len) +{ +// if(touchController == TC_CST836U) +// { +// if(stm32_i2c_master_tx(TOUCH_I2C_BUS, addr, ®, 1, 3) < 0) +// return false; +// delay_us(5); +// if(stm32_i2c_master_rx(TOUCH_I2C_BUS, addr, data, len, I2C_TIMEOUT_MAX) < 0) +// return false; +// } else { + if (stm32_i2c_read(TOUCH_I2C_BUS, addr, reg, 1, data, len, I2C_TIMEOUT_MAX) < 0) + return false; +// } + + return true; +} + +#if 0 +static bool touch_i2c_write(uint8_t addr, uint8_t reg, uint8_t * data, uint8_t len) +{ + if (stm32_i2c_write(TOUCH_I2C_BUS, addr, reg, 1, data, len, I2C_TIMEOUT_MAX) < 0) + return false; + + return true; +} + +static void TS_IO_Write(uint8_t addr, uint8_t reg, uint8_t data) +{ + uint8_t tryCount = 3; + while (!touch_i2c_write(addr, reg, &data, 1)) { + if (--tryCount == 0) break; + I2C_Init(); + } +} +#endif + +static uint8_t TS_IO_Read(uint8_t addr, uint8_t reg) +{ + uint8_t retult; + uint8_t tryCount = 3; + while (!touch_i2c_read(addr, reg, &retult, 1)) { + if (--tryCount == 0) break; + I2C_Init(); + } + return retult; +} + +static uint16_t TS_IO_ReadMultiple(uint8_t addr, uint8_t reg, uint8_t * buffer, uint16_t length) +{ + uint8_t tryCount = 3; + while (!touch_i2c_read(addr, reg, buffer, length)) { + if (--tryCount == 0) break; + I2C_Init(); + } + return 1; +} + +static void touch_ft6236_debug_info(void) +{ +#if defined(DEBUG) + TRACE("ft6x36: thrhld = %d", TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_TH_GROUP) * 4); + TRACE("ft6x36: rep rate=", TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_PERIODACTIVE) * 10); + TRACE("ft6x36: fw lib 0x%02X %02X", TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_LIB_VER_H), TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_LIB_VER_L)); + TRACE("ft6x36: fw v 0x%02X", TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_FIRMID)); + TRACE("ft6x36: CHIP ID 0x%02X", TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_CIPHER)); + TRACE("ft6x36: CTPM ID 0x%02X", TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_FOCALTECH_ID)); + TRACE("ft6x36: rel code 0x%02X", TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_RELEASE_CODE_ID)); +#endif +} + +/** + * @brief Return if there is touches detected or not. + * Try to detect new touches and forget the old ones (reset internal global + * variables). + * @param DeviceAddr: Device address on communication Bus. + * @retval : Number of active touches detected (can be 0, 1 or 2). + */ +static uint8_t ft6x06_TS_DetectTouch() +{ + volatile uint8_t nbTouch = 0; + + /* Read register FT6206_TD_STAT_REG to check number of touches detection */ + nbTouch = TS_IO_Read(TOUCH_FT6236_I2C_ADDRESS, FT6206_TD_STAT_REG); + nbTouch &= FT6206_TD_STAT_MASK; + if (nbTouch > FT6206_MAX_DETECTABLE_TOUCH) { + /* If invalid number of touch detected, set it to zero */ + nbTouch = 0; + } + /* Update ft6x06 driver internal global : current number of active touches */ + tc_handle.currActiveTouchNb = nbTouch; + + /* Reset current active touch index on which to work on */ + tc_handle.currActiveTouchIdx = 0; + return (nbTouch); +} + +#if 0 +/** + * @brief Get the touch detailed informations on touch number 'touchIdx' (0..1) + * This touch detailed information contains : + * - weight that was applied to this touch + * - sub-area of the touch in the touch panel + * - event of linked to the touch (press down, lift up, ...) + * @param DeviceAddr: Device address on communication Bus (I2C slave address of FT6x06). + * @param touchIdx : Passed index of the touch (0..1) on which we want to get the + * detailed information. + * @param pWeight : Pointer to to get the weight information of 'touchIdx'. + * @param pArea : Pointer to to get the sub-area information of 'touchIdx'. + * @param pEvent : Pointer to to get the event information of 'touchIdx'. + + * @retval None. + */ +static void ft6x06_TS_GetTouchInfo(uint16_t DeviceAddr, + uint32_t touchIdx, + uint32_t * pWeight, + uint32_t * pArea, + uint32_t * pEvent) +{ + uint8_t regAddress = 0; + uint8_t dataxy[3]; + + if (touchIdx < ft6x06_handle.currActiveTouchNb) { + switch (touchIdx) { + case 0 : + regAddress = FT6206_P1_WEIGHT_REG; + break; + + case 1 : + regAddress = FT6206_P2_WEIGHT_REG; + break; + + default : + break; + + } /* end switch(touchIdx) */ + + /* Read weight, area and Event Id of touch index */ + TS_IO_ReadMultiple(DeviceAddr, regAddress, dataxy, sizeof(dataxy)); + + /* Return weight of touch index */ + *pWeight = (dataxy[0] & FT6206_TOUCH_WEIGHT_MASK) >> FT6206_TOUCH_WEIGHT_SHIFT; + /* Return area of touch index */ + *pArea = (dataxy[1] & FT6206_TOUCH_AREA_MASK) >> FT6206_TOUCH_AREA_SHIFT; + /* Return Event Id of touch index */ + *pEvent = (dataxy[2] & FT6206_TOUCH_EVT_FLAG_MASK) >> FT6206_TOUCH_EVT_FLAG_SHIFT; + + } /* of if(touchIdx < ft6x06_handle.currActiveTouchNb) */ +} +#endif + +/** + * @brief Get the touch screen X and Y positions values + * Manage multi touch thanks to touch Index global + * variable 'tc_handle.currActiveTouchIdx'. + * @param DeviceAddr: Device address on communication Bus. + * @param X: Pointer to X position value + * @param Y: Pointer to Y position value + * @retval None. + */ +static void ft6x06_TS_GetXY(uint16_t * X, uint16_t * Y, uint32_t * event) +{ + uint8_t regAddress = 0; + uint8_t dataxy[4]; + + if (tc_handle.currActiveTouchIdx < tc_handle.currActiveTouchNb) { + switch (tc_handle.currActiveTouchIdx) { + case 0 : + regAddress = FT6206_P1_XH_REG; + break; + case 1 : + regAddress = FT6206_P2_XH_REG; + break; + + default : + break; + } + + /* Read X and Y positions */ + TS_IO_ReadMultiple(TOUCH_FT6236_I2C_ADDRESS, regAddress, dataxy, sizeof(dataxy)); + /* Send back ready X position to caller */ + *X = ((dataxy[0] & FT6206_MSB_MASK) << 8) | (dataxy[1] & FT6206_LSB_MASK); + /* Send back ready Y position to caller */ + *Y = ((dataxy[2] & FT6206_MSB_MASK) << 8) | (dataxy[3] & FT6206_LSB_MASK); + + *event = (dataxy[0] & FT6206_TOUCH_EVT_FLAG_MASK) >> FT6206_TOUCH_EVT_FLAG_SHIFT; + /* + uint32_t weight; + uint32_t area; + ft6x06_TS_GetTouchInfo(DeviceAddr, ft6x06_handle.currActiveTouchIdx, &weight, &area, event); + */ + tc_handle.currActiveTouchIdx++; + } +} + +static void touch_cst340_debug_info(void) +{ +#if 0 // Disabled because cannot compile, will fix when necessary +#if defined(DEBUG) + uint8_t tmp[4]; + if (!TS_IO_Write(CST340_MODE_DEBUG_INFO, tmp, 0)) + TRACE("CST340 chip NOT FOUND"); + + // Check the value, expected ChipID + uint32_t chipId = tmp[0] << 8) + tmp[1]; + + if (!I2C_CST340_ReadRegister(CST340_FWVER_REG, tmp, 4)) + TRACE("Error reading CST340 firmware version!"); + uint32_t fwVersion = tmp[0] << 24 | tmp[1]<<16 | tmp[2]<<8 | tmp[0]; + + // Enter normal mode + if (!I2C_CST340_WriteRegister(CST340_MODE_NORMAL, tmp, 0)) + TRACE("ERROR chaning CST340 mode back to normal!"); + + TRACE("cst340: fw ver 0x%08X", fwVersion); + TRACE("cst836u: chip id 0x%04X", chipId); +#endif +#endif +} + +/** + * @brief Get the touch screen X and Y positions values + * @param DeviceAddr: Device address on communication Bus. + * @param X: Pointer to X position value + * @param Y: Pointer to Y position value + * @retval None. + */ +static void cst340_TS_GetXY(uint16_t * X, uint16_t * Y, uint32_t * event) +{ + uint8_t dataxy[4]; + + /* Read X and Y positions */ + TS_IO_ReadMultiple(TOUCH_CST340_I2C_ADDRESS, CST340_FINGER1_REG, dataxy, sizeof(dataxy)); + /* Send back ready X position to caller */ + *X = ((dataxy[1]<<4) + ((dataxy[3]>>4)&0x0f)); + *Y = ((dataxy[2]<<4) + ((dataxy[3])&0x0f)); + /* Send back ready Y position to caller */ + + *event = dataxy[0]; +} + +/** + * @brief Return if there is touches detected or not. + * Try to detect new touches and forget the old ones (reset internal global + * variables). + * @param DeviceAddr: Device address on communication Bus. + * @retval : Number of active touches detected + */ +static uint8_t cst340_TS_DetectTouch() +{ + uint8_t nbTouch; + uint8_t reg = TS_IO_Read(TOUCH_CST340_I2C_ADDRESS, CST340_FINGER1_REG); + if( reg == 0x06 ) + nbTouch = 1; + else + nbTouch = 0; + + tc_handle.currActiveTouchNb = nbTouch; + + tc_handle.currActiveTouchIdx = 0; + return (nbTouch); +} + +void TouchReset() +{ + LL_GPIO_ResetOutputPin(TOUCH_RST_GPIO, TOUCH_RST_GPIO_PIN); + delay_ms(10); + LL_GPIO_SetOutputPin(TOUCH_RST_GPIO, TOUCH_RST_GPIO_PIN); + delay_ms(300); +} + +static const TouchControllerDescriptor FT6236 = +{ + .read = ft6x06_TS_GetXY, + .detectTouch = ft6x06_TS_DetectTouch, + .printDebugInfo = touch_ft6236_debug_info, + .contactEvent = FT6206_TOUCH_EVT_FLAG_CONTACT +}; +static const TouchControllerDescriptor CST340 = +{ + .read = cst340_TS_GetXY, + .detectTouch = cst340_TS_DetectTouch, + .printDebugInfo = touch_cst340_debug_info, + .contactEvent = CST340_TOUCH_EVT_FLAG_CONTACT +}; + +void detectTouchController() +{ + if( stm32_i2c_is_dev_ready(TOUCH_I2C_BUS, TOUCH_CST340_I2C_ADDRESS, 3, 5) == 0) + { + touchController = TC_CST340; + tc = &CST340; + } else { + touchController = TC_FT6236; + tc = &FT6236; + } +} + +void TouchInit() +{ + I2C_Init(); + TouchReset(); + detectTouchController(); + tc->printDebugInfo(); +} + +void handleTouch() +{ + unsigned short touchX; + unsigned short touchY; + uint32_t tEvent = 0; + tc->read(&touchX, &touchY, &tEvent); + + // touch sensor is rotated by 90 deg + unsigned short tmp = touchY; + touchY = 319 - touchX; + touchX = tmp; + + if (tEvent == tc->contactEvent) { + int dx = touchX - internalTouchState.x; + int dy = touchY - internalTouchState.y; + + internalTouchState.x = touchX; + internalTouchState.y = touchY; + + if (internalTouchState.event == TE_NONE || internalTouchState.event == TE_UP || internalTouchState.event == TE_SLIDE_END) { + internalTouchState.startX = internalTouchState.x; + internalTouchState.startY = internalTouchState.y; + internalTouchState.event = TE_DOWN; + } + else if (internalTouchState.event == TE_DOWN) { + if (dx >= SLIDE_RANGE || dx <= -SLIDE_RANGE || dy >= SLIDE_RANGE || dy <= -SLIDE_RANGE) { + internalTouchState.event = TE_SLIDE; + internalTouchState.deltaX = (short) dx; + internalTouchState.deltaY = (short) dy; + } + else { + internalTouchState.event = TE_DOWN; + internalTouchState.deltaX = 0; + internalTouchState.deltaY = 0; + } + } + else if (internalTouchState.event == TE_SLIDE) { + internalTouchState.event = TE_SLIDE; //no change + internalTouchState.deltaX = (short) dx; + internalTouchState.deltaY = (short) dy; + } + + } +} + +#if !defined(TOUCH_INT_EXTI_IRQHandler) + #error "TOUCH_INT_EXTI_IRQHandler is not defined" +#endif +extern "C" void TOUCH_INT_EXTI_IRQHandler(void) +{ + if (LL_EXTI_IsEnabledIT_0_31(TOUCH_INT_EXTI_Line) && + LL_EXTI_IsActiveFlag_0_31(TOUCH_INT_EXTI_Line)) { + touchEventOccured = true; + LL_EXTI_ClearFlag_0_31(TOUCH_INT_EXTI_Line); + } +} + +bool touchPanelEventOccured() +{ + return touchEventOccured; +} + +TouchState touchPanelRead() +{ + if (!touchEventOccured) return internalTouchState; + + touchEventOccured = false; + + tmr10ms_t now = get_tmr10ms(); + internalTouchState.tapCount = 0; + + if (tc->detectTouch()) { + handleTouch(); + if (internalTouchState.event == TE_DOWN && downTime == 0) { + downTime = now; + } + } else { + if (internalTouchState.event == TE_DOWN) { + internalTouchState.event = TE_UP; + if (now - downTime <= TAP_TIME) { + if (now - tapTime > TAP_TIME) { + tapCount = 1; + } else { + tapCount++; + } + internalTouchState.tapCount = tapCount; + tapTime = now; + } else { + internalTouchState.tapCount = 0; // not a tap + } + downTime = 0; + } else { + tapCount = 0; + internalTouchState.tapCount = 0; + internalTouchState.event = TE_SLIDE_END; + } + } + TouchState ret = internalTouchState; + internalTouchState.deltaX = 0; + internalTouchState.deltaY = 0; + if(internalTouchState.event == TE_UP || internalTouchState.event == TE_SLIDE_END) + internalTouchState.event = TE_NONE; + return ret; +} + +TouchState getInternalTouchState() +{ + return internalTouchState; +} diff --git a/radio/src/targets/pl18/tp_cst340.h b/radio/src/targets/pl18/tp_cst340.h new file mode 100644 index 00000000000..ec5f1610749 --- /dev/null +++ b/radio/src/targets/pl18/tp_cst340.h @@ -0,0 +1,289 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "touch.h" + +typedef struct +{ + /* field holding the current number of simultaneous active touches */ + uint8_t currActiveTouchNb; + + /* field holding the touch index currently managed */ + uint8_t currActiveTouchIdx; + +} tc_handle_TypeDef; + + +#define TOUCH_FT6236_MAX_TOUCH_POINTS 2 + +#define TOUCH_FT6236_REG_TH_GROUP 0x80 +#define TOUCH_FT6236_REG_PERIODACTIVE 0x88 +#define TOUCH_FT6236_REG_LIB_VER_H 0xa1 +#define TOUCH_FT6236_REG_LIB_VER_L 0xa2 +#define TOUCH_FT6236_REG_CIPHER 0xa3 +#define TOUCH_FT6236_REG_FIRMID 0xa6 +#define TOUCH_FT6236_REG_FOCALTECH_ID 0xa8 +#define TOUCH_FT6236_REG_RELEASE_CODE_ID 0xaf + +#define TOUCH_FT6236_EVENT_PRESS_DOWN 0 +#define TOUCH_FT6236_EVENT_LIFT_UP 1 +#define TOUCH_FT6236_EVENT_CONTACT 2 +#define TOUCH_FT6236_EVENT_NO_EVENT 3 + +#define TOUCH_FT6236_GESTURE_MOVE_FLAG 0x10 +#define TOUCH_FT6236_GESTURE_MOVE_UP 0x10 +#define TOUCH_FT6236_GESTURE_MOVE_RIGHT 0x14 +#define TOUCH_FT6236_GESTURE_MOVE_DOWN 0x18 +#define TOUCH_FT6236_GESTURE_MOVE_LEFT 0x1C +#define TOUCH_FT6236_GESTURE_ZOOM_IN 0x48 +#define TOUCH_FT6236_GESTURE_ZOOM_OUT 0x49 +#define TOUCH_FT6236_GESTURE_NONE 0x00 + +#define TOUCH_GESTURE_UP ((TOUCH_FT6236_GESTURE_MOVE_UP & 0x0F)+1) +#define TOUCH_GESTURE_DOWN ((TOUCH_FT6236_GESTURE_MOVE_DOWN & 0x0F)+1) +#define TOUCH_GESTURE_LEFT ((TOUCH_FT6236_GESTURE_MOVE_LEFT & 0x0F)+1) +#define TOUCH_GESTURE_RIGHT ((TOUCH_FT6236_GESTURE_MOVE_RIGHT & 0x0F)+1) +#define TOUCH_GESTURE_MOUSE_DOWN (0x80+TOUCH_FT6236_EVENT_PRESS_DOWN) +#define TOUCH_GESTURE_MOUSE_UP (0x80+TOUCH_FT6236_EVENT_LIFT_UP) +#define TOUCH_GESTURE_MOUSE_MOVE (0x80+TOUCH_FT6236_EVENT_CONTACT) +#define TOUCH_GESTURE_MOUSE_NONE (0x80+TOUCH_FT6236_EVENT_NO_EVENT) + + + + /* Maximum border values of the touchscreen pad */ +#define FT_6206_MAX_WIDTH ((uint16_t)800) /* Touchscreen pad max width */ +#define FT_6206_MAX_HEIGHT ((uint16_t)480) /* Touchscreen pad max height */ + + /* Possible values of driver functions return status */ +#define FT6206_STATUS_OK 0 +#define FT6206_STATUS_NOT_OK 1 + + /* Max detectable simultaneous touches */ +#define FT6206_MAX_DETECTABLE_TOUCH 2 + + /** + * @brief : Definitions for FT6206 I2C register addresses on 8 bit + **/ + + /* Current mode register of the FT6206 (R/W) */ +#define FT6206_DEV_MODE_REG 0x00 + + /* Possible values of FT6206_DEV_MODE_REG */ +#define FT6206_DEV_MODE_WORKING 0x00 +#define FT6206_DEV_MODE_FACTORY 0x04 + +#define FT6206_DEV_MODE_MASK 0x7 +#define FT6206_DEV_MODE_SHIFT 4 + + /* Gesture ID register */ +#define FT6206_GEST_ID_REG 0x01 + + /* Possible values of FT6206_GEST_ID_REG */ +#define FT6206_GEST_ID_NO_GESTURE 0x00 +#define FT6206_GEST_ID_MOVE_UP 0x10 +#define FT6206_GEST_ID_MOVE_RIGHT 0x14 +#define FT6206_GEST_ID_MOVE_DOWN 0x18 +#define FT6206_GEST_ID_MOVE_LEFT 0x1C +#define FT6206_GEST_ID_ZOOM_IN 0x48 +#define FT6206_GEST_ID_ZOOM_OUT 0x49 + + /* Touch Data Status register : gives number of active touch points (0..2) */ +#define FT6206_TD_STAT_REG 0x02 + + /* Values related to FT6206_TD_STAT_REG */ +#define FT6206_TD_STAT_MASK 0x0F +#define FT6206_TD_STAT_SHIFT 0x00 + + /* Values Pn_XH and Pn_YH related */ +#define FT6206_TOUCH_EVT_FLAG_PRESS_DOWN 0x00 +#define FT6206_TOUCH_EVT_FLAG_LIFT_UP 0x01 +#define FT6206_TOUCH_EVT_FLAG_CONTACT 0x02 +#define FT6206_TOUCH_EVT_FLAG_NO_EVENT 0x03 + +#define FT6206_TOUCH_EVT_FLAG_SHIFT 6 +#define FT6206_TOUCH_EVT_FLAG_MASK (3 << FT6206_TOUCH_EVT_FLAG_SHIFT) + +#define FT6206_MSB_MASK 0x0F +#define FT6206_MSB_SHIFT 0 + + /* Values Pn_XL and Pn_YL related */ +#define FT6206_LSB_MASK 0xFF +#define FT6206_LSB_SHIFT 0 + +#define FT6206_P1_XH_REG 0x03 +#define FT6206_P1_XL_REG 0x04 +#define FT6206_P1_YH_REG 0x05 +#define FT6206_P1_YL_REG 0x06 + + /* Touch Pressure register value (R) */ +#define FT6206_P1_WEIGHT_REG 0x07 + + /* Values Pn_WEIGHT related */ +#define FT6206_TOUCH_WEIGHT_MASK 0xFF +#define FT6206_TOUCH_WEIGHT_SHIFT 0 + + /* Touch area register */ +#define FT6206_P1_MISC_REG 0x08 + + /* Values related to FT6206_Pn_MISC_REG */ +#define FT6206_TOUCH_AREA_MASK (0x04 << 4) +#define FT6206_TOUCH_AREA_SHIFT 0x04 + +#define FT6206_P2_XH_REG 0x09 +#define FT6206_P2_XL_REG 0x0A +#define FT6206_P2_YH_REG 0x0B +#define FT6206_P2_YL_REG 0x0C +#define FT6206_P2_WEIGHT_REG 0x0D +#define FT6206_P2_MISC_REG 0x0E + + /* Threshold for touch detection */ +#define FT6206_TH_GROUP_REG 0x80 + + /* Values FT6206_TH_GROUP_REG : threshold related */ +#define FT6206_THRESHOLD_MASK 0xFF +#define FT6206_THRESHOLD_SHIFT 0 + + /* Filter function coefficients */ +#define FT6206_TH_DIFF_REG 0x85 + + /* Control register */ +#define FT6206_CTRL_REG 0x86 + + /* Values related to FT6206_CTRL_REG */ + + /* Will keep the Active mode when there is no touching */ +#define FT6206_CTRL_KEEP_ACTIVE_MODE 0x00 + + /* Switching from Active mode to Monitor mode automatically when there is no touching */ +#define FT6206_CTRL_KEEP_AUTO_SWITCH_MONITOR_MODE 0x01 + + /* The time period of switching from Active mode to Monitor mode when there is no touching */ +#define FT6206_TIMEENTERMONITOR_REG 0x87 + + /* Report rate in Active mode */ +#define FT6206_PERIODACTIVE_REG 0x88 + + /* Report rate in Monitor mode */ +#define FT6206_PERIODMONITOR_REG 0x89 + + /* The value of the minimum allowed angle while Rotating gesture mode */ +#define FT6206_RADIAN_VALUE_REG 0x91 + + /* Maximum offset while Moving Left and Moving Right gesture */ +#define FT6206_OFFSET_LEFT_RIGHT_REG 0x92 + + /* Maximum offset while Moving Up and Moving Down gesture */ +#define FT6206_OFFSET_UP_DOWN_REG 0x93 + + /* Minimum distance while Moving Left and Moving Right gesture */ +#define FT6206_DISTANCE_LEFT_RIGHT_REG 0x94 + + /* Minimum distance while Moving Up and Moving Down gesture */ +#define FT6206_DISTANCE_UP_DOWN_REG 0x95 + + /* Maximum distance while Zoom In and Zoom Out gesture */ +#define FT6206_DISTANCE_ZOOM_REG 0x96 + + /* High 8-bit of LIB Version info */ +#define FT6206_LIB_VER_H_REG 0xA1 + + /* Low 8-bit of LIB Version info */ +#define FT6206_LIB_VER_L_REG 0xA2 + + /* Chip Selecting */ +#define FT6206_CIPHER_REG 0xA3 + + /* Interrupt mode register (used when in interrupt mode) */ +#define FT6206_GMODE_REG 0xA4 + +#define FT6206_G_MODE_INTERRUPT_MASK 0x03 +#define FT6206_G_MODE_INTERRUPT_SHIFT 0x00 + + /* Possible values of FT6206_GMODE_REG */ +#define FT6206_G_MODE_INTERRUPT_POLLING 0x00 +#define FT6206_G_MODE_INTERRUPT_TRIGGER 0x01 + + /* Current power mode the FT6206 system is in (R) */ +#define FT6206_PWR_MODE_REG 0xA5 + + /* FT6206 firmware version */ +#define FT6206_FIRMID_REG 0xA6 + + /* FT6206 Chip identification register */ +#define FT6206_CHIP_ID_REG 0xA8 + + /* Possible values of FT6206_CHIP_ID_REG */ +#define FT6206_ID_VALUE 0x11 + + /* Release code version */ +#define FT6206_RELEASE_CODE_ID_REG 0xAF + + /* Current operating mode the FT6206 system is in (R) */ +#define FT6206_STATE_REG 0xBC + + + +#define HAS_TOUCH_PANEL() touchCST340Flag == true + +#define CST340_MODE_DEBUG_INFO 0xD101 // To read out chip ID and firmware version +#define CST340_MODE_NORMAL 0xD109 // Normal mode +#define CST340_FINGER1_REG 0xD000 // Touch info register +#define CST340_CHIPTYPE_REG 0xD204 // uint16_t chip IC type & uint16_t project ID register +#define CST340_FWVER_REG 0xD208 // Firmware version register(uint8_t major, uint8_t minor, uint16_t build) +#define CST340_TOUCH_EVT_FLAG_CONTACT 6 + +#define CST340_CHIP_ID 0x011C // Expected answer to CST340_CHIPTYPE_REG query for the ChipID field + +#define TOUCH_POINTS_MAX 5 // Max touch points + +#define TOUCH_ACK 0 +#define TOUCH_NACK 1 + +#define CST340_I2C_ADDR 0x1A + +extern bool touchCST340Flag; +extern uint32_t touchI2Chiccups; +extern uint16_t touchICfwver; +void TouchInit(); + +struct TouchState touchPanelRead(); +bool touchPanelEventOccured(); + +PACK(typedef struct { + uint8_t track; + uint16_t x; + uint16_t y; + uint16_t size; + uint8_t reserved; +}) TouchPoint; + +PACK(struct TouchData { + union + { + TouchPoint points[TOUCH_POINTS_MAX]; + uint8_t data[TOUCH_POINTS_MAX * sizeof(TouchPoint)]; + }; +}); + +#define TPRST_LOW() do { TOUCH_RST_GPIO->BSRRH = TOUCH_RST_GPIO_PIN; } while(0) +#define TPRST_HIGH() do { TOUCH_RST_GPIO->BSRRL = TOUCH_RST_GPIO_PIN; } while(0) diff --git a/radio/src/targets/simu/opentxsimulator.cpp b/radio/src/targets/simu/opentxsimulator.cpp index 3c13292347f..4f51bc44bb2 100644 --- a/radio/src/targets/simu/opentxsimulator.cpp +++ b/radio/src/targets/simu/opentxsimulator.cpp @@ -712,6 +712,8 @@ class OpenTxSimulatorFactory: public SimulatorFactory return Board::BOARD_TARANIS_X9LITE; #elif defined(PCBNV14) return Board::BOARD_FLYSKY_NV14; +#elif defined(PCBPL18) + return Board::BOARD_FLYSKY_PL18; #else return Board::BOARD_TARANIS_X9D; #endif diff --git a/radio/src/targets/simu/simpgmspace.cpp b/radio/src/targets/simu/simpgmspace.cpp index 76aaef71ee2..04e6dff25dd 100644 --- a/radio/src/targets/simu/simpgmspace.cpp +++ b/radio/src/targets/simu/simpgmspace.cpp @@ -535,7 +535,7 @@ void boardOff() void hapticOff() {} -#if defined(PCBFRSKY) || defined(PCBFLYSKY) +#if defined(PCBFRSKY) || defined(PCBNV14) HardwareOptions hardwareOptions; #endif diff --git a/radio/src/targets/simu/simufatfs.cpp b/radio/src/targets/simu/simufatfs.cpp index bd6abc90c0c..c1c15c63a97 100644 --- a/radio/src/targets/simu/simufatfs.cpp +++ b/radio/src/targets/simu/simufatfs.cpp @@ -616,6 +616,7 @@ FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs) #include "hal/storage.h" void storageInit() {} +void storageDeInit() {} void storagePreMountHook() {} bool storageIsPresent() { return true; } diff --git a/radio/src/targets/taranis/CMakeLists.txt b/radio/src/targets/taranis/CMakeLists.txt index 7fc9ef2113c..4207ed87203 100644 --- a/radio/src/targets/taranis/CMakeLists.txt +++ b/radio/src/targets/taranis/CMakeLists.txt @@ -480,10 +480,6 @@ else() message( FATAL_ERROR "Unknown CPU_TYPE_FULL" ) endif() -if(NOT PCB STREQUAL PCBXLITE) - add_definitions(-DHARDWARE_TRAINER_JACK) -endif() - if(ENABLE_SERIAL_PASSTHROUGH) set(CLI ON "Enable CLI") endif() diff --git a/radio/src/translations/cn.h b/radio/src/translations/cn.h index f338756d2e4..d4b4af337e5 100644 --- a/radio/src/translations/cn.h +++ b/radio/src/translations/cn.h @@ -216,7 +216,7 @@ #if defined(PCBFRSKY) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" diff --git a/radio/src/translations/cz.h b/radio/src/translations/cz.h index aa001fff4e2..660ee700c8f 100644 --- a/radio/src/translations/cz.h +++ b/radio/src/translations/cz.h @@ -229,7 +229,7 @@ #if defined(PCBTARANIS) || defined(PCBHORUS) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[DALŠÍ]" #else #define TR_ENTER "[MENU]" @@ -330,7 +330,7 @@ #define TR_SLOWUP TR3("Zpomalení(+)", "Zpomal(\176)", "Zpomalení(\176)") #define TR_MIXES "MIXER" #define TR_CV "K" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "GP" #else #define TR_GV TR("G", "GP") diff --git a/radio/src/translations/de.h b/radio/src/translations/de.h index 9b1697762e1..bd56056ea42 100644 --- a/radio/src/translations/de.h +++ b/radio/src/translations/de.h @@ -220,7 +220,7 @@ #if defined(PCBFRSKY) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" @@ -321,7 +321,7 @@ #define TR_SLOWUP "Langs.Up" #define TR_MIXES "MISCHER" #define TR_CV "KV" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "GV" #else #define TR_GV TR("G", "GV") diff --git a/radio/src/translations/en.h b/radio/src/translations/en.h index 05e9267a613..a5e67d903e3 100644 --- a/radio/src/translations/en.h +++ b/radio/src/translations/en.h @@ -220,7 +220,7 @@ #if defined(PCBFRSKY) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" @@ -320,7 +320,7 @@ #define TR_SLOWUP "Slow up" #define TR_MIXES "MIXES" #define TR_CV "CV" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "GV" #else #define TR_GV TR("G", "GV") diff --git a/radio/src/translations/es.h b/radio/src/translations/es.h index 71d1448c879..835072d661c 100644 --- a/radio/src/translations/es.h +++ b/radio/src/translations/es.h @@ -216,7 +216,7 @@ #if defined(PCBTARANIS) || defined(PCBHORUS) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" @@ -318,7 +318,7 @@ #define TR_SLOWUP "Subir lento" #define TR_MIXES "MIXES" #define TR_CV "CV" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "GV" #else #define TR_GV TR("G", "GV") diff --git a/radio/src/translations/fr.h b/radio/src/translations/fr.h index 490924f143d..6773574f577 100644 --- a/radio/src/translations/fr.h +++ b/radio/src/translations/fr.h @@ -226,7 +226,7 @@ #if defined(PCBFRSKY) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[SUIVANT]" #else #define TR_ENTER "[MENU]" @@ -327,7 +327,7 @@ #define TR_SLOWUP "Ralenti haut" #define TR_MIXES "MIXEUR" #define TR_CV "CV" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "VG" #else #define TR_GV TR("G", "VG") diff --git a/radio/src/translations/it.h b/radio/src/translations/it.h index 0b87268d50b..a84e61c3b1d 100644 --- a/radio/src/translations/it.h +++ b/radio/src/translations/it.h @@ -220,7 +220,7 @@ #if defined(PCBFRSKY) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" @@ -320,7 +320,7 @@ #define TR_SLOWUP "Rall.Su" #define TR_MIXES "MIXER" #define TR_CV "CV" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "GV" #else #define TR_GV TR("G", "GV") diff --git a/radio/src/translations/nl.h b/radio/src/translations/nl.h index afe22cb2a53..b92c9a84230 100644 --- a/radio/src/translations/nl.h +++ b/radio/src/translations/nl.h @@ -217,7 +217,7 @@ #if defined(PCBFRSKY) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" @@ -317,7 +317,7 @@ #define TR_SLOWUP "Langz.Up" #define TR_MIXES "MIXER" #define TR_CV "CV" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "GV" #else #define TR_GV TR("G", "GV") diff --git a/radio/src/translations/pl.h b/radio/src/translations/pl.h index 7dcb4acd8c1..a04fba07090 100644 --- a/radio/src/translations/pl.h +++ b/radio/src/translations/pl.h @@ -216,7 +216,7 @@ #if defined(PCBTARANIS) || defined(PCBHORUS) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" @@ -318,7 +318,7 @@ #define TR_SLOWUP "Spowoln.(+)" #define TR_MIXES "MIKSERY" #define TR_CV "Kr" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "ZG" #else #define TR_GV TR("G", "ZG") diff --git a/radio/src/translations/pt.h b/radio/src/translations/pt.h index 2000191a1e8..d67c3aaa6a8 100644 --- a/radio/src/translations/pt.h +++ b/radio/src/translations/pt.h @@ -223,7 +223,7 @@ #if defined(PCBFRSKY) #define TR_ENTER "[ENTER]" -#elif defined(PCBNV14) +#elif defined(PCBNV14) || defined(PCBPL18) #define TR_ENTER "[NEXT]" #else #define TR_ENTER "[MENU]" @@ -323,7 +323,7 @@ #define TR_SLOWUP "Slow up" #define TR_MIXES "MIXES" #define TR_CV "CV" -#if defined(PCBNV14) +#if defined(PCBNV14) || defined(PCBPL18) #define TR_GV "GV" #else #define TR_GV TR("G", "GV") diff --git a/radio/util/hw_defs/hal_keys.jinja b/radio/util/hw_defs/hal_keys.jinja index 499a41a397c..15b563d94e3 100644 --- a/radio/util/hw_defs/hal_keys.jinja +++ b/radio/util/hw_defs/hal_keys.jinja @@ -14,14 +14,11 @@ const char* const _key_labels[] = { {% endfor %} }; -constexpr uint32_t _defined_keys = +constexpr uint32_t _defined_keys = 0 {% for key in keys %} - {% if loop.last %} - (1 << {{ key.key }}); - {% else %} - (1 << {{ key.key }}) | - {% endif %} + | (1 << {{ key.key }}) {% endfor %} +; constexpr uint8_t _n_keys = {{ keys | length }}; constexpr uint8_t _n_trims = {{ trims | length }}; diff --git a/radio/util/hw_defs/legacy_names.py b/radio/util/hw_defs/legacy_names.py index 77f21d27c08..0c941db0a60 100644 --- a/radio/util/hw_defs/legacy_names.py +++ b/radio/util/hw_defs/legacy_names.py @@ -338,6 +338,74 @@ } } }, + { + "targets": { + "pl18" + }, + "inputs": { + "LH": { + "yaml": "Rud" + }, + "LV": { + "yaml": "Ele" + }, + "RV": { + "yaml": "Thr" + }, + "RH": { + "yaml": "Ail" + }, + "P1": { + "yaml": "POT1", + "lua": "s1", + "label": "S1", + "short_label": "1", + "description": "Potentiometer 1" + }, + "P2": { + "yaml": "POT2", + "lua": "s2", + "label": "S2", + "short_label": "2", + "description": "Potentiometer 2" + }, + "P3": { + "yaml": "POT3", + "lua": "s3", + "label": "S3", + "short_label": "3", + "description": "Potentiometer 3" + }, + "SL1": { + "yaml": "LS", + "lua": "ls", + "label": "LS", + "short_label": "L", + "description": "Left slider" + }, + "SL2": { + "yaml": "RS", + "lua": "rs", + "label": "RS", + "short_label": "R", + "description": "Right slider" + }, + "EXT1": { + "yaml": "EXT1", + "lua": "ext1", + "label": "EXT1", + "short_label": "E1", + "description": "Ext 1" + }, + "EXT2": { + "yaml": "EXT2", + "lua": "ext2", + "label": "EXT2", + "short_label": "E2", + "description": "Ext 2" + }, + } + }, { "targets": {"t20"}, "inputs": { diff --git a/tools/build-companion.sh b/tools/build-companion.sh index 128fba24c5c..54c7a0f545d 100755 --- a/tools/build-companion.sh +++ b/tools/build-companion.sh @@ -70,7 +70,7 @@ declare -a simulator_plugins=(x9lite x9lites tlite tpro lr3pro x9d x9dp x9dp2019 x9e xlite xlites - nv14 + nv14 pl18 x10 x10-access x12s t16 t18 tx16s) @@ -170,6 +170,9 @@ do commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" ;; + pl18) + BUILD_OPTIONS+="-DPCB=PL18" + ;; *) echo "Unknown target: $target_name" exit 1 diff --git a/tools/build-flysky.py b/tools/build-flysky.py index c6c60ad74d0..b7f0a3b4d77 100644 --- a/tools/build-flysky.py +++ b/tools/build-flysky.py @@ -9,15 +9,9 @@ boards = { - "NV14": { - "PCB": "NV14", - "DEFAULT_MODE": "1", - }, - "EL18": { - "PCB": "NV14", - "PCBREV": "EL18", - "DEFAULT_MODE": "1", - }, + "NV14": { "PCB": "NV14" }, + "EL18": { "PCB": "NV14", "PCBREV": "EL18" }, + "PL18": { "PCB": "PL18" }, } translations = [ diff --git a/tools/build-gh.sh b/tools/build-gh.sh index aa154af5959..790229b31a8 100755 --- a/tools/build-gh.sh +++ b/tools/build-gh.sh @@ -191,6 +191,9 @@ do el18) BUILD_OPTIONS+="-DPCB=NV14 -DPCBREV=EL18" ;; + pl18) + BUILD_OPTIONS+="-DPCB=PL18" + ;; commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" ;; diff --git a/tools/commit-tests.sh b/tools/commit-tests.sh index 66ea77e8516..83230496b3e 100755 --- a/tools/commit-tests.sh +++ b/tools/commit-tests.sh @@ -148,6 +148,9 @@ do el18) BUILD_OPTIONS+="-DPCB=NV14 -DPCBREV=EL18" ;; + pl18) + BUILD_OPTIONS+="-DPCB=PL18" + ;; commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" ;; diff --git a/tools/generate-yaml.sh b/tools/generate-yaml.sh index aa1b04c33ec..cb834c7a05f 100755 --- a/tools/generate-yaml.sh +++ b/tools/generate-yaml.sh @@ -8,7 +8,7 @@ if [[ -n ${GCC_ARM} ]] ; then export PATH=${GCC_ARM}:$PATH fi -: ${FLAVOR:="tx16s;x12s;nv14;x9d;x9dp;x9e;x9lite;xlites;x7;tpro;t20"} +: ${FLAVOR:="tx16s;x12s;nv14;pl18;x9d;x9dp;x9e;x9lite;xlites;x7;tpro;t20"} : ${SRCDIR:=$(dirname "$(pwd)/$0")/..} : ${COMMON_OPTIONS:="-DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_RULE_MESSAGES=OFF -Wno-dev -DDISABLE_COMPANION=YES -DCMAKE_MESSAGE_LOG_LEVEL=WARNING"} @@ -110,6 +110,9 @@ do nv14) BUILD_OPTIONS+="-DPCB=NV14" ;; + pl18) + BUILD_OPTIONS+="-DPCB=PL18" + ;; commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" ;; From 65ce1b56d69c054363134e6af7b885e857b93480 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Sun, 24 Sep 2023 20:27:43 +0200 Subject: [PATCH 03/57] feat: ADC driver timeout --- .../boards/generic_stm32/analog_inputs.cpp | 3 + .../targets/common/arm/stm32/stm32_adc.cpp | 18 ++-- .../src/targets/common/arm/stm32/stm32_adc.h | 1 + radio/src/targets/pl18/hal.h | 97 +++++++++++-------- radio/util/hw_defs/stm32_adc_inputs.jinja | 1 + 5 files changed, 75 insertions(+), 45 deletions(-) diff --git a/radio/src/boards/generic_stm32/analog_inputs.cpp b/radio/src/boards/generic_stm32/analog_inputs.cpp index 19036a17973..bbcbc7edaab 100644 --- a/radio/src/boards/generic_stm32/analog_inputs.cpp +++ b/radio/src/boards/generic_stm32/analog_inputs.cpp @@ -47,6 +47,9 @@ constexpr uint8_t n_ADC_spi = DIM(_ADC_spi); constexpr uint8_t n_GPIO = DIM(_ADC_GPIOs); constexpr uint8_t n_inputs = DIM(_ADC_inputs); +static_assert(n_inputs <= MAX_ADC_INPUTS, "Too many ADC inputs"); +static_assert(n_inputs <= MAX_ANALOG_INPUTS, "Too many analog inputs"); + static bool adc_init() { bool success = stm32_hal_adc_init(_ADC_adc, n_ADC, _ADC_inputs, _ADC_GPIOs, n_GPIO); diff --git a/radio/src/targets/common/arm/stm32/stm32_adc.cpp b/radio/src/targets/common/arm/stm32/stm32_adc.cpp index 4244a5d96a9..48c98278ee9 100644 --- a/radio/src/targets/common/arm/stm32/stm32_adc.cpp +++ b/radio/src/targets/common/arm/stm32/stm32_adc.cpp @@ -26,9 +26,10 @@ #include "opentx.h" -#define ADC_COMMON ((ADC_Common_TypeDef *) ADC_BASE) -#define MAX_ADC_INPUTS 32 -#define OVERSAMPLING 4 +#define ADC_COMMON ((ADC_Common_TypeDef *)ADC_BASE) +#define OVERSAMPLING 4 + +#define SAMPLING_TIMEOUT_US 500 // Please note that we use the same prio for DMA TC and ADC IRQs // to avoid issues with preemption between these 2 @@ -43,7 +44,7 @@ static volatile uint32_t _adc_inhibit_mask; static uint16_t _adc_dma_buffer[MAX_ADC_INPUTS] __DMA; // ADCs started -static uint8_t _adc_started_mask; +static volatile uint8_t _adc_started_mask; static volatile uint8_t _adc_completed; static const stm32_adc_t* _adc_ADCs; @@ -532,8 +533,14 @@ void stm32_hal_adc_wait_completion(const stm32_adc_t* ADCs, uint8_t n_ADC, (void)inputs; (void)n_inputs; + auto timeout = timersGetUsTick(); while(!_adc_completed) { // busy wait + if ((uint32_t)(timersGetUsTick() - timeout) >= SAMPLING_TIMEOUT_US) { + TRACE("ADC timeout"); + _adc_started_mask = 0; + return; + } } } @@ -572,8 +579,7 @@ static void _adc_chain_conversions(const stm32_adc_t* adc) void stm32_hal_adc_dma_isr(const stm32_adc_t* adc) { // Disable IRQ - auto dma_stream = _dma_get_stream(adc->DMAx, adc->DMA_Stream); - CLEAR_BIT(dma_stream->CR, DMA_SxCR_TCIE | DMA_SxCR_TEIE | DMA_SxCR_DMEIE); + adc_dma_clear_flags(adc->DMAx, adc->DMA_Stream); uint16_t* dma_buffer = _adc_dma_buffer + adc->offset; copy_adc_values(dma_buffer, adc, _adc_inputs); diff --git a/radio/src/targets/common/arm/stm32/stm32_adc.h b/radio/src/targets/common/arm/stm32/stm32_adc.h index 9d73a709988..97ee324e1dc 100644 --- a/radio/src/targets/common/arm/stm32/stm32_adc.h +++ b/radio/src/targets/common/arm/stm32/stm32_adc.h @@ -24,6 +24,7 @@ #include "stm32_hal_ll.h" #include "hal/adc_driver.h" +#define MAX_ADC_INPUTS 32 struct stm32_adc_input_t { GPIO_TypeDef* GPIOx; diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 4cc54ffc4e6..122cff3169c 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -130,14 +130,37 @@ #define TRIMS_GPIO_REG_IN4 GPIOJ->IDR #define TRIMS_GPIO_PIN_IN4 LL_GPIO_PIN_12 // PJ.12 +// Index of all trims + +#define KEYS_GPIOB_PINS (LL_GPIO_PIN_15) + +// PC8 allocated to SDIO D0, is not required to sample SWA ! +#define KEYS_GPIOC_PINS (LL_GPIO_PIN_13) + +#define KEYS_GPIOD_PINS (LL_GPIO_PIN_7) + +#define KEYS_GPIOH_PINS \ + (LL_GPIO_PIN_8 | LL_GPIO_PIN_9 | LL_GPIO_PIN_10 | LL_GPIO_PIN_11) + +#define KEYS_GPIOJ_PINS (LL_GPIO_PIN_12) + +#define KEYS_OUT_GPIOG_PINS (LL_GPIO_PIN_2 | LL_GPIO_PIN_10 | LL_GPIO_PIN_11) + +#define KEYS_OUT_GPIOH_PINS (LL_GPIO_PIN_7) + + // Monitor pin // #define MONITOR_RCC_AHB1Periph (RCC_AHB1Periph_GPIOJ) // #define VBUS_MONITOR_GPIO (GPIOJ) // #define VBUS_MONITOR_PIN (LL_GPIO_PIN_14) -// Switches -// Switches A and C on PL18/PL18EV are 2-position switches, so there is no NEED to configure two pins for Switches A and C. -// Especially, as on current dev. state, using PC8 for SDIO D0 - happy coincidence ;) +// Switches: +// Switches A and C on PL18/PL18EV are 2-position switches, +// so there is no NEED to configure two pins for Switches A and C. +// +// Especially, as on current dev. state, using PC8 for SDIO D0. +// (happy coincidence ;) +// // #define SWITCHES_GPIO_REG_A_H GPIOC // #define SWITCHES_GPIO_PIN_A_H LL_GPIO_PIN_8 // PC.08 // #define SWITCHES_GPIO_REG_A_L GPIOC @@ -146,7 +169,9 @@ #define SWITCHES_GPIO_REG_A GPIOC #define SWITCHES_GPIO_PIN_A LL_GPIO_PIN_9 // PC.09 -// High rail of Switch C is not required and thus PC10 is free to use for customizations. +// High rail of Switch C is not required and thus PC10 is free to use for +// customizations. +// // #define SWITCHES_GPIO_REG_C_H GPIOC // #define SWITCHES_GPIO_PIN_C_H LL_GPIO_PIN_10 // PC.10 // #define SWITCHES_GPIO_REG_C_L GPIOC @@ -155,19 +180,6 @@ #define SWITCHES_GPIO_REG_C GPIOC #define SWITCHES_GPIO_PIN_C LL_GPIO_PIN_11 // PC.11 -// Index of all switches / trims - -#define KEYS_GPIOB_PINS (LL_GPIO_PIN_15) - // PC8 allocated to SDIO D0, is not required to sample SWA ! -#define KEYS_GPIOC_PINS (LL_GPIO_PIN_9 | LL_GPIO_PIN_11 | LL_GPIO_PIN_13) -#define KEYS_GPIOD_PINS (LL_GPIO_PIN_7) -#define KEYS_GPIOH_PINS \ - (LL_GPIO_PIN_8 | LL_GPIO_PIN_9 | LL_GPIO_PIN_10 | LL_GPIO_PIN_11) -#define KEYS_GPIOJ_PINS (LL_GPIO_PIN_12) -#define KEYS_OUT_GPIOG_PINS (LL_GPIO_PIN_2 | LL_GPIO_PIN_10 | LL_GPIO_PIN_11) -#define KEYS_OUT_GPIOH_PINS (LL_GPIO_PIN_7) - - // ADC #define ADC_GPIO_PIN_STICK_LH @@ -178,14 +190,12 @@ #define ADC_GPIO_PIN_POT1 LL_GPIO_PIN_6 // PA.06 VRA #define ADC_GPIO_PIN_POT2 LL_GPIO_PIN_4 // PC.04 VRB #define ADC_GPIO_PIN_POT3 LL_GPIO_PIN_8 // PF.08 VRC -#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 -#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 #define ADC_GPIO_PIN_SLIDER1 LL_GPIO_PIN_9 // PF.09 VRD/LS #define ADC_GPIO_PIN_SLIDER2 LL_GPIO_PIN_7 // PA.07 VRE/RS +#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 +#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 -// #define ADC_GPIO_PIN_SWA LL_GPIO_PIN_1 // PB.01 #define ADC_GPIO_PIN_SWB LL_GPIO_PIN_1 // PC.01 -// #define ADC_GPIO_PIN_SWC LL_GPIO_PIN_0 // PB.00 #define ADC_GPIO_PIN_SWD LL_GPIO_PIN_0 // PC.00 #define ADC_GPIO_PIN_SWE LL_GPIO_PIN_2 // PC.02 #define ADC_GPIO_PIN_SWF LL_GPIO_PIN_0 // PB.00 @@ -194,14 +204,13 @@ #define ADC_GPIO_PIN_BATT LL_GPIO_PIN_5 // PC.05 -#define ADC_GPIOA_PINS \ - (ADC_GPIO_PIN_POT1 | ADC_GPIO_PIN_EXT1 | ADC_GPIO_PIN_SLIDER2) +#define ADC_GPIOA_PINS (ADC_GPIO_PIN_POT1 | ADC_GPIO_PIN_SLIDER2 | ADC_GPIO_PIN_EXT1) #define ADC_GPIOB_PINS (ADC_GPIO_PIN_SWF | ADC_GPIO_PIN_SWG) -#define ADC_GPIOC_PINS \ - (ADC_GPIO_PIN_POT2 | ADC_GPIO_PIN_SWB | ADC_GPIO_PIN_SWD | ADC_GPIO_PIN_SWE | ADC_GPIO_PIN_BATT) -#define ADC_GPIOF_PINS \ - (ADC_GPIO_PIN_POT3 | ADC_GPIO_PIN_EXT2 | ADC_GPIO_PIN_SLIDER1 | ADC_GPIO_PIN_SWH) - +#define ADC_GPIOC_PINS \ + (ADC_GPIO_PIN_POT2 | ADC_GPIO_PIN_BATT | ADC_GPIO_PIN_SWB | ADC_GPIO_PIN_SWD | ADC_GPIO_PIN_SWE) +#define ADC_GPIOF_PINS \ + (ADC_GPIO_PIN_POT3 | ADC_GPIO_PIN_SLIDER1 | ADC_GPIO_PIN_EXT2 | ADC_GPIO_PIN_SWH) + #define ADC_CHANNEL_STICK_LH #define ADC_CHANNEL_STICK_LV #define ADC_CHANNEL_STICK_RV @@ -210,25 +219,33 @@ #define ADC_CHANNEL_POT1 LL_ADC_CHANNEL_6 // ADC12_IN6 -> ADC1_IN6 #define ADC_CHANNEL_POT2 LL_ADC_CHANNEL_14 // ADC12_IN14 -> ADC1_IN14 #define ADC_CHANNEL_POT3 LL_ADC_CHANNEL_6 // ADC3_IN6 -> ADC3_IN6 -#define ADC_CHANNEL_EXT1 LL_ADC_CHANNEL_2 // ADC123_IN2 -> ADC3_IN2 (Right stick end pot on PL18EV) -#define ADC_CHANNEL_EXT2 LL_ADC_CHANNEL_4 // ADC3_IN4 -> ADC3_IN4 (Left stick end pot on PL18EV) #define ADC_CHANNEL_SLIDER1 LL_ADC_CHANNEL_7 // ADC3_IN7 -> ADC3_IN7 #define ADC_CHANNEL_SLIDER2 LL_ADC_CHANNEL_7 // ADC12_IN7 -> ADC1_IN7 -#define ADC_CHANNEL_SWB LL_ADC_CHANNEL_11 // ADC123_IN11 -> ADC1_IN11 -#define ADC_CHANNEL_SWD LL_ADC_CHANNEL_10 // ADC123_IN10 -> ADC1_IN10 -#define ADC_CHANNEL_SWE LL_ADC_CHANNEL_12 // ADC123_IN12 -> ADC1_IN12 +// Right stick end pot on PL18EV +#define ADC_CHANNEL_EXT1 LL_ADC_CHANNEL_2 // ADC123_IN2 -> ADC3_IN2 + +// Left stick end pot on PL18EV +#define ADC_CHANNEL_EXT2 LL_ADC_CHANNEL_4 // ADC3_IN4 -> ADC3_IN4 + +// Analog switches +#define ADC_CHANNEL_SWB LL_ADC_CHANNEL_11 // ADC123_IN11 -> ADC3_IN11 +#define ADC_CHANNEL_SWD LL_ADC_CHANNEL_10 // ADC123_IN10 -> ADC3_IN10 +#define ADC_CHANNEL_SWE LL_ADC_CHANNEL_12 // ADC123_IN12 -> ADC3_IN12 #define ADC_CHANNEL_SWF LL_ADC_CHANNEL_8 // ADC12_IN8 -> ADC1_IN8 #define ADC_CHANNEL_SWG LL_ADC_CHANNEL_9 // ADC12_IN9 -> ADC1_IN9 #define ADC_CHANNEL_SWH LL_ADC_CHANNEL_8 // ADC3_IN8 -> ADC3_IN8 #define ADC_CHANNEL_BATT LL_ADC_CHANNEL_15 // ADC12_IN15 -> ADC1_IN15 -#define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 +// #define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 #define ADC_MAIN ADC1 #define ADC_EXT ADC3 -#define ADC_EXT_CHANNELS \ - { ADC_CHANNEL_POT3, ADC_CHANNEL_EXT1, ADC_CHANNEL_EXT2, ADC_CHANNEL_SLIDER1, ADC_CHANNEL_SWH } + +#define ADC_EXT_CHANNELS \ + { ADC_CHANNEL_POT3, ADC_CHANNEL_SLIDER1, ADC_CHANNEL_EXT1, ADC_CHANNEL_EXT2, \ + ADC_CHANNEL_SWB, ADC_CHANNEL_SWD, ADC_CHANNEL_SWE, ADC_CHANNEL_SWH \ + } #define ADC_SAMPTIME LL_ADC_SAMPLINGTIME_28CYCLES #define ADC_DMA DMA2 @@ -243,14 +260,16 @@ #define ADC_EXT_DMA_STREAM_IRQ DMA2_Stream0_IRQn #define ADC_EXT_DMA_STREAM_IRQHandler DMA2_Stream0_IRQHandler #define ADC_EXT_SAMPTIME LL_ADC_SAMPLINGTIME_28CYCLES + #define ADC_VREF_PREC2 330 #define ADC_DIRECTION { \ 0,0,0,0, /* gimbals */ \ 0,0,0, /* pots */ \ - 0,0, /* sliders */ \ + -1,-1, /* sliders */ \ 0,0, /* ext1 & 2 */ \ - 0,0, /* vbat, rtc_bat */ \ + 0, /* vbat */ \ + /* 0, */ /* rtc_bat */ \ -1, /* SWB */ \ -1, /* SWD */ \ 0, /* SWE */ \ @@ -539,7 +558,7 @@ #define EXTMODULE #define EXTMODULE_PULSES #define EXTMODULE_PWR_GPIO GPIOD -#define EXTMODULE_PWR_GPIO_PIN GPIO_Pin_11 +#define EXTMODULE_PWR_GPIO_PIN GPIO_Pin_11 // PD.11 #define EXTMODULE_RCC_AHB1Periph \ (RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOC | \ RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_DMA2) diff --git a/radio/util/hw_defs/stm32_adc_inputs.jinja b/radio/util/hw_defs/stm32_adc_inputs.jinja index b30ce7d2c2c..fc1103021f5 100644 --- a/radio/util/hw_defs/stm32_adc_inputs.jinja +++ b/radio/util/hw_defs/stm32_adc_inputs.jinja @@ -6,6 +6,7 @@ static const stm32_adc_input_t _ADC_inputs[] = { {% for input in adc_inputs.inputs %} { + // {{ input.name }} // ADC_INPUT_{{ input.type }}, {{ input.gpio if input.gpio else 'nullptr' }}, {{ input.pin if input.gpio else '0' }}, From bb351fc10166ddfb944296f7d94c448cc7aeace2 Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Mon, 25 Sep 2023 17:08:14 +0200 Subject: [PATCH 04/57] feat: use milliseconds for TRACE instead of a float --- radio/src/debug.h | 5 +++-- radio/src/tasks.h | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/radio/src/debug.h b/radio/src/debug.h index aa31b77cf1d..02d3fc52b15 100644 --- a/radio/src/debug.h +++ b/radio/src/debug.h @@ -46,9 +46,10 @@ EXTERN_C(extern volatile uint32_t g_tmr10ms); #define debugPrintf(...) #endif -#define TRACE_TIME_FORMAT "%0.2fs: " -#define TRACE_TIME_VALUE ((float)g_tmr10ms / 100.0) +#define TRACE_TIME_FORMAT "%dms: " +#define TRACE_TIME_VALUE (g_tmr10ms * 10) + #define TRACE_NOCRLF(...) debugPrintf(__VA_ARGS__) #define TRACE(f_, ...) debugPrintf((TRACE_TIME_FORMAT f_ CRLF), TRACE_TIME_VALUE, ##__VA_ARGS__) #define DUMP(data, size) dump(data, size) diff --git a/radio/src/tasks.h b/radio/src/tasks.h index 0e6181e5660..e12829108de 100644 --- a/radio/src/tasks.h +++ b/radio/src/tasks.h @@ -30,8 +30,15 @@ #else #define MENUS_STACK_SIZE 2000 #endif + +#if !defined(DEBUG) #define MIXER_STACK_SIZE 400 #define AUDIO_STACK_SIZE 400 +#else +#define MIXER_STACK_SIZE 512 +#define AUDIO_STACK_SIZE 512 +#endif + #define CLI_STACK_SIZE 1024 // only consumed with CLI build option #if defined(FREE_RTOS) From 573d8b8cb4b9ce6cb2965c1b24f4e9e5c44ceceb Mon Sep 17 00:00:00 2001 From: raphaelcoeffic Date: Tue, 26 Sep 2023 11:45:26 +0200 Subject: [PATCH 05/57] WiP: no internal module --- radio/src/targets/common/arm/CMakeLists.txt | 24 +++++++++++---------- radio/src/targets/pl18/CMakeLists.txt | 4 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/radio/src/targets/common/arm/CMakeLists.txt b/radio/src/targets/common/arm/CMakeLists.txt index a24ee4e8242..e7e353b02e2 100644 --- a/radio/src/targets/common/arm/CMakeLists.txt +++ b/radio/src/targets/common/arm/CMakeLists.txt @@ -22,17 +22,19 @@ option(DEBUG_USB_INTERRUPTS "Count individual USB interrupts" OFF) option(DEBUG_TIMERS "Time critical parts of the code" OFF) option(DEBUG_BLUETOOTH "Debug Bluetooth" OFF) -# option to select the default internal module -#set(DEFAULT_INTERNAL_MODULE NONE CACHE STRING "Default internal module") -set_property(CACHE DEFAULT_INTERNAL_MODULE PROPERTY STRINGS ${INTERNAL_MODULES}) - -# define variables for internal modules -foreach(module ${INTERNAL_MODULES}) - set(INTERNAL_MODULE_${module} ON CACHE BOOL "Support for ${module} internal module") - if (INTERNAL_MODULE_${module}) - message("-- Adding support for ${module} as internal module") - endif() -endforeach() +if(INTERNAL_MODULES) + # option to select the default internal module + #set(DEFAULT_INTERNAL_MODULE NONE CACHE STRING "Default internal module") + set_property(CACHE DEFAULT_INTERNAL_MODULE PROPERTY STRINGS ${INTERNAL_MODULES}) + + # define variables for internal modules + foreach(module ${INTERNAL_MODULES}) + set(INTERNAL_MODULE_${module} ON CACHE BOOL "Support for ${module} internal module") + if (INTERNAL_MODULE_${module}) + message("-- Adding support for ${module} as internal module") + endif() + endforeach() +endif() if(INTERNAL_MODULE_PXX1) add_definitions(-DHARDWARE_INTERNAL_MODULE) diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 9dd2993af6d..6f0989ab149 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -39,8 +39,8 @@ add_definitions(-DSOFTWARE_VOLUME) add_definitions(-DSPI_FLASH) # defines existing internal modules -set(INTERNAL_MODULES MULTI CACHE STRING "Internal modules") -set(DEFAULT_INTERNAL_MODULE MULTIMODULE CACHE STRING "Default internal module") +# set(INTERNAL_MODULES MULTI CACHE STRING "Internal modules") +# set(DEFAULT_INTERNAL_MODULE MULTIMODULE CACHE STRING "Default internal module") set(BITMAPS_TARGET pl18_bitmaps) set(FONTS_TARGET x12_fonts) From e23fad003334acaa5cf06f0488e8f586dff2e497 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Wed, 4 Oct 2023 12:12:03 +0800 Subject: [PATCH 06/57] Update cst340 driver to generic EXTI driver. --- radio/src/targets/pl18/CMakeLists.txt | 9 +- radio/src/targets/pl18/hal.h | 39 +++--- radio/src/targets/pl18/tp_cst340.cpp | 187 +++++++++----------------- 3 files changed, 84 insertions(+), 151 deletions(-) diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 6f0989ab149..78b5806e436 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -19,8 +19,6 @@ set(HARDWARE_EXTERNAL_MODULE YES) set(WIRELESS_CHARGER YES) set(TARGET_DIR pl18) -set(LINKER_SCRIPT targets/pl18/stm32f4_flash_bootloader.ld) - set(RTC_BACKUP_RAM YES) set(PPM_LIMITS_SYMETRICAL YES) # for size report script @@ -100,10 +98,13 @@ set(SRC set(GVAR_SCREEN model_gvars.cpp) if(BOOTLOADER) + set(LINKER_SCRIPT targets/pl18/stm32f4_flash_bootloader.ld) set(FIRMWARE_TARGET_SRC ${FIRMWARE_TARGET_SRC} ../common/arm/loadboot.cpp ) +else() + set(LINKER_SCRIPT targets/pl18/stm32f4_flash.ld) endif() set(SRC @@ -133,12 +134,12 @@ set(FIRMWARE_TARGET_SRC set(FIRMWARE_SRC ${FIRMWARE_SRC} hal/adc_driver.cpp - targets/common/arm/stm32/stm32_adc.cpp + targets/common/arm/stm32/stm32_exti_driver.cpp targets/common/arm/stm32/timers_driver.cpp targets/common/arm/stm32/stm32_pulse_driver.cpp targets/common/arm/stm32/stm32_usart_driver.cpp - targets/common/arm/stm32/trainer_driver.cpp targets/common/arm/stm32/pwr_driver.cpp + targets/common/arm/stm32/trainer_driver.cpp targets/common/arm/stm32/audio_dac_driver.cpp targets/common/arm/stm32/spi_flash.cpp targets/common/arm/stm32/diskio_spi_flash.cpp diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 122cff3169c..85f1ba6db79 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -464,36 +464,29 @@ #define AUDIO_TIMER TIM6 #define AUDIO_DMA DMA1 -// I2C Bus - Touch -#define I2C_B1_RCC_AHB1Periph RCC_AHB1Periph_GPIOB -#define I2C_B1_RCC_APB1Periph RCC_APB1Periph_I2C1 +// I2C Bus #define I2C_B1 I2C1 #define I2C_B1_GPIO GPIOB #define I2C_B1_SDA_GPIO_PIN LL_GPIO_PIN_7 // PB.07 #define I2C_B1_SCL_GPIO_PIN LL_GPIO_PIN_8 // PB.08 -#define I2C_B1_GPIO_AF GPIO_AF4_I2C1 -//#define I2C_B1_SDA_GPIO_PinSource GPIO_PinSource7 -//#define I2C_B1_SCL_GPIO_PinSource GPIO_PinSource8 -//#define I2C_B1_CLK_RATE 100000 +#define I2C_B1_GPIO_AF LL_GPIO_AF_4 -#define TOUCH_I2C_BUS I2C_Bus_1 -#define TOUCH_I2C_CLK_RATE 400000 - - -#define TOUCH_RST_RCC_AHB1Periph RCC_AHB1Periph_GPIOB -#define TOUCH_RST_GPIO GPIOB -#define TOUCH_RST_GPIO_PIN LL_GPIO_PIN_12 // PB.12 - -#define TOUCH_INT_RCC_AHB1Periph RCC_AHB1Periph_GPIOB +// Touch +#define TOUCH_I2C_BUS I2C_Bus_1 +#define TOUCH_I2C_CLK_RATE 400000 #define TOUCH_INT_GPIO GPIOB #define TOUCH_INT_GPIO_PIN LL_GPIO_PIN_9 // PB.09 -#define TOUCH_INT_EXTI_LINE1 LL_EXTI_LINE_9 - -#define TOUCH_INT_EXTI_Line LL_EXTI_LINE_9 -#define TOUCH_INT_EXTI_Port LL_SYSCFG_EXTI_PORTB -#define TOUCH_INT_EXTI_SysCfgLine LL_SYSCFG_EXTI_LINE9 -#define TOUCH_INT_EXTI_IRQn EXTI9_5_IRQn -#define TOUCH_INT_EXTI_IRQHandler EXTI9_5_IRQHandler +#define TOUCH_RST_GPIO GPIOB +#define TOUCH_RST_GPIO_PIN LL_GPIO_PIN_12 // PB.12 +#define TOUCH_INT_EXTI_Line LL_EXTI_LINE_9 +#define TOUCH_INT_EXTI_Port LL_SYSCFG_EXTI_PORTB +#define TOUCH_INT_EXTI_SysCfgLine LL_SYSCFG_EXTI_LINE9 + +// TOUCH_INT_EXTI IRQ +#if !defined(USE_EXTI9_5_IRQ) + #define USE_EXTI9_5_IRQ + #define EXTI9_5_IRQ_Priority 9 +#endif // Haptic: TIM1_CH1 #define HAPTIC_PWM diff --git a/radio/src/targets/pl18/tp_cst340.cpp b/radio/src/targets/pl18/tp_cst340.cpp index a5ee1b74cbd..07a7ae5b575 100644 --- a/radio/src/targets/pl18/tp_cst340.cpp +++ b/radio/src/targets/pl18/tp_cst340.cpp @@ -21,26 +21,21 @@ #include "stm32_hal_ll.h" #include "stm32_hal.h" -#include "hal.h" - #include "stm32_i2c_driver.h" +#include "stm32_gpio_driver.h" +#include "stm32_exti_driver.h" + +#include "hal.h" #include "timers_driver.h" #include "delays_driver.h" #include "tp_cst340.h" #include "debug.h" -volatile static bool touchEventOccured; - -#define TOUCH_RCC_AHB1Periph RCC_AHB1Periph_GPIOB -#define TOUCH_RCC_APB1Periph RCC_APB1Periph_I2C1 -#define TOUCH_GPIO I2C_B1_GPIO -#define TOUCH_SCL_GPIO_PIN I2C_B1_SCL_GPIO_PIN // PB.08 -#define TOUCH_SDA_GPIO_PIN I2C_B1_SDA_GPIO_PIN // PB.09 - #define TOUCH_FT6236_I2C_ADDRESS (0x70>>1) #define TOUCH_CST340_I2C_ADDRESS 0x1A +volatile static bool touchEventOccured; enum TouchControllers {TC_NONE, TC_FT6236, TC_CST340}; TouchControllers touchController = TC_NONE; @@ -63,129 +58,69 @@ static const TouchControllerDescriptor *tc = nullptr; static TouchState internalTouchState = {}; -void I2C_FreeBus() +static void _cst340_exti_isr(void) { - LL_GPIO_InitTypeDef gpioInit; - LL_GPIO_StructInit(&gpioInit); - - // reset i2c bus by setting clk as output and sending manual clock pulses - gpioInit.Pin = TOUCH_SCL_GPIO_PIN; - gpioInit.Mode = LL_GPIO_MODE_OUTPUT; - gpioInit.OutputType = LL_GPIO_OUTPUT_PUSHPULL; - gpioInit.Pull = LL_GPIO_PULL_NO; - gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; - LL_GPIO_Init(TOUCH_GPIO, &gpioInit); - - gpioInit.Pin = TOUCH_SDA_GPIO_PIN; - gpioInit.Mode = LL_GPIO_MODE_INPUT; - gpioInit.OutputType = LL_GPIO_OUTPUT_OPENDRAIN; - gpioInit.Pull = LL_GPIO_PULL_UP; - gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; - LL_GPIO_Init(TOUCH_GPIO, &gpioInit); - - //send 100khz clock train for some 100ms - tmr10ms_t until = get_tmr10ms() + 11; - while (get_tmr10ms() < until) { - if (LL_GPIO_IsInputPinSet(TOUCH_GPIO, TOUCH_SDA_GPIO_PIN)) { - TRACE("touch: i2c free again\n"); - break; - } - TRACE("FREEEEE"); - LL_GPIO_ResetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); - delay_us(10); - LL_GPIO_SetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); - delay_us(10); - } - - //send stop condition: - gpioInit.Pin = TOUCH_SDA_GPIO_PIN; - gpioInit.Mode = LL_GPIO_MODE_OUTPUT; - gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; - LL_GPIO_Init(TOUCH_GPIO, &gpioInit); - - //clock is low - LL_GPIO_ResetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); - delay_us(10); - //sda = lo - LL_GPIO_SetOutputPin(TOUCH_GPIO, TOUCH_SDA_GPIO_PIN); - delay_us(10); - //clock goes high - LL_GPIO_ResetOutputPin(TOUCH_GPIO, TOUCH_SCL_GPIO_PIN); - delay_us(10); - //sda = hi - LL_GPIO_SetOutputPin(TOUCH_GPIO, TOUCH_SDA_GPIO_PIN); - delay_us(10); - TRACE("FREE BUS"); + touchEventOccured = true; } -// void Touch_DeInit() -// { -// I2C_DeInit(I2C_B1); -// __HAL_RCC_I2C1_FORCE_RESET(); -// delay_ms(150); -// __HAL_RCC_I2C1_RELEASE_RESET(); -// } - -static int _enable_gpio_clock(GPIO_TypeDef *GPIOx) +static void TOUCH_AF_ExtiStop(void) { - if (GPIOx == GPIOB) - __HAL_RCC_GPIOB_CLK_ENABLE(); - else - return -1; - - return 0; + stm32_exti_disable(TOUCH_INT_EXTI_Line); } -void I2C_Init() +static void TOUCH_AF_ExtiConfig(void) { - stm32_i2c_deinit(TOUCH_I2C_BUS); - - __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_SYSCFG_CLK_ENABLE(); + LL_SYSCFG_SetEXTISource(TOUCH_INT_EXTI_Port, TOUCH_INT_EXTI_SysCfgLine); - __HAL_RCC_I2C1_CLK_ENABLE(); - __HAL_RCC_I2C1_CLK_DISABLE(); - - I2C_FreeBus(); - - stm32_i2c_init(TOUCH_I2C_BUS, TOUCH_I2C_CLK_RATE); + stm32_exti_enable(TOUCH_INT_EXTI_Line, + LL_EXTI_TRIGGER_FALLING, + _cst340_exti_isr); +} +static void TOUCH_AF_GPIOConfig(void) +{ LL_GPIO_InitTypeDef gpioInit; LL_GPIO_StructInit(&gpioInit); - _enable_gpio_clock(TOUCH_RST_GPIO); - _enable_gpio_clock(TOUCH_INT_GPIO); - - gpioInit.Pin = TOUCH_RST_GPIO_PIN; + stm32_gpio_enable_clock(TOUCH_RST_GPIO); + stm32_gpio_enable_clock(TOUCH_INT_GPIO); + gpioInit.Mode = LL_GPIO_MODE_OUTPUT; gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; gpioInit.OutputType = LL_GPIO_OUTPUT_PUSHPULL; - gpioInit.Pull = LL_GPIO_PULL_UP; + gpioInit.Pull = LL_GPIO_PULL_NO; + + gpioInit.Pin = TOUCH_RST_GPIO_PIN; LL_GPIO_Init(TOUCH_RST_GPIO, &gpioInit); + LL_GPIO_SetOutputPin(TOUCH_RST_GPIO, TOUCH_RST_GPIO_PIN); - //ext interupt gpioInit.Pin = TOUCH_INT_GPIO_PIN; gpioInit.Mode = LL_GPIO_MODE_INPUT; gpioInit.Pull = LL_GPIO_PULL_UP; - gpioInit.Speed = LL_GPIO_SPEED_FREQ_HIGH; gpioInit.OutputType = LL_GPIO_OUTPUT_OPENDRAIN; LL_GPIO_Init(TOUCH_INT_GPIO, &gpioInit); + LL_GPIO_SetOutputPin(TOUCH_INT_GPIO, TOUCH_INT_GPIO_PIN); +} - LL_SYSCFG_SetEXTISource(TOUCH_INT_EXTI_Port, TOUCH_INT_EXTI_SysCfgLine); - - LL_EXTI_InitTypeDef extiInit; - LL_EXTI_StructInit(&extiInit); - - extiInit.Line_0_31 = TOUCH_INT_EXTI_Line; - extiInit.Mode = LL_EXTI_MODE_IT; - extiInit.Trigger = LL_EXTI_TRIGGER_FALLING; - extiInit.LineCommand = ENABLE; - LL_EXTI_Init(&extiInit); +void I2C_Init_Radio(void) +{ + TRACE("CST340 I2C Init"); - NVIC_SetPriority(TOUCH_INT_EXTI_IRQn, 8); - NVIC_EnableIRQ(TOUCH_INT_EXTI_IRQn); + if (stm32_i2c_init(TOUCH_I2C_BUS, TOUCH_I2C_CLK_RATE) < 0) { + TRACE("CST340 ERROR: stm32_i2c_init failed"); + return; + } } +// void Touch_DeInit() +// { +// I2C_DeInit(I2C_B1); +// __HAL_RCC_I2C1_FORCE_RESET(); +// delay_ms(150); +// __HAL_RCC_I2C1_RELEASE_RESET(); +// } + #define I2C_TIMEOUT_MAX 5 // 5 ms bool touch_i2c_read(uint8_t addr, uint8_t reg, uint8_t * data, uint8_t len) @@ -226,13 +161,14 @@ static void TS_IO_Write(uint8_t addr, uint8_t reg, uint8_t data) static uint8_t TS_IO_Read(uint8_t addr, uint8_t reg) { - uint8_t retult; + uint8_t result; uint8_t tryCount = 3; - while (!touch_i2c_read(addr, reg, &retult, 1)) { + while (!touch_i2c_read(addr, reg, &result, 1)) { if (--tryCount == 0) break; - I2C_Init(); + I2C_Init_Radio(); +// I2C_Init(); } - return retult; + return result; } static uint16_t TS_IO_ReadMultiple(uint8_t addr, uint8_t reg, uint8_t * buffer, uint16_t length) @@ -240,7 +176,8 @@ static uint16_t TS_IO_ReadMultiple(uint8_t addr, uint8_t reg, uint8_t * buffer, uint8_t tryCount = 3; while (!touch_i2c_read(addr, reg, buffer, length)) { if (--tryCount == 0) break; - I2C_Init(); + I2C_Init_Radio(); +// I2C_Init(); } return 1; } @@ -439,6 +376,9 @@ static uint8_t cst340_TS_DetectTouch() { uint8_t nbTouch; uint8_t reg = TS_IO_Read(TOUCH_CST340_I2C_ADDRESS, CST340_FINGER1_REG); +#if defined(DEBUG) + TRACE("cst340_TS_DetectTouch: reg=%d", reg); +#endif if( reg == 0x06 ) nbTouch = 1; else @@ -487,9 +427,12 @@ void detectTouchController() void TouchInit() { - I2C_Init(); + TOUCH_AF_GPIOConfig(); // SET RST=OUT INT=IN INT=HIGH + I2C_Init_Radio(); TouchReset(); detectTouchController(); + TOUCH_AF_ExtiConfig(); + tc->printDebugInfo(); } @@ -500,6 +443,9 @@ void handleTouch() uint32_t tEvent = 0; tc->read(&touchX, &touchY, &tEvent); +#if defined(DEBUG) + TRACE("handleTouch: touchX=%d, touchY=%d, tEvent=%d", touchX, touchY, tEvent); +#endif // touch sensor is rotated by 90 deg unsigned short tmp = touchY; touchY = 319 - touchX; @@ -538,18 +484,6 @@ void handleTouch() } } -#if !defined(TOUCH_INT_EXTI_IRQHandler) - #error "TOUCH_INT_EXTI_IRQHandler is not defined" -#endif -extern "C" void TOUCH_INT_EXTI_IRQHandler(void) -{ - if (LL_EXTI_IsEnabledIT_0_31(TOUCH_INT_EXTI_Line) && - LL_EXTI_IsActiveFlag_0_31(TOUCH_INT_EXTI_Line)) { - touchEventOccured = true; - LL_EXTI_ClearFlag_0_31(TOUCH_INT_EXTI_Line); - } -} - bool touchPanelEventOccured() { return touchEventOccured; @@ -595,6 +529,11 @@ TouchState touchPanelRead() internalTouchState.deltaY = 0; if(internalTouchState.event == TE_UP || internalTouchState.event == TE_SLIDE_END) internalTouchState.event = TE_NONE; + +#if defined(DEBUG) + TRACE("%s: Event = %d", touchController == TC_CST340 ? "CST340" : "FT6236", ret.event); +#endif + return ret; } From 192db005314d73fa521dd94249ec99b2495f392c Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 6 Oct 2023 16:38:47 +0800 Subject: [PATCH 07/57] Fixed rebase problems due to changes in PR #3894. --- radio/src/storage/yaml/yaml_datastructs_pl18.cpp | 13 +++++++++++-- radio/src/targets/pl18/CMakeLists.txt | 2 +- radio/src/targets/pl18/bootloader/boot_menu.cpp | 2 +- radio/src/targets/pl18/hal.h | 2 ++ radio/src/targets/pl18/key_driver.cpp | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index b330a845a9e..1bc5aa32205 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -4,6 +4,13 @@ // Enums first // +const struct YamlIdStr enum_HatsMode[] = { + { HATSMODE_TRIMS_ONLY, "TRIMS_ONLY" }, + { HATSMODE_KEYS_ONLY, "KEYS_ONLY" }, + { HATSMODE_SWITCHABLE, "SWITCHABLE" }, + { HATSMODE_GLOBAL, "GLOBAL" }, + { 0, NULL } +}; const struct YamlIdStr enum_BacklightMode[] = { { e_backlight_mode_off, "backlight_mode_off" }, { e_backlight_mode_keys, "backlight_mode_keys" }, @@ -284,7 +291,8 @@ static const struct YamlNode struct_CustomFunctionData[] = { static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "manuallyEdited", 1 ), YAML_SIGNED( "timezoneMinutes", 3 ), - YAML_PADDING( 4 ), + YAML_ENUM("hatsMode", 2, enum_HatsMode), + YAML_PADDING( 2 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 22, struct_CalibData, NULL), @@ -820,7 +828,8 @@ static const struct YamlNode struct_ModelData[] = { YAML_UNSIGNED( "disableTelemetryWarning", 1 ), YAML_UNSIGNED( "showInstanceIds", 1 ), YAML_UNSIGNED( "checklistInteractive", 1 ), - YAML_PADDING( 4 ), + YAML_ENUM("hatsMode", 2, enum_HatsMode), + YAML_PADDING( 2 ), YAML_SIGNED( "customThrottleWarningPosition", 8 ), YAML_UNSIGNED( "beepANACenter", 16 ), YAML_ARRAY("mixData", 160, 64, struct_MixData, NULL), diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 78b5806e436..9e53807485e 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -56,7 +56,7 @@ add_definitions( -DSTM32F429_439xx -DSTM32F429xx -DSDRAM -DCOLORLCD -DLIBOPENUI -DHARDWARE_TOUCH -DHARDWARE_KEYS - -DSOFTWARE_KEYBOARD -DUSE_TRIMS_AS_BUTTONS) + -DSOFTWARE_KEYBOARD -DUSE_HATS_AS_KEYS) set(SDRAM ON) diff --git a/radio/src/targets/pl18/bootloader/boot_menu.cpp b/radio/src/targets/pl18/bootloader/boot_menu.cpp index 1848732b404..951796ef37d 100644 --- a/radio/src/targets/pl18/bootloader/boot_menu.cpp +++ b/radio/src/targets/pl18/bootloader/boot_menu.cpp @@ -63,7 +63,7 @@ void bootloaderInitScreen() lcdInitDisplayDriver(); backlightInit(); backlightEnable(100); - setTrimsAsButtons(true); + setHatsAsKeys(true); } static void bootloaderDrawTitle(const char* text) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 85f1ba6db79..3c8dcbee05d 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -619,6 +619,8 @@ #define TRAINER_GPIO_AF LL_GPIO_AF_2 #define TRAINER_TIMER_FREQ (PERI1_FREQUENCY * TIMER_MULT_APB1) +//ROTARY emulation for trims as buttons +#define ROTARY_ENCODER_NAVIGATION //BLUETOOTH #define BLUETOOTH_ON_RCC_AHB1Periph RCC_AHB1Periph_GPIOI diff --git a/radio/src/targets/pl18/key_driver.cpp b/radio/src/targets/pl18/key_driver.cpp index 7c768fe6ddf..cd09c374d71 100644 --- a/radio/src/targets/pl18/key_driver.cpp +++ b/radio/src/targets/pl18/key_driver.cpp @@ -191,7 +191,7 @@ uint32_t readKeys() { uint32_t result = 0; - if (getTrimsAsButtons()) { + if (getHatsAsKeys()) { uint32_t mkeys = _readKeyMatrix(); if (mkeys & (1 << TR4D)) result |= 1 << KEY_ENTER; if (mkeys & (1 << TR4U)) result |= 1 << KEY_EXIT; From 2e2bc98d9186d40a1020cdaa7acf040279094250 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 9 Oct 2023 13:14:25 +0800 Subject: [PATCH 08/57] Fixed rebase problems due to changes in PR #4139. --- .../src/targets/pl18/bootloader/boot_menu.cpp | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/radio/src/targets/pl18/bootloader/boot_menu.cpp b/radio/src/targets/pl18/bootloader/boot_menu.cpp index 951796ef37d..9b7333b27b4 100644 --- a/radio/src/targets/pl18/bootloader/boot_menu.cpp +++ b/radio/src/targets/pl18/bootloader/boot_menu.cpp @@ -45,11 +45,6 @@ const uint8_t __bmp_usb_plugged[] { }; LZ4Bitmap BMP_USB_PLUGGED(BMP_ARGB4444, __bmp_usb_plugged); -const uint8_t __bmp_background[] { -#include "bmp_background.lbm" -}; -LZ4Bitmap BMP_BACKGROUND(BMP_ARGB4444, __bmp_background); - #define BL_GREEN COLOR2FLAGS(RGB(73, 219, 62)) #define BL_RED COLOR2FLAGS(RGB(229, 32, 30)) #define BL_BACKGROUND COLOR2FLAGS(BLACK) @@ -79,30 +74,7 @@ static void bootloaderDrawFooter() static void bootloaderDrawBackground() { - // we have plenty of memory, let's cache that background - static BitmapBuffer* _background = nullptr; - - if (!_background) { - _background = new BitmapBuffer(BMP_RGB565, LCD_W, LCD_H); - - for (int i=0; idrawBitmap(i, j, bg_bmp); - } - } - _background->drawFilledRect(0, 0, LCD_W, LCD_H, SOLID, - COLOR2FLAGS(BLACK), OPACITY(4)); - } - - if (_background) { - lcd->drawBitmap(0, 0, _background); - lcd->drawFilledRect(0, 0, LCD_W, LCD_H, SOLID, - COLOR2FLAGS(BLACK), OPACITY(4)); - } - else { lcd->clear(BL_BACKGROUND); - } } void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) From df1b9e9811003ddd52f7c830bbf87893be938380 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 6 Oct 2023 22:42:37 +0800 Subject: [PATCH 09/57] Workaround for interrupt occurs when touch panel is idle. --- radio/src/targets/pl18/tp_cst340.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/radio/src/targets/pl18/tp_cst340.cpp b/radio/src/targets/pl18/tp_cst340.cpp index 07a7ae5b575..b18453e09dc 100644 --- a/radio/src/targets/pl18/tp_cst340.cpp +++ b/radio/src/targets/pl18/tp_cst340.cpp @@ -376,9 +376,6 @@ static uint8_t cst340_TS_DetectTouch() { uint8_t nbTouch; uint8_t reg = TS_IO_Read(TOUCH_CST340_I2C_ADDRESS, CST340_FINGER1_REG); -#if defined(DEBUG) - TRACE("cst340_TS_DetectTouch: reg=%d", reg); -#endif if( reg == 0x06 ) nbTouch = 1; else @@ -484,9 +481,24 @@ void handleTouch() } } +static bool lastHasTouch = false; bool touchPanelEventOccured() { - return touchEventOccured; + bool result = touchEventOccured; + uint8_t hasTouch = false; + if (touchEventOccured) { + hasTouch = tc->detectTouch(); + if (!hasTouch && !lastHasTouch) { + touchEventOccured = false; + result = false; + } + lastHasTouch = hasTouch; + } + +#if defined(DEBUG) + TRACE("TouchEvent: %d, %d, %d, %d", touchEventOccured, lastHasTouch, hasTouch, result); +#endif + return result; } TouchState touchPanelRead() From ced26481d223e8024be0e6878024f9339f4d7cd7 Mon Sep 17 00:00:00 2001 From: Xy201207 Date: Wed, 11 Oct 2023 15:34:59 +0800 Subject: [PATCH 10/57] Fixed some LCD bugs in PL18 --- radio/src/targets/pl18/lcd_driver.cpp | 58 ++++++++++++++++----------- radio/src/targets/pl18/tp_cst340.cpp | 5 +++ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/radio/src/targets/pl18/lcd_driver.cpp b/radio/src/targets/pl18/lcd_driver.cpp index 4f623a64259..ca3f9cf79ce 100644 --- a/radio/src/targets/pl18/lcd_driver.cpp +++ b/radio/src/targets/pl18/lcd_driver.cpp @@ -27,8 +27,12 @@ #include "stm32f4xx_ltdc.h" #include "stm32f4xx_dma2d.h" -#define LCD_PHYS_W 480 -#define LCD_PHYS_H 320 +#pragma GCC push_options +#pragma GCC optimize("O0") // FOR DEBUG + +uint8_t TouchControllerType = 0; //0:cst340; 1 ft6236 +static volatile uint16_t lcd_phys_w = 320; +static volatile uint16_t lcd_phys_h = 480; static volatile uint8_t _frame_addr_reloaded = 0; @@ -1295,7 +1299,9 @@ void LCD_ST7796S_Init(void) { delay_ms(120); - lcdWriteCommand( 0x21 ); + if( !TouchControllerType ) { + lcdWriteCommand( 0x21 ); + } LCD_ST7796S_On(); } @@ -2775,13 +2781,13 @@ void LCD_Init_LTDC() { /* Configure accumulated vertical back porch */ LTDC_InitStruct.LTDC_AccumulatedVBP = VBP; /* Configure accumulated active width */ - LTDC_InitStruct.LTDC_AccumulatedActiveW = LCD_PHYS_W + HBP; + LTDC_InitStruct.LTDC_AccumulatedActiveW = lcd_phys_w + HBP; /* Configure accumulated active height */ - LTDC_InitStruct.LTDC_AccumulatedActiveH = LCD_PHYS_H + VBP; + LTDC_InitStruct.LTDC_AccumulatedActiveH = lcd_phys_h + VBP; /* Configure total width */ - LTDC_InitStruct.LTDC_TotalWidth = LCD_PHYS_W + HBP + HFP; + LTDC_InitStruct.LTDC_TotalWidth = lcd_phys_w + HBP + HFP; /* Configure total height */ - LTDC_InitStruct.LTDC_TotalHeigh = LCD_PHYS_H + VBP + VFP; + LTDC_InitStruct.LTDC_TotalHeigh = lcd_phys_h + VBP + VFP; LTDC_Init(<DC_InitStruct); @@ -2791,7 +2797,7 @@ void LCD_Init_LTDC() { // Trigger on last line - LTDC_LIPConfig(LCD_PHYS_H); + LTDC_LIPConfig(lcd_phys_h); LTDC_ITConfig(LTDC_IER_LIE, ENABLE); } @@ -2805,9 +2811,9 @@ void LCD_LayerInit() { Vertical start = vertical synchronization + vertical back porch = 4 Vertical stop = Vertical start + window height -1 = 4 + 320 -1 */ LTDC_Layer_InitStruct.LTDC_HorizontalStart = HBP + 1; - LTDC_Layer_InitStruct.LTDC_HorizontalStop = (LCD_PHYS_W + HBP); + LTDC_Layer_InitStruct.LTDC_HorizontalStop = (lcd_phys_w + HBP); LTDC_Layer_InitStruct.LTDC_VerticalStart = VBP + 1; - LTDC_Layer_InitStruct.LTDC_VerticalStop = (LCD_PHYS_H + VBP); + LTDC_Layer_InitStruct.LTDC_VerticalStop = (lcd_phys_h + VBP); /* Pixel Format configuration*/ LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_RGB565; @@ -2828,14 +2834,14 @@ void LCD_LayerInit() { Active high width = LCD_W number of bytes per pixel = 2 (pixel_format : RGB565) */ - LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((LCD_PHYS_W * 2) + 3); + LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((lcd_phys_w * 2) + 3); /* the pitch is the increment from the start of one line of pixels to the start of the next line in bytes, then : Pitch = Active high width x number of bytes per pixel */ - LTDC_Layer_InitStruct.LTDC_CFBPitch = (LCD_PHYS_W * 2); + LTDC_Layer_InitStruct.LTDC_CFBPitch = (lcd_phys_w * 2); /* Configure the number of lines */ - LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_PHYS_H; + LTDC_Layer_InitStruct.LTDC_CFBLineNumber = lcd_phys_h; /* Start Address configuration : the LCD Frame buffer is defined on SDRAM w/ Offset */ uint32_t layer_address = (uint32_t)lcdFront->getData(); @@ -2893,6 +2899,9 @@ void lcdInit(void) lcdOffFunction = LCD_ILI9488_Off; lcdOnFunction = LCD_ILI9488_On; lcdPixelClock = 12000000; + + lcd_phys_w = 480; + lcd_phys_h = 320; } else if (LCD_HX8357D_ReadID() == LCD_HX8357D_ID) { TRACE("LCD INIT: HX8357D"); boardLcdType = "HX8357D"; @@ -2900,23 +2909,21 @@ void lcdInit(void) lcdOffFunction = LCD_HX8357D_Off; lcdOnFunction = LCD_HX8357D_On; lcdPixelClock = 12000000; - } else if (LCD_ST7796S_ReadID() == LCD_ST7796S_ID || 1) { + } else if (LCD_ST7796S_ReadID() == LCD_ST7796S_ID ) { TRACE("LCD INIT (default): ST7796S"); boardLcdType = "ST7796S"; lcdInitFunction = LCD_ST7796S_Init; lcdOffFunction = LCD_ST7796S_Off; lcdOnFunction = LCD_ST7796S_On; lcdPixelClock = 14500000; - /*} else { // NT35310 can not be detected - TRACE("LCD INIT (default): NT35310"); - boardLcdType = "NT35310"; - lcdInitFunction = LCD_NT35310_Init; - lcdOffFunction = LCD_NT35310_Off; - lcdOnFunction = LCD_NT35310_On; - lcdPixelClock = 12500000;*/ -/* } else { - TRACE("LCD INIT: unknown LCD controller"); - boardLcdType = "unknown";*/ + } + else{ + TRACE("LCD INIT (default): ST7796S"); + boardLcdType = "ST7796S"; + lcdInitFunction = LCD_ST7796S_Init; + lcdOffFunction = LCD_ST7796S_Off; + lcdOnFunction = LCD_ST7796S_On; + lcdPixelClock = 12000000; } lcdInitFunction(); @@ -3097,3 +3104,6 @@ extern "C" void LTDC_IRQHandler(void) _frame_addr_reloaded = 1; } + +#pragma GCC pop_options + diff --git a/radio/src/targets/pl18/tp_cst340.cpp b/radio/src/targets/pl18/tp_cst340.cpp index b18453e09dc..c8e633ff3cf 100644 --- a/radio/src/targets/pl18/tp_cst340.cpp +++ b/radio/src/targets/pl18/tp_cst340.cpp @@ -35,6 +35,9 @@ #define TOUCH_FT6236_I2C_ADDRESS (0x70>>1) #define TOUCH_CST340_I2C_ADDRESS 0x1A + +extern uint8_t TouchControllerType; + volatile static bool touchEventOccured; enum TouchControllers {TC_NONE, TC_FT6236, TC_CST340}; TouchControllers touchController = TC_NONE; @@ -414,9 +417,11 @@ void detectTouchController() { if( stm32_i2c_is_dev_ready(TOUCH_I2C_BUS, TOUCH_CST340_I2C_ADDRESS, 3, 5) == 0) { + TouchControllerType = 0; touchController = TC_CST340; tc = &CST340; } else { + TouchControllerType = 1; touchController = TC_FT6236; tc = &FT6236; } From e4fc082f81c27ee68afc801a048fe9a9461fa4be Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 3 Oct 2023 12:40:28 +0800 Subject: [PATCH 11/57] Enable the support of UART7 internal module. --- radio/src/targets/pl18/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 9e53807485e..3252ab31459 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -37,8 +37,8 @@ add_definitions(-DSOFTWARE_VOLUME) add_definitions(-DSPI_FLASH) # defines existing internal modules -# set(INTERNAL_MODULES MULTI CACHE STRING "Internal modules") -# set(DEFAULT_INTERNAL_MODULE MULTIMODULE CACHE STRING "Default internal module") +set(INTERNAL_MODULES MULTI CACHE STRING "Internal modules") +set(DEFAULT_INTERNAL_MODULE MULTIMODULE CACHE STRING "Default internal module") set(BITMAPS_TARGET pl18_bitmaps) set(FONTS_TARGET x12_fonts) From debdc3dd9e41ad229e6bf6383e49e8f813b85d30 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 13 Oct 2023 12:14:36 +0800 Subject: [PATCH 12/57] Fixed rebase problems due to changes in PR #3814. --- radio/src/targets/pl18/board.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radio/src/targets/pl18/board.cpp b/radio/src/targets/pl18/board.cpp index 1483406932e..5edcff91c2a 100644 --- a/radio/src/targets/pl18/board.cpp +++ b/radio/src/targets/pl18/board.cpp @@ -145,7 +145,8 @@ void boardInit() #endif #if defined(DEBUG) - serialInit(SP_AUX1, UART_MODE_DEBUG); + serialSetMode(SP_AUX1, UART_MODE_DEBUG); // indicate AUX1 is used + serialInit(SP_AUX1, UART_MODE_DEBUG); // early AUX1 init #endif TRACE("\nPL18 board started :)"); From ce5051b5eeac468ee65d1480ccdb361941d42511 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Sat, 14 Oct 2023 08:32:25 +0800 Subject: [PATCH 13/57] Fixed rebase problems due to changes in PR #4104. --- radio/src/targets/pl18/libopenui_config.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/radio/src/targets/pl18/libopenui_config.h b/radio/src/targets/pl18/libopenui_config.h index 4c0e71b2e0e..90e0d3b6eca 100644 --- a/radio/src/targets/pl18/libopenui_config.h +++ b/radio/src/targets/pl18/libopenui_config.h @@ -32,11 +32,7 @@ constexpr uint32_t ALERT_MESSAGE_LEFT = ALERT_TITLE_LEFT; constexpr coord_t INPUT_EDIT_CURVE_WIDTH = 132; constexpr coord_t INPUT_EDIT_CURVE_HEIGHT = INPUT_EDIT_CURVE_WIDTH; -constexpr coord_t MENUS_LINE_HEIGHT = 30; -constexpr coord_t MENUS_WIDTH = 200; -constexpr coord_t MENUS_MAX_HEIGHT = LCD_H * 0.9; - -constexpr rect_t MENUS_TOOLBAR_RECT = { 100, 51, 30, 209 }; +constexpr coord_t MENUS_MAX_HEIGHT = (MENUS_LINE_HEIGHT * 8) + 8; // Disable rotary encoder, as the PL18 does not have one #define ROTARY_ENCODER_SPEED() 0 From 9c9bfae5d534c19ced06d71a5c04f106ee2c6a3b Mon Sep 17 00:00:00 2001 From: Richard Li Date: Sat, 14 Oct 2023 08:49:35 +0800 Subject: [PATCH 14/57] Fixed compile error in B/W radios. --- radio/src/keys.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/radio/src/keys.cpp b/radio/src/keys.cpp index c997b03cc93..54e3f3a238c 100644 --- a/radio/src/keys.cpp +++ b/radio/src/keys.cpp @@ -32,11 +32,6 @@ #include "hal/rotary_encoder.h" #include "dataconstants.h" -// required by watchdog macro.. -#if !defined(SIMU) -#include "stm32_cmsis.h" -#endif - // long key press minimum duration (x10ms), // must be less than KEY_REPEAT_DELAY #define KEY_LONG_DELAY 32 From 627d439450c5831dec82d28bf0955bae9961f21c Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Mon, 16 Oct 2023 07:36:55 +1000 Subject: [PATCH 15/57] fix: T20 Trims --- radio/src/targets/taranis/board.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/radio/src/targets/taranis/board.h b/radio/src/targets/taranis/board.h index 26bfff7d1d1..d5400c773d3 100644 --- a/radio/src/targets/taranis/board.h +++ b/radio/src/targets/taranis/board.h @@ -479,4 +479,12 @@ void setTopBatteryValue(uint32_t volts); #define VOLTAGE_DROP 20 #endif +#if defined(RADIO_T20) +#define NUM_TRIMS 8 +#else +#define NUM_TRIMS 4 +#endif + +#define NUM_TRIMS_KEYS (NUM_TRIMS * 2) + #endif // _BOARD_H_ From 7165b09d86eb4a2518c34fed8c2b15c6ae87346c Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Mon, 16 Oct 2023 07:37:44 +1000 Subject: [PATCH 16/57] fix: PL18 tests --- radio/src/tests/bitmap_480x320.png | Bin 0 -> 7162 bytes radio/src/tests/clipping_480x320.png | Bin 0 -> 5121 bytes radio/src/tests/lines_480x320.png | Bin 0 -> 5734 bytes radio/src/tests/masks_480x320.png | Bin 0 -> 16592 bytes radio/src/tests/primitives_EN_480x320.png | Bin 0 -> 10452 bytes radio/src/tests/transparency_EN_480x320.png | Bin 0 -> 10019 bytes radio/src/tests/vline_480x320.png | Bin 0 -> 5186 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 radio/src/tests/bitmap_480x320.png create mode 100644 radio/src/tests/clipping_480x320.png create mode 100644 radio/src/tests/lines_480x320.png create mode 100644 radio/src/tests/masks_480x320.png create mode 100644 radio/src/tests/primitives_EN_480x320.png create mode 100644 radio/src/tests/transparency_EN_480x320.png create mode 100644 radio/src/tests/vline_480x320.png diff --git a/radio/src/tests/bitmap_480x320.png b/radio/src/tests/bitmap_480x320.png new file mode 100644 index 0000000000000000000000000000000000000000..000c0fb07763fbd3424c4a28869d0731f27128db GIT binary patch literal 7162 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 D+eN~3 literal 0 HcmV?d00001 diff --git a/radio/src/tests/clipping_480x320.png b/radio/src/tests/clipping_480x320.png new file mode 100644 index 0000000000000000000000000000000000000000..f14ef839bd3236ba7de2c2a133f3627a535b914d GIT binary patch literal 5121 zcmeAS@N?(olHy`uVBq!ia0y~yV0-|?4jfECk=Ls%bQlF?(U4ElpwXGKgBzPOa#6d}>#jIa4PRT&{jBOX0s!RlX z+74uJfQbnLOpA42Tzq0A#RN73#4!Py1>qQS3`bl_b1~&5@&rudqk$F)YlN zC>j8bQ#%Xe>moU%r;R~V5gerYFsnh~i{veo@IS1;;?{2AHu)novUn~DNC1tS{0z$P z5kqo_n{&eyNRUp4S&b6Dz&Jn-{zPEJJIgFSSpkhJwoDF?ah}Ccz8g16LU8bc1nFs* z)yUxs3U=h+H{fWzsMvQg#Ri&=nt^5ljay;~OIN@YiIff$R9Qel`Z5(}HFEd@;{YZ2 zC0Y)=5ICM;BnM4Xj#5lLOoqm8%IPrO&Itl-43C@5<=vFqP6!xiL=+}Ev8+7EdWH3h zQ=8kPSl8gs?vsyaa0HmMXq*&$^7P1I$+?97LnKv_@>nHUCob3@ND9#2IE%j_DH z3*f}A!P9tgjf)RMA2522Zz1Bk-BoJA-Ls*KBU6B3By78rP3FSNvkVTNE=W<)0n6+F68t<);GU)=tYt=G>4nnO0k#EEdP1W; zP0(loO7M^NG{NN+p`PYw@*W*X0gYc#Hju)=kbT5_hSdwjr@)~-22WQ%mvv4FO#o%S B+vETM literal 0 HcmV?d00001 diff --git a/radio/src/tests/lines_480x320.png b/radio/src/tests/lines_480x320.png new file mode 100644 index 0000000000000000000000000000000000000000..885a7a1e0caf191f29cd977e6f7ae6eeffc38469 GIT binary patch literal 5734 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 literal 0 HcmV?d00001 diff --git a/radio/src/tests/transparency_EN_480x320.png b/radio/src/tests/transparency_EN_480x320.png new file mode 100644 index 0000000000000000000000000000000000000000..54827cd16112e62f8a6a05e64fddbbd43ad7a28b GIT binary patch literal 10019 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 Qfd2+5|Bj~mi|%0ee-2<9Qvd(} literal 0 HcmV?d00001 From 08ea16854dce02c670596f312294e9a163c10e55 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 16 Oct 2023 13:37:57 +0800 Subject: [PATCH 17/57] Fixed PL18 int MPM problem after PR #3822 and #4198. --- radio/src/targets/pl18/hal.h | 38 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 3c8dcbee05d..29c34ae2ebc 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -192,8 +192,8 @@ #define ADC_GPIO_PIN_POT3 LL_GPIO_PIN_8 // PF.08 VRC #define ADC_GPIO_PIN_SLIDER1 LL_GPIO_PIN_9 // PF.09 VRD/LS #define ADC_GPIO_PIN_SLIDER2 LL_GPIO_PIN_7 // PA.07 VRE/RS -#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 -#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 +//#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 +//#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 #define ADC_GPIO_PIN_SWB LL_GPIO_PIN_1 // PC.01 #define ADC_GPIO_PIN_SWD LL_GPIO_PIN_0 // PC.00 @@ -263,19 +263,19 @@ #define ADC_VREF_PREC2 330 -#define ADC_DIRECTION { \ - 0,0,0,0, /* gimbals */ \ - 0,0,0, /* pots */ \ - -1,-1, /* sliders */ \ - 0,0, /* ext1 & 2 */ \ - 0, /* vbat */ \ - /* 0, */ /* rtc_bat */ \ - -1, /* SWB */ \ - -1, /* SWD */ \ - 0, /* SWE */ \ - 0, /* SWF */ \ - 0, /* SWG */ \ - 0 /* SWH */ \ +#define ADC_DIRECTION { \ + 0,0,0,0, /* gimbals */ \ + 0,0,0, /* pots */ \ + -1,-1, /* sliders */ \ + /* 0,0,*/ /* ext1 & 2 */ \ + 0, /* vbat */ \ + /* 0, */ /* rtc_bat */ \ + -1, /* SWB */ \ + -1, /* SWD */ \ + 0, /* SWE */ \ + 0, /* SWF */ \ + 0, /* SWG */ \ + 0 /* SWH */ \ } // Power @@ -519,23 +519,21 @@ #define FLYSKY_HALL_DMA_Stream_TX LL_DMA_STREAM_4 // Internal Module -#define INTMODULE_RCC_AHB1Periph (RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOH | RCC_AHB1Periph_DMA1) +#define INTMODULE_RCC_AHB1Periph (RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_DMA1) #define INTMODULE_PWR_GPIO GPIOI #define INTMODULE_PWR_GPIO_PIN GPIO_Pin_0 // PI.00 #define INTMODULE_GPIO GPIOF #define INTMODULE_TX_GPIO_PIN LL_GPIO_PIN_7 // PF.07 #define INTMODULE_RX_GPIO_PIN LL_GPIO_PIN_6 // PF.06 #define INTMODULE_USART UART7 -#define INTMODULE_GPIO_AF GPIO_AF_UART7 -#define INTMODULE_GPIO_AF_LL LL_GPIO_AF_8 +#define INTMODULE_GPIO_AF LL_GPIO_AF_8 #define INTMODULE_USART_IRQn UART7_IRQn #define INTMODULE_USART_IRQHandler UART7_IRQHandler #define INTMODULE_DMA DMA1 #define INTMODULE_DMA_STREAM LL_DMA_STREAM_1 #define INTMODULE_DMA_STREAM_IRQ DMA1_Stream1_IRQn -#define INTMODULE_DMA_FLAG_TC DMA_IT_TCIF1 +#define INTMODULE_DMA_FLAG_TC DMA_FLAG_TCIF1 #define INTMODULE_DMA_CHANNEL LL_DMA_CHANNEL_5 - #define INTMODULE_RX_DMA DMA1 #define INTMODULE_RX_DMA_STREAM LL_DMA_STREAM_3 #define INTMODULE_RX_DMA_CHANNEL LL_DMA_CHANNEL_5 From 1c0bc169d5a2b5b9553077dc62e654b415157738 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 17 Oct 2023 12:01:38 +0800 Subject: [PATCH 18/57] Update datastructs after rebase. --- radio/src/storage/yaml/yaml_datastructs_pl18.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index 1bc5aa32205..41afcb49841 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -92,6 +92,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_DISABLE_TOUCH, "DISABLE_TOUCH" }, { FUNC_SET_SCREEN, "SET_SCREEN" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { @@ -166,6 +167,7 @@ const struct YamlIdStr enum_SwitchSources[] = { { SWSRC_ONE, "ONE" }, { SWSRC_TELEMETRY_STREAMING, "TELEMETRY_STREAMING" }, { SWSRC_RADIO_ACTIVITY, "RADIO_ACTIVITY" }, + { SWSRC_TRAINER_CONNECTED, "TRAINER_CONNECTED" }, { SWSRC_OFF, "OFF" }, { 0, NULL } }; @@ -292,7 +294,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "manuallyEdited", 1 ), YAML_SIGNED( "timezoneMinutes", 3 ), YAML_ENUM("hatsMode", 2, enum_HatsMode), - YAML_PADDING( 2 ), + YAML_UNSIGNED( "ppmunit", 2 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), YAML_ARRAY("calib", 48, 22, struct_CalibData, NULL), From 4d6c7efae6e5869daf88e9247a5e0c77f11afe42 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 17 Oct 2023 12:49:05 +0800 Subject: [PATCH 19/57] Added RTC Voltage ADC Settings. --- radio/src/targets/pl18/hal.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 29c34ae2ebc..208dffff745 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -237,7 +237,7 @@ #define ADC_CHANNEL_SWH LL_ADC_CHANNEL_8 // ADC3_IN8 -> ADC3_IN8 #define ADC_CHANNEL_BATT LL_ADC_CHANNEL_15 // ADC12_IN15 -> ADC1_IN15 -// #define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 +#define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 #define ADC_MAIN ADC1 #define ADC_EXT ADC3 @@ -261,7 +261,7 @@ #define ADC_EXT_DMA_STREAM_IRQHandler DMA2_Stream0_IRQHandler #define ADC_EXT_SAMPTIME LL_ADC_SAMPLINGTIME_28CYCLES -#define ADC_VREF_PREC2 330 +#define ADC_VREF_PREC2 660 #define ADC_DIRECTION { \ 0,0,0,0, /* gimbals */ \ @@ -269,7 +269,7 @@ -1,-1, /* sliders */ \ /* 0,0,*/ /* ext1 & 2 */ \ 0, /* vbat */ \ - /* 0, */ /* rtc_bat */ \ + 0, /* rtc_bat */ \ -1, /* SWB */ \ -1, /* SWD */ \ 0, /* SWE */ \ From bcc4a0b4ef0867fd8066ab5412a67df846c34fb9 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 17 Oct 2023 15:22:50 +0800 Subject: [PATCH 20/57] PL18 has 3 pots. --- companion/src/firmwares/generalsettings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/companion/src/firmwares/generalsettings.cpp b/companion/src/firmwares/generalsettings.cpp index e915150ff79..52aa0394426 100644 --- a/companion/src/firmwares/generalsettings.cpp +++ b/companion/src/firmwares/generalsettings.cpp @@ -299,6 +299,7 @@ void GeneralSettings::setDefaultControlTypes(Board::Type board) else if (IS_FLYSKY_PL18(board)) { potConfig[0] = Board::POT_WITHOUT_DETENT; potConfig[1] = Board::POT_WITHOUT_DETENT; + potConfig[2] = Board::POT_WITHOUT_DETENT; } else if (IS_TARANIS_XLITE(board)) { potConfig[0] = Board::POT_WITHOUT_DETENT; From 759f881364befbb77350bcabd7268d986fdbfc03 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 19 Oct 2023 10:15:13 +0800 Subject: [PATCH 21/57] Fixed LUA mixer problem due to changes in PR #3973. --- radio/src/targets/pl18/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 3252ab31459..c71451ac93c 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -6,6 +6,7 @@ option(GHOST "Ghost TX Module" ON) option(PXX1 "PXX1 protocol support" ON) option(PXX2 "PXX2 protocol support" OFF) option(MODULE_SIZE_STD "Standard size TX Module" ON) +option(LUA_MIXER "Enable LUA mixer/model scripts support" ON) set(PWR_BUTTON "PRESS" CACHE STRING "Pwr button type (PRESS/SWITCH)") set(CPU_TYPE STM32F4) From 6dd943a5cf3cd4a3f7774fa7974b88334c2142bd Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 19 Oct 2023 23:21:27 +0800 Subject: [PATCH 22/57] Added default switch config for PL18. --- radio/util/hw_defs/switch_config.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/radio/util/hw_defs/switch_config.py b/radio/util/hw_defs/switch_config.py index 3d7cb80cd25..dc83f543e61 100644 --- a/radio/util/hw_defs/switch_config.py +++ b/radio/util/hw_defs/switch_config.py @@ -29,6 +29,16 @@ "SG": { "default": "3POS" }, "SH": { "default": "TOGGLE" } }, + "pl18": { + "SA": { "default": "2POS" }, + "SB": { "default": "3POS" }, + "SC": { "default": "3POS" }, + "SD": { "default": "2POS" }, + "SE": { "default": "3POS" }, + "SF": { "default": "2POS" }, + "SG": { "default": "3POS" }, + "SH": { "default": "TOGGLE" } + }, "lr3pro": { # left side "SA": {"default": "3POS", "display": [0, 0]}, From 816dd2846f59949d4400e8b20c374327ee4ea442 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 19 Oct 2023 23:22:42 +0800 Subject: [PATCH 23/57] Amended some PL18 properties in companion. --- companion/src/CMakeLists.txt | 4 ++- companion/src/companion.qrc | 6 +++- companion/src/firmwares/boards.cpp | 3 +- companion/src/firmwares/generalsettings.cpp | 2 +- .../src/firmwares/opentx/opentxeeprom.cpp | 4 +-- companion/src/generaledit/generalsetup.cpp | 2 +- companion/src/modeledit/setup.cpp | 2 +- companion/src/modelprinter.cpp | 2 +- radio/src/targets/pl18/CMakeLists.txt | 35 +++++++++++-------- 9 files changed, 37 insertions(+), 23 deletions(-) diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index c623a69a268..c88b42061c3 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -316,8 +316,10 @@ elseif(PCB STREQUAL X10 AND PCBREV STREQUAL TX16S) set(FLAVOUR tx16s) elseif(PCB STREQUAL X10 AND PCBREV STREQUAL T18) set(FLAVOUR t18) -elseif(PCB STREQUAL NV14 AND PCBREV STREQUAL EL18) + elseif(PCB STREQUAL NV14 AND PCBREV STREQUAL EL18) set(FLAVOUR el18) + elseif(PCB STREQUAL PL18) + set(FLAVOUR pl18) else() string(TOLOWER ${PCB} FLAVOUR) endif() diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index 792e2f494b9..075d53d6638 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -292,7 +292,11 @@ images/simulator/NV14/left.png images/simulator/NV14/right.png images/simulator/NV14/top.png - images/simulator/NV14/bottom.png + images/simulator/PL18/bottom.png + images/simulator/PL18/left.png + images/simulator/PL18/right.png + images/simulator/PL18/top.png + images/simulator/PL18/bottom.png images/wizard/ailerons.png images/wizard/airbrakes.png images/wizard/elevons.png diff --git a/companion/src/firmwares/boards.cpp b/companion/src/firmwares/boards.cpp index 39b099c501b..f7f2be15615 100644 --- a/companion/src/firmwares/boards.cpp +++ b/companion/src/firmwares/boards.cpp @@ -671,7 +671,7 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) return false; case SportMaxBaudRate: - if (IS_FAMILY_T16(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board) || IS_TARANIS_X7_ACCESS(board) || + if (IS_FAMILY_T16(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board) || IS_FLYSKY_PL18(board) ||IS_TARANIS_X7_ACCESS(board) || (IS_TARANIS(board) && !IS_TARANIS_XLITE(board) && !IS_TARANIS_X7(board) && !IS_TARANIS_X9LITE(board))) return 400000; // 400K and higher else @@ -1232,6 +1232,7 @@ int Boards::getDefaultInternalModules(Board::Type board) case BOARD_JUMPER_TLITE_F4: case BOARD_JUMPER_TPRO: case BOARD_JUMPER_TPROV2: + case BOARD_FLYSKY_PL18: return (int)MODULE_TYPE_MULTIMODULE; case BOARD_BETAFPV_LR3PRO: diff --git a/companion/src/firmwares/generalsettings.cpp b/companion/src/firmwares/generalsettings.cpp index 52aa0394426..9e7688c99ca 100644 --- a/companion/src/firmwares/generalsettings.cpp +++ b/companion/src/firmwares/generalsettings.cpp @@ -271,7 +271,7 @@ void GeneralSettings::init() internalModule = g.profile[g.sessionId()].defaultInternalModule(); - if (IS_FLYSKY_NV14(board)) + if (IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) stickDeadZone = 2; } diff --git a/companion/src/firmwares/opentx/opentxeeprom.cpp b/companion/src/firmwares/opentx/opentxeeprom.cpp index 6e285788e6b..b7c44f0988e 100644 --- a/companion/src/firmwares/opentx/opentxeeprom.cpp +++ b/companion/src/firmwares/opentx/opentxeeprom.cpp @@ -116,7 +116,7 @@ inline int MAX_POTS_STORAGE(Board::Type board, int version) { if (version <= 218 && IS_FAMILY_HORUS_OR_T16(board)) return 3; - if (version <= 220 && IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board)) + if (version <= 220 && IS_FAMILY_HORUS_OR_T16(board) && !IS_FLYSKY_NV14(board) && !IS_FLYSKY_PL18(board)) return 5; if (IS_FAMILY_T12(board)) return 2; @@ -2608,7 +2608,7 @@ class TopBarField: public StructField { TopBarField(DataField * parent, TopBarPersistentData & topBar, Board::Type board, unsigned int version): StructField(parent, "Top Bar") { - Append(new WidgetsContainerPersistentField(this, topBar, (IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) ? 2 : 4, MAX_TOPBAR_OPTIONS, board, version)); + Append(new WidgetsContainerPersistentField(this, topBar, IS_FLYSKY_NV14(board) ? 2 : 4, MAX_TOPBAR_OPTIONS, board, version)); //dump(); } }; diff --git a/companion/src/generaledit/generalsetup.cpp b/companion/src/generaledit/generalsetup.cpp index 75d3b2dabc7..265aad75ac1 100644 --- a/companion/src/generaledit/generalsetup.cpp +++ b/companion/src/generaledit/generalsetup.cpp @@ -175,7 +175,7 @@ ui(new Ui::GeneralSetup) ui->usbModeCB->hide(); } - if (IS_FLYSKY_EL18(board) || IS_FLYSKY_NV14(board)) { + if (IS_FLYSKY_EL18(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) { ui->hatsModeCB->setModel(new FilteredItemModel(GeneralSettings::hatsModeItemModel())); ui->hatsModeCB->setField(generalSettings.hatsMode, this); } diff --git a/companion/src/modeledit/setup.cpp b/companion/src/modeledit/setup.cpp index 345e6aed9e1..d0911ccf99f 100644 --- a/companion/src/modeledit/setup.cpp +++ b/companion/src/modeledit/setup.cpp @@ -1696,7 +1696,7 @@ SetupPanel::SetupPanel(QWidget * parent, ModelData & model, GeneralSettings & ge ui->trimsDisplay->setField(model.trimsDisplay, this); - if (IS_FLYSKY_EL18(board) || IS_FLYSKY_NV14(board)) { + if (IS_FLYSKY_EL18(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) { ui->cboHatsMode->setModel(panelFilteredModels->getItemModel(FIM_HATSMODE)); ui->cboHatsMode->setField(model.hatsMode, this); } diff --git a/companion/src/modelprinter.cpp b/companion/src/modelprinter.cpp index 5501340990f..e524454aaa3 100644 --- a/companion/src/modelprinter.cpp +++ b/companion/src/modelprinter.cpp @@ -841,7 +841,7 @@ QString ModelPrinter::printSettingsTrim() str << printLabelValue(tr("Display"), printTrimsDisplayMode()); str << printLabelValue(tr("Extended"), printBoolean(model.extendedTrims, BOOLEAN_YESNO)); Board::Type board = firmware->getBoard(); - if (IS_FLYSKY_EL18(board) || IS_FLYSKY_NV14(board)) { + if (IS_FLYSKY_EL18(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_PL18(board)) { str << printLabelValue(tr("Hats Mode"), printHatsMode()); } return str.join(" "); diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index c71451ac93c..412f802e6c9 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -1,10 +1,9 @@ option(UNEXPECTED_SHUTDOWN "Enable the Unexpected Shutdown screen" ON) -option(STICKS_DEAD_ZONE "Enable sticks dead zone" YES) -option(MULTIMODULE "DIY Multiprotocol TX Module (https://github.com/pascallanger/DIY-Multiprotocol-TX-Module)" ON) -option(AFHDS2 "Support for AFHDS2" OFF) -option(GHOST "Ghost TX Module" ON) option(PXX1 "PXX1 protocol support" ON) option(PXX2 "PXX2 protocol support" OFF) +option(AFHDS3 "AFHDS3 TX Module" ON) +option(MULTIMODULE "DIY Multiprotocol TX Module (https://github.com/pascallanger/DIY-Multiprotocol-TX-Module)" ON) +option(GHOST "Ghost TX Module" ON) option(MODULE_SIZE_STD "Standard size TX Module" ON) option(LUA_MIXER "Enable LUA mixer/model scripts support" ON) @@ -16,20 +15,31 @@ set(STORAGE_MODELSLIST YES) set(HAPTIC YES) set(GUI_DIR colorlcd) set(BITMAPS_DIR 480x272) -set(HARDWARE_EXTERNAL_MODULE YES) -set(WIRELESS_CHARGER YES) set(TARGET_DIR pl18) - +set(LINKER_SCRIPT targets/pl18/stm32f4_flash.ld) set(RTC_BACKUP_RAM YES) set(PPM_LIMITS_SYMETRICAL YES) +set(USB_SERIAL ON CACHE BOOL "Enable USB serial (CDC)") +set(HARDWARE_EXTERNAL_MODULE YES) +set(WIRELESS_CHARGER YES) + +if(BOOTLOADER) + set(LINKER_SCRIPT targets/pl18/stm32f4_flash_bootloader.ld) +else() + set(LINKER_SCRIPT targets/pl18/stm32f4_flash.ld) +endif() + +#option(STICKS_DEAD_ZONE "Enable sticks dead zone" YES) +#option(AFHDS2 "Support for AFHDS2" OFF) + + # for size report script set(CPU_TYPE_FULL STM32F429xI) set(SIZE_TARGET_MEM_DEFINE "MEM_SIZE_SDRAM2=8192") -option(USB_SERIAL "Enable USB serial (CDC)" ON) -set(RF_BAUD_RATE 921600 230400 115200 57600 38400 19200 9600 4800 2400 1200) -set(PCB_RF_BAUD 921600 CACHE STRING "INTERNAL_MODULE_BAUDRATE: ${RF_BAUD_RATE}") -set_property(CACHE PCB_RF_BAUD PROPERTY STRINGS ${RF_BAUD_RATE}) +#set(RF_BAUD_RATE 921600 230400 115200 57600 38400 19200 9600 4800 2400 1200) +#set(PCB_RF_BAUD 921600 CACHE STRING "INTERNAL_MODULE_BAUDRATE: ${RF_BAUD_RATE}") +#set_property(CACHE PCB_RF_BAUD PROPERTY STRINGS ${RF_BAUD_RATE}) set(FLAVOUR pl18) add_definitions(-DPCBPL18 -DPCBFLYSKY) @@ -99,13 +109,10 @@ set(SRC set(GVAR_SCREEN model_gvars.cpp) if(BOOTLOADER) - set(LINKER_SCRIPT targets/pl18/stm32f4_flash_bootloader.ld) set(FIRMWARE_TARGET_SRC ${FIRMWARE_TARGET_SRC} ../common/arm/loadboot.cpp ) -else() - set(LINKER_SCRIPT targets/pl18/stm32f4_flash.ld) endif() set(SRC From 3671f5af5ae8e93dd24402dc3cef2b5df4005de0 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 20 Oct 2023 07:32:41 +0800 Subject: [PATCH 24/57] Fixed rebase problem due to PR #4038 --- radio/src/targets/pl18/CMakeLists.txt | 9 --------- radio/src/targets/pl18/hal.h | 6 ++++++ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 412f802e6c9..cc4287ad7c5 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -141,13 +141,6 @@ set(FIRMWARE_TARGET_SRC set(FIRMWARE_SRC ${FIRMWARE_SRC} - hal/adc_driver.cpp - targets/common/arm/stm32/stm32_exti_driver.cpp - targets/common/arm/stm32/timers_driver.cpp - targets/common/arm/stm32/stm32_pulse_driver.cpp - targets/common/arm/stm32/stm32_usart_driver.cpp - targets/common/arm/stm32/pwr_driver.cpp - targets/common/arm/stm32/trainer_driver.cpp targets/common/arm/stm32/audio_dac_driver.cpp targets/common/arm/stm32/spi_flash.cpp targets/common/arm/stm32/diskio_spi_flash.cpp @@ -163,7 +156,5 @@ set(STM32LIB_SRC STM32F4xx_StdPeriph_Driver/src/stm32f4xx_ltdc.c STM32F4xx_StdPeriph_Driver/src/stm32f4xx_tim.c STM32F4xx_StdPeriph_Driver/src/stm32f4xx_dma2d.c - STM32F4xx_StdPeriph_Driver/src/stm32f4xx_exti.c - STM32F4xx_StdPeriph_Driver/src/stm32f4xx_syscfg.c ) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 208dffff745..48636864f25 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -386,6 +386,12 @@ #define TELEMETRY_TX_POL_INV() TELEMETRY_REV_GPIO->BSRRL = TELEMETRY_TX_REV_GPIO_PIN #define TELEMETRY_RX_POL_NORM() TELEMETRY_REV_GPIO->BSRRH = TELEMETRY_RX_REV_GPIO_PIN #define TELEMETRY_RX_POL_INV() TELEMETRY_REV_GPIO->BSRRL = TELEMETRY_RX_REV_GPIO_PIN + +// Software IRQ (Prio 5 -> FreeRTOS compatible) +#define TELEMETRY_RX_FRAME_EXTI_LINE LL_EXTI_LINE_4 +#define USE_EXTI4_IRQ +#define EXTI4_IRQ_Priority 5 + // USB #define USB_RCC_AHB1Periph_GPIO RCC_AHB1Periph_GPIOA #define USB_GPIO GPIOA From 1097c84f08da0f7b94e8491062b36fa022628143 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Wed, 25 Oct 2023 12:01:14 +0800 Subject: [PATCH 25/57] Fixed rebase problems due to changes in PR #3036 --- .../arm/stm32/bootloader/CMakeLists.txt | 1 - radio/src/targets/pl18/CMakeLists.txt | 1 + radio/src/targets/pl18/board.cpp | 3 +- radio/src/targets/pl18/board.h | 23 +- radio/src/targets/pl18/hal.h | 19 +- radio/src/targets/pl18/lcd_driver.cpp | 503 +++++------------- radio/src/targets/pl18/lcd_driver.h | 80 +-- 7 files changed, 167 insertions(+), 463 deletions(-) diff --git a/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt b/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt index af6d3b15246..85e66458cf2 100644 --- a/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt +++ b/radio/src/targets/common/arm/stm32/bootloader/CMakeLists.txt @@ -78,7 +78,6 @@ if(PCB STREQUAL X10 OR PCB STREQUAL X12S OR PCB STREQUAL NV14 OR PCB STREQUAL PL ../../../../../thirdparty/libopenui/src/bitmapbuffer.cpp ../../../../../thirdparty/libopenui/thirdparty/lz4/lz4.c ../../../../../targets/common/arm/stm32/dma2d.cpp - ../../../../../targets/common/arm/stm32/diskio_sdio.cpp ../../../../../targets/common/arm/stm32/rtc_driver.cpp ../../../../../targets/common/arm/stm32/diskio_spi_flash.cpp ../../../../../targets/common/arm/stm32/spi_flash.cpp diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index cc4287ad7c5..26ff8f2bf0a 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -142,6 +142,7 @@ set(FIRMWARE_TARGET_SRC set(FIRMWARE_SRC ${FIRMWARE_SRC} targets/common/arm/stm32/audio_dac_driver.cpp + targets/common/arm/stm32/dma2d.cpp targets/common/arm/stm32/spi_flash.cpp targets/common/arm/stm32/diskio_spi_flash.cpp drivers/frftl.cpp diff --git a/radio/src/targets/pl18/board.cpp b/radio/src/targets/pl18/board.cpp index 5edcff91c2a..7642f34b578 100644 --- a/radio/src/targets/pl18/board.cpp +++ b/radio/src/targets/pl18/board.cpp @@ -205,7 +205,8 @@ void boardInit() rtcInit(); // RTC must be initialized before rambackupRestore() is called #endif - + lcdSetInitalFrameBuffer(lcdFront->getData()); + #if defined(DEBUG) DBGMCU_APB1PeriphConfig( DBGMCU_IWDG_STOP | DBGMCU_TIM1_STOP | DBGMCU_TIM2_STOP | diff --git a/radio/src/targets/pl18/board.h b/radio/src/targets/pl18/board.h index fcdf46eecb8..9906c41ac25 100644 --- a/radio/src/targets/pl18/board.h +++ b/radio/src/targets/pl18/board.h @@ -139,31 +139,10 @@ const etx_serial_port_t* auxSerialGetPort(int port_nr); #define AUX_SERIAL_POWER_OFF() // LCD driver -#define LCD_W 480 -#define LCD_H 320 -#define LCD_DEPTH 16 -#define LCD_CONTRAST_DEFAULT 20 - +void lcdSetInitalFrameBuffer(void* fbAddress); void lcdInit(); void lcdCopy(void * dest, void * src); -void DMAFillRect(uint16_t* dest, uint16_t destw, uint16_t desth, uint16_t x, - uint16_t y, uint16_t w, uint16_t h, uint16_t color); -void DMACopyBitmap(uint16_t* dest, uint16_t destw, uint16_t desth, uint16_t x, - uint16_t y, const uint16_t* src, uint16_t srcw, - uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, - uint16_t h); -void DMACopyAlphaBitmap(uint16_t* dest, uint16_t destw, uint16_t desth, - uint16_t x, uint16_t y, const uint16_t* src, - uint16_t srcw, uint16_t srch, uint16_t srcx, - uint16_t srcy, uint16_t w, uint16_t h); -void DMACopyAlphaMask(uint16_t* dest, uint16_t destw, uint16_t desth, - uint16_t x, uint16_t y, const uint8_t* src, uint16_t srcw, - uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, - uint16_t h, uint16_t bg_color); -void DMABitmapConvert(uint16_t* dest, const uint8_t* src, uint16_t w, - uint16_t h, uint32_t format); - void lcdOff(); void lcdOn(); diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 48636864f25..cf582ece2b9 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -408,12 +408,12 @@ #define LCD_RCC_APB1Periph 0 #define LCD_RCC_APB2Periph RCC_APB2Periph_LTDC #define LCD_NRST_GPIO GPIOG -#define LCD_NRST_GPIO_PIN GPIO_Pin_9 // PG.09 +#define LCD_NRST_GPIO_PIN LL_GPIO_PIN_9 // PG.09 #define LCD_SPI_GPIO GPIOE -#define LCD_SPI_CS_GPIO_PIN GPIO_Pin_4 // PE.04 -#define LCD_SPI_SCK_GPIO_PIN GPIO_Pin_2 // PE.02 -#define LCD_SPI_MISO_GPIO_PIN GPIO_Pin_5 // PE.05 -#define LCD_SPI_MOSI_GPIO_PIN GPIO_Pin_6 // PE.06 +#define LCD_SPI_CS_GPIO_PIN LL_GPIO_PIN_4 // PE.04 +#define LCD_SPI_SCK_GPIO_PIN LL_GPIO_PIN_2 // PE.02 +#define LCD_SPI_MISO_GPIO_PIN LL_GPIO_PIN_5 // PE.05 +#define LCD_SPI_MOSI_GPIO_PIN LL_GPIO_PIN_6 // PE.06 #define LTDC_IRQ_PRIO 4 #define DMA_SCREEN_IRQ_PRIO 6 @@ -662,4 +662,13 @@ #define MIXER_SCHEDULER_TIMER_IRQn TIM8_BRK_TIM12_IRQn #define MIXER_SCHEDULER_TIMER_IRQHandler TIM8_BRK_TIM12_IRQHandler +#define LCD_W 480 +#define LCD_H 320 + +#define LCD_PHYS_W 320 +#define LCD_PHYS_H 480 + +#define LCD_DEPTH 16 +#define LCD_CONTRAST_DEFAULT 20 + #endif // _HAL_H_ diff --git a/radio/src/targets/pl18/lcd_driver.cpp b/radio/src/targets/pl18/lcd_driver.cpp index ca3f9cf79ce..5f5bfe3038e 100644 --- a/radio/src/targets/pl18/lcd_driver.cpp +++ b/radio/src/targets/pl18/lcd_driver.cpp @@ -19,20 +19,22 @@ * GNU General Public License for more details. */ +#include "stm32_hal_ll.h" +#include "stm32_hal.h" #include "opentx_types.h" +#include "dma2d.h" +#include "hal.h" +#include "delays_driver.h" +#include "debug.h" #include "lcd.h" #include "lcd_driver.h" -// StdPeriph -#include "stm32f4xx_ltdc.h" -#include "stm32f4xx_dma2d.h" - -#pragma GCC push_options -#pragma GCC optimize("O0") // FOR DEBUG - uint8_t TouchControllerType = 0; //0:cst340; 1 ft6236 -static volatile uint16_t lcd_phys_w = 320; -static volatile uint16_t lcd_phys_h = 480; +static volatile uint16_t lcd_phys_w = LCD_PHYS_W; +static volatile uint16_t lcd_phys_h = LCD_PHYS_H; + +static LTDC_HandleTypeDef hltdc; +static void* initialFrameBuffer = nullptr; static volatile uint8_t _frame_addr_reloaded = 0; @@ -42,11 +44,12 @@ static void startLcdRefresh(lv_disp_drv_t *disp_drv, uint16_t *buffer, (void)disp_drv; (void)copy_area; + LTDC_Layer1->CFBAR &= ~(LTDC_LxCFBAR_CFBADD); LTDC_Layer1->CFBAR = (uint32_t)buffer; - // reload shadow registers on vertical blank _frame_addr_reloaded = 0; LTDC->SRCR = LTDC_SRCR_VBR; + __HAL_LTDC_ENABLE_IT(&hltdc, LTDC_IT_LI); // wait for reload // TODO: replace through some smarter mechanism without busy wait @@ -84,33 +87,6 @@ enum ENUM_IO_MODE IO_MODE_ANALOG }; - -void GPIO_SetDirection( GPIO_TypeDef *GPIOx, unsigned char Pin, unsigned char IsInput ) -{ - unsigned int Mask; - unsigned int Position; - unsigned int Register; - - - Position = Pin << 1; - Mask = ~( 0x03UL << Position ); - - //EnterCritical(); - Register = GPIOx->OSPEEDR & Mask; - Register |= IO_SPEED_HIGH << Position; - GPIOx->OSPEEDR = Register; - //ExitCritical(); - - //EnterCritical(); - Register = GPIOx->MODER & Mask; - if( !IsInput ) - { - Register |= IO_MODE_OUTPUT << Position; - } - - GPIOx->MODER = Register; - //ExitCritical(); -} static void LCD_AF_GPIOConfig(void) { /* ----------------------------------------------------------------------------- @@ -125,70 +101,54 @@ static void LCD_AF_GPIOConfig(void) { | | LCD_B3 <-> PJ.15 | */ - // GPIOG configuration - GPIO_PinAFConfig(GPIOG, GPIO_PinSource7, GPIO_AF_LTDC); - GPIO_InitTypeDef GPIO_InitStructure; - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; - GPIO_Init(GPIOG, &GPIO_InitStructure); + LL_GPIO_InitTypeDef GPIO_InitStructure; + LL_GPIO_StructInit(&GPIO_InitStructure); + // GPIOG configuration + GPIO_InitStructure.Pin = LL_GPIO_PIN_7; + GPIO_InitStructure.Speed = LL_GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Mode = LL_GPIO_MODE_ALTERNATE; + GPIO_InitStructure.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStructure.Pull = LL_GPIO_PULL_NO; + GPIO_InitStructure.Alternate = LL_GPIO_AF_14; // AF LTDC + LL_GPIO_Init(GPIOG, &GPIO_InitStructure); + // GPIOI configuration - GPIO_PinAFConfig(GPIOI, GPIO_PinSource12, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOI, GPIO_PinSource13, GPIO_AF_LTDC); - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13; - GPIO_Init(GPIOI, &GPIO_InitStructure); + GPIO_InitStructure.Pin = LL_GPIO_PIN_12 | LL_GPIO_PIN_13; + LL_GPIO_Init(GPIOI, &GPIO_InitStructure); // GPIOJ configuration - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource2, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource3, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource4, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource5, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource6, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource9, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource10, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource11, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOJ, GPIO_PinSource15, GPIO_AF_LTDC); - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_15; - GPIO_Init(GPIOJ, &GPIO_InitStructure); + GPIO_InitStructure.Pin = LL_GPIO_PIN_2 | LL_GPIO_PIN_3 | LL_GPIO_PIN_4 | LL_GPIO_PIN_5 | LL_GPIO_PIN_6 | LL_GPIO_PIN_9 | LL_GPIO_PIN_10 | LL_GPIO_PIN_11 | LL_GPIO_PIN_15; + LL_GPIO_Init(GPIOJ, &GPIO_InitStructure); // GPIOK configuration - GPIO_PinAFConfig(GPIOK, GPIO_PinSource0, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOK, GPIO_PinSource1, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOK, GPIO_PinSource2, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOK, GPIO_PinSource3, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOK, GPIO_PinSource4, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOK, GPIO_PinSource5, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOK, GPIO_PinSource6, GPIO_AF_LTDC); - GPIO_PinAFConfig(GPIOK, GPIO_PinSource7, GPIO_AF_LTDC); - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; - GPIO_Init(GPIOK, &GPIO_InitStructure); + GPIO_InitStructure.Pin = LL_GPIO_PIN_0 | LL_GPIO_PIN_1 | LL_GPIO_PIN_2 | LL_GPIO_PIN_3 | LL_GPIO_PIN_4 | LL_GPIO_PIN_5 | LL_GPIO_PIN_6 | LL_GPIO_PIN_7; + LL_GPIO_Init(GPIOK, &GPIO_InitStructure); } static void lcdSpiConfig(void) { - GPIO_InitTypeDef GPIO_InitStructure; - - GPIO_InitStructure.GPIO_Pin = LCD_SPI_SCK_GPIO_PIN | LCD_SPI_MOSI_GPIO_PIN; - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; - GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure); - - GPIO_InitStructure.GPIO_Pin = LCD_SPI_CS_GPIO_PIN; - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; - GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure); - - GPIO_InitStructure.GPIO_Pin = LCD_NRST_GPIO_PIN; - GPIO_Init(LCD_NRST_GPIO, &GPIO_InitStructure); + LL_GPIO_InitTypeDef GPIO_InitStructure; + LL_GPIO_StructInit(&GPIO_InitStructure); + + GPIO_InitStructure.Pin = LCD_SPI_SCK_GPIO_PIN | LCD_SPI_MOSI_GPIO_PIN; + GPIO_InitStructure.Speed = LL_GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Mode = LL_GPIO_MODE_OUTPUT; + GPIO_InitStructure.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStructure.Pull = LL_GPIO_PULL_NO; + LL_GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.Pin = LCD_SPI_CS_GPIO_PIN; + GPIO_InitStructure.Speed = LL_GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Mode = LL_GPIO_MODE_OUTPUT; + GPIO_InitStructure.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + GPIO_InitStructure.Pull = LL_GPIO_PULL_UP; + LL_GPIO_Init(LCD_SPI_GPIO, &GPIO_InitStructure); + + GPIO_InitStructure.Pin = LCD_NRST_GPIO_PIN; + LL_GPIO_Init(LCD_NRST_GPIO, &GPIO_InitStructure); /* Set the chip select pin aways low */ - SET_LCD_CS(); + LCD_CS_LOW(); } void lcdDelay() { @@ -210,25 +170,25 @@ unsigned char LCD_ReadByteOnFallingEdge(void) { unsigned int i; unsigned char ReceiveData = 0; - SET_LCD_DATA(); - SET_LCD_DATA_INPUT(); + LCD_MOSI_HIGH(); + LCD_MOSI_AS_INPUT(); for (i = 0; i < 8; i++) { LCD_DELAY(); - SET_LCD_CLK(); + LCD_SCK_HIGH(); LCD_DELAY(); LCD_DELAY(); ReceiveData <<= 1; - CLR_LCD_CLK(); + LCD_SCK_LOW(); LCD_DELAY(); LCD_DELAY(); - if (READ_LCD_DATA_PIN()) { + if (LCD_READ_DATA_PIN()) { ReceiveData |= 0x01; } } - SET_LCD_DATA_OUTPUT(); + LCD_MOSI_AS_OUTPUT(); return (ReceiveData); } @@ -270,46 +230,41 @@ unsigned char LCD_ReadByte(void) { unsigned int i; unsigned char ReceiveData = 0; - SET_LCD_DATA(); - SET_LCD_DATA_INPUT(); + LCD_MOSI_HIGH(); + LCD_MOSI_AS_INPUT(); for (i = 0; i < 8; i++) { - CLR_LCD_CLK(); - lcdDelay(); + LCD_SCK_LOW(); + LCD_DELAY(); + LCD_DELAY(); ReceiveData <<= 1; - SET_LCD_CLK(); - lcdDelay(); - if (READ_LCD_DATA_PIN()) { + LCD_SCK_HIGH(); + LCD_DELAY(); + LCD_DELAY(); + if (LCD_READ_DATA_PIN()) { ReceiveData |= 0x01; } } - CLR_LCD_CLK(); - SET_LCD_DATA_OUTPUT(); - + LCD_SCK_LOW(); + LCD_MOSI_AS_OUTPUT(); return (ReceiveData); } unsigned char LCD_ReadRegister(unsigned char Register) { unsigned char ReadData = 0; - CLR_LCD_CS(); lcdWriteByte(0, Register); - lcdDelay(); - lcdDelay(); + LCD_DELAY(); + LCD_DELAY(); ReadData = LCD_ReadByte(); - SET_LCD_CS(); return (ReadData); } void lcdWriteCommand(uint8_t command) { - CLR_LCD_CS(); lcdWriteByte(0, command); - SET_LCD_CS(); } void lcdWriteData(uint8_t data) { - CLR_LCD_CS(); lcdWriteByte(1, data); - SET_LCD_CS(); } void LCD_HX8357D_Init(void) { @@ -1324,14 +1279,13 @@ unsigned int LCD_ST7796S_ReadID(void) { lcdWriteCommand( 0XD3 ); - SET_LCD_CLK_OUTPUT(); - SET_LCD_DATA_INPUT(); - CLR_LCD_CLK(); - lcdDelay(); - lcdDelay(); - SET_LCD_CLK(); - lcdDelay(); - lcdDelay(); + LCD_MOSI_AS_INPUT(); + LCD_SCK_LOW(); + LCD_DELAY(); + LCD_DELAY(); + LCD_SCK_HIGH(); + LCD_DELAY(); + LCD_DELAY(); LCD_ReadByte(); ID += (uint16_t)(LCD_ReadByte())<<8; @@ -2738,7 +2692,7 @@ void LCD_NT35310_Off( void ) } void LCD_Init_LTDC() { - LTDC_InitTypeDef LTDC_InitStruct; + hltdc.Instance = LTDC; /* Configure PLLSAI prescalers for LCD */ /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */ @@ -2746,131 +2700,110 @@ void LCD_Init_LTDC() { /* PLLLCDCLK = PLLSAI_VCO Output/PLL_LTDC = PLLSAI_VCO/4 = YY Mhz */ /* LTDC clock frequency = PLLLCDCLK / RCC_PLLSAIDivR = YY/4 = lcdPixelClock Mhz */ uint32_t clock = (lcdPixelClock*16) / 1000000; // clock*16 in MHz - RCC_PLLSAIConfig(clock, 6, 4); - RCC_LTDCCLKDivConfig (RCC_PLLSAIDivR_Div4); - - /* Enable PLLSAI Clock */ - RCC_PLLSAICmd(ENABLE); - - /* Wait for PLLSAI activation */ - while (RCC_GetFlagStatus(RCC_FLAG_PLLSAIRDY) == RESET); + RCC_PeriphCLKInitTypeDef clkConfig; + clkConfig.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + clkConfig.PLLSAI.PLLSAIN = clock; + clkConfig.PLLSAI.PLLSAIR = 4; + clkConfig.PLLSAIDivQ = 6; + clkConfig.PLLSAIDivR = RCC_PLLSAIDIVR_4; + HAL_RCCEx_PeriphCLKConfig(&clkConfig); /* LTDC Configuration *********************************************************/ /* Polarity configuration */ /* Initialize the horizontal synchronization polarity as active low */ - LTDC_InitStruct.LTDC_HSPolarity = LTDC_HSPolarity_AL; + hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL; /* Initialize the vertical synchronization polarity as active low */ - LTDC_InitStruct.LTDC_VSPolarity = LTDC_VSPolarity_AL; + hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL; /* Initialize the data enable polarity as active low */ - LTDC_InitStruct.LTDC_DEPolarity = LTDC_DEPolarity_AL; + hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL; /* Initialize the pixel clock polarity as input pixel clock */ -// LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IPC; - LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IIPC; + hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IIPC; /* Configure R,G,B component values for LCD background color */ - LTDC_InitStruct.LTDC_BackgroundRedValue = 0; - LTDC_InitStruct.LTDC_BackgroundGreenValue = 0; - LTDC_InitStruct.LTDC_BackgroundBlueValue = 0; + hltdc.Init.Backcolor.Red = 0; + hltdc.Init.Backcolor.Green = 0; + hltdc.Init.Backcolor.Blue = 0; /* Configure horizontal synchronization width */ - LTDC_InitStruct.LTDC_HorizontalSync = HSW; + hltdc.Init.HorizontalSync = HSW; /* Configure vertical synchronization height */ - LTDC_InitStruct.LTDC_VerticalSync = VSH; + hltdc.Init.VerticalSync = VSH; /* Configure accumulated horizontal back porch */ - LTDC_InitStruct.LTDC_AccumulatedHBP = HBP; + hltdc.Init.AccumulatedHBP = HBP; /* Configure accumulated vertical back porch */ - LTDC_InitStruct.LTDC_AccumulatedVBP = VBP; + hltdc.Init.AccumulatedVBP = VBP; /* Configure accumulated active width */ - LTDC_InitStruct.LTDC_AccumulatedActiveW = lcd_phys_w + HBP; + hltdc.Init.AccumulatedActiveW = lcd_phys_w + HBP; /* Configure accumulated active height */ - LTDC_InitStruct.LTDC_AccumulatedActiveH = lcd_phys_h + VBP; + hltdc.Init.AccumulatedActiveH = lcd_phys_h + VBP; /* Configure total width */ - LTDC_InitStruct.LTDC_TotalWidth = lcd_phys_w + HBP + HFP; + hltdc.Init.TotalWidth = lcd_phys_w + HBP + HFP; /* Configure total height */ - LTDC_InitStruct.LTDC_TotalHeigh = lcd_phys_h + VBP + VFP; + hltdc.Init.TotalHeigh = lcd_phys_h + VBP + VFP; - LTDC_Init(<DC_InitStruct); + HAL_LTDC_Init(&hltdc); // Configure IRQ (line) NVIC_SetPriority(LTDC_IRQn, LTDC_IRQ_PRIO); NVIC_EnableIRQ(LTDC_IRQn); - // Trigger on last line - LTDC_LIPConfig(lcd_phys_h); - LTDC_ITConfig(LTDC_IER_LIE, ENABLE); + HAL_LTDC_ProgramLineEvent(&hltdc, lcd_phys_h); + __HAL_LTDC_ENABLE_IT(&hltdc, LTDC_IT_LI); } void LCD_LayerInit() { - LTDC_Layer_InitTypeDef LTDC_Layer_InitStruct; + auto& layer = hltdc.LayerCfg[0]; /* Windowing configuration */ - /* In this case all the active display area is used to display a picture then : - Horizontal start = horizontal synchronization + Horizontal back porch = 30 - Horizontal stop = Horizontal start + window width -1 = 30 + 240 -1 - Vertical start = vertical synchronization + vertical back porch = 4 - Vertical stop = Vertical start + window height -1 = 4 + 320 -1 */ - LTDC_Layer_InitStruct.LTDC_HorizontalStart = HBP + 1; - LTDC_Layer_InitStruct.LTDC_HorizontalStop = (lcd_phys_w + HBP); - LTDC_Layer_InitStruct.LTDC_VerticalStart = VBP + 1; - LTDC_Layer_InitStruct.LTDC_VerticalStop = (lcd_phys_h + VBP); + layer.WindowX0 = 0; + layer.WindowX1 = lcd_phys_w; + layer.WindowY0 = 0; + layer.WindowY1 = lcd_phys_h; /* Pixel Format configuration*/ - LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_RGB565; + layer.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; + /* Alpha constant (255 totally opaque) */ - LTDC_Layer_InitStruct.LTDC_ConstantAlpha = 255; + layer.Alpha = 255; + /* Default Color configuration (configure A,R,G,B component values) */ - LTDC_Layer_InitStruct.LTDC_DefaultColorBlue = 0; - LTDC_Layer_InitStruct.LTDC_DefaultColorGreen = 0; - LTDC_Layer_InitStruct.LTDC_DefaultColorRed = 0; - LTDC_Layer_InitStruct.LTDC_DefaultColorAlpha = 0; + layer.Backcolor.Blue = 0; + layer.Backcolor.Green = 0; + layer.Backcolor.Red = 0; + layer.Alpha0 = 0; /* Configure blending factors */ - LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_CA; - LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_CA; - - /* the length of one line of pixels in bytes + 3 then : - Line Lenth = Active high width x number of bytes per pixel + 3 - Active high width = LCD_W - number of bytes per pixel = 2 (pixel_format : RGB565) - */ - LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((lcd_phys_w * 2) + 3); - /* the pitch is the increment from the start of one line of pixels to the - start of the next line in bytes, then : - Pitch = Active high width x number of bytes per pixel */ - LTDC_Layer_InitStruct.LTDC_CFBPitch = (lcd_phys_w * 2); + layer.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA; + layer.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA; - /* Configure the number of lines */ - LTDC_Layer_InitStruct.LTDC_CFBLineNumber = lcd_phys_h; + layer.ImageWidth = lcd_phys_w; + layer.ImageHeight = lcd_phys_h; /* Start Address configuration : the LCD Frame buffer is defined on SDRAM w/ Offset */ - uint32_t layer_address = (uint32_t)lcdFront->getData(); - LTDC_Layer_InitStruct.LTDC_CFBStartAdress = layer_address; + layer.FBStartAdress = (intptr_t)initialFrameBuffer; /* Initialize LTDC layer 1 */ - LTDC_LayerInit(LTDC_Layer1, <DC_Layer_InitStruct); - - /* LTDC configuration reload */ - LTDC_ReloadConfig(LTDC_IMReload); - - LTDC_LayerCmd(LTDC_Layer1, ENABLE); - LTDC_LayerAlpha(LTDC_Layer1, 255); - - LTDC_ReloadConfig(LTDC_IMReload); + HAL_LTDC_ConfigLayer(&hltdc, &hltdc.LayerCfg[0], 0); /* dithering activation */ - LTDC_DitherCmd(ENABLE); + HAL_LTDC_EnableDither(&hltdc); +} + +extern "C" +void lcdSetInitalFrameBuffer(void* fbAddress) +{ + initialFrameBuffer = fbAddress; } const char* boardLcdType = ""; +extern "C" void lcdInit(void) { /* Configure the LCD SPI+RESET pins */ lcdSpiConfig(); - // TODO: init lVGL - /* Reset the LCD --------------------------------------------------------*/ lcdReset(); @@ -2899,9 +2832,8 @@ void lcdInit(void) lcdOffFunction = LCD_ILI9488_Off; lcdOnFunction = LCD_ILI9488_On; lcdPixelClock = 12000000; - - lcd_phys_w = 480; - lcd_phys_h = 320; + lcd_phys_w = LCD_PHYS_H; + lcd_phys_h = LCD_PHYS_W; } else if (LCD_HX8357D_ReadID() == LCD_HX8357D_ID) { TRACE("LCD INIT: HX8357D"); boardLcdType = "HX8357D"; @@ -2930,180 +2862,17 @@ void lcdInit(void) LCD_Init_LTDC(); LCD_LayerInit(); - LTDC_Cmd(ENABLE); - LTDC_ReloadConfig(LTDC_IMReload); - lcdSetFlushCb(startLcdRefresh); -} - -void DMAWait() -{ - while(DMA2D->CR & DMA2D_CR_START); -} - -void DMACopyBitmap(uint16_t *dest, uint16_t destw, uint16_t desth, uint16_t x, - uint16_t y, const uint16_t *src, uint16_t srcw, - uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, - uint16_t h) -{ - DMAWait(); - DMA2D_DeInit(); - - DMA2D_InitTypeDef DMA2D_InitStruct; - DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M; - DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; - DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest + y*destw + x); - DMA2D_InitStruct.DMA2D_OutputGreen = 0; - DMA2D_InitStruct.DMA2D_OutputBlue = 0; - DMA2D_InitStruct.DMA2D_OutputRed = 0; - DMA2D_InitStruct.DMA2D_OutputAlpha = 0; - DMA2D_InitStruct.DMA2D_OutputOffset = destw - w; - DMA2D_InitStruct.DMA2D_NumberOfLine = h; - DMA2D_InitStruct.DMA2D_PixelPerLine = w; - DMA2D_Init(&DMA2D_InitStruct); - - DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; - DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); - DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src + srcy*srcw + srcx); - DMA2D_FG_InitStruct.DMA2D_FGO = srcw - w; - DMA2D_FG_InitStruct.DMA2D_FGCM = CM_RGB565; - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; - DMA2D_FGConfig(&DMA2D_FG_InitStruct); - - /* Start Transfer */ - DMA2D_StartTransfer(); -} + // Enable LCD display + __HAL_LTDC_ENABLE(&hltdc); -void DMACopyAlphaBitmap(uint16_t *dest, uint16_t destw, uint16_t desth, - uint16_t x, uint16_t y, const uint16_t *src, - uint16_t srcw, uint16_t srch, uint16_t srcx, - uint16_t srcy, uint16_t w, uint16_t h) -{ - DMAWait(); - DMA2D_DeInit(); - - DMA2D_InitTypeDef DMA2D_InitStruct; - DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M_BLEND; - DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; - DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest + y*destw + x); - DMA2D_InitStruct.DMA2D_OutputGreen = 0; - DMA2D_InitStruct.DMA2D_OutputBlue = 0; - DMA2D_InitStruct.DMA2D_OutputRed = 0; - DMA2D_InitStruct.DMA2D_OutputAlpha = 0; - DMA2D_InitStruct.DMA2D_OutputOffset = destw - w; - DMA2D_InitStruct.DMA2D_NumberOfLine = h; - DMA2D_InitStruct.DMA2D_PixelPerLine = w; - DMA2D_Init(&DMA2D_InitStruct); - - DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; - DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); - DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src + srcy*srcw + srcx); - DMA2D_FG_InitStruct.DMA2D_FGO = srcw - w; - DMA2D_FG_InitStruct.DMA2D_FGCM = CM_ARGB4444; - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; - DMA2D_FGConfig(&DMA2D_FG_InitStruct); - - DMA2D_BG_InitTypeDef DMA2D_BG_InitStruct; - DMA2D_BG_StructInit(&DMA2D_BG_InitStruct); - DMA2D_BG_InitStruct.DMA2D_BGMA = CONVERT_PTR_UINT(dest + y*destw + x); - DMA2D_BG_InitStruct.DMA2D_BGO = destw - w; - DMA2D_BG_InitStruct.DMA2D_BGCM = CM_RGB565; - DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; - DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_VALUE = 0; - DMA2D_BGConfig(&DMA2D_BG_InitStruct); - - /* Start Transfer */ - DMA2D_StartTransfer(); -} - -// same as DMACopyAlphaBitmap(), but with an 8 bit mask for each pixel (used by fonts) -void DMACopyAlphaMask(uint16_t *dest, uint16_t destw, uint16_t desth, - uint16_t x, uint16_t y, const uint8_t *src, uint16_t srcw, - uint16_t srch, uint16_t srcx, uint16_t srcy, uint16_t w, - uint16_t h, uint16_t bg_color) -{ - DMAWait(); - DMA2D_DeInit(); - - DMA2D_InitTypeDef DMA2D_InitStruct; - DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M_BLEND; - DMA2D_InitStruct.DMA2D_CMode = CM_RGB565; - DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest + y*destw + x); - DMA2D_InitStruct.DMA2D_OutputBlue = 0; - DMA2D_InitStruct.DMA2D_OutputGreen = 0; - DMA2D_InitStruct.DMA2D_OutputRed = 0; - DMA2D_InitStruct.DMA2D_OutputAlpha = 0; - DMA2D_InitStruct.DMA2D_OutputOffset = destw - w; - DMA2D_InitStruct.DMA2D_NumberOfLine = h; - DMA2D_InitStruct.DMA2D_PixelPerLine = w; - DMA2D_Init(&DMA2D_InitStruct); - - DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; - DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); - DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src + srcy*srcw + srcx); - DMA2D_FG_InitStruct.DMA2D_FGO = srcw - w; - DMA2D_FG_InitStruct.DMA2D_FGCM = CM_A8; // 8 bit inputs every time - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; - DMA2D_FG_InitStruct.DMA2D_FGC_RED = GET_RED(bg_color); // 8 bit red - DMA2D_FG_InitStruct.DMA2D_FGC_GREEN = GET_GREEN(bg_color); // 8 bit green - DMA2D_FG_InitStruct.DMA2D_FGC_BLUE = GET_BLUE(bg_color); // 8 bit blue - - DMA2D_FGConfig(&DMA2D_FG_InitStruct); - - DMA2D_BG_InitTypeDef DMA2D_BG_InitStruct; - DMA2D_BG_StructInit(&DMA2D_BG_InitStruct); - DMA2D_BG_InitStruct.DMA2D_BGMA = CONVERT_PTR_UINT(dest + y*destw + x); - DMA2D_BG_InitStruct.DMA2D_BGO = destw - w; - DMA2D_BG_InitStruct.DMA2D_BGCM = CM_RGB565; - DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; - DMA2D_BG_InitStruct.DMA2D_BGPFC_ALPHA_VALUE = 0; - DMA2D_BGConfig(&DMA2D_BG_InitStruct); - - /* Start Transfer */ - DMA2D_StartTransfer(); -} - -void DMABitmapConvert(uint16_t * dest, const uint8_t * src, uint16_t w, uint16_t h, uint32_t format) -{ - DMAWait(); - DMA2D_DeInit(); - - DMA2D_InitTypeDef DMA2D_InitStruct; - DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M_PFC; - DMA2D_InitStruct.DMA2D_CMode = format; - DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest); - DMA2D_InitStruct.DMA2D_OutputGreen = 0; - DMA2D_InitStruct.DMA2D_OutputBlue = 0; - DMA2D_InitStruct.DMA2D_OutputRed = 0; - DMA2D_InitStruct.DMA2D_OutputAlpha = 0; - DMA2D_InitStruct.DMA2D_OutputOffset = 0; - DMA2D_InitStruct.DMA2D_NumberOfLine = h; - DMA2D_InitStruct.DMA2D_PixelPerLine = w; - DMA2D_Init(&DMA2D_InitStruct); - - DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; - DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); - DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src); - DMA2D_FG_InitStruct.DMA2D_FGO = 0; - DMA2D_FG_InitStruct.DMA2D_FGCM = CM_ARGB8888; - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = REPLACE_ALPHA_VALUE; - DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_VALUE = 0; - DMA2D_FGConfig(&DMA2D_FG_InitStruct); - - /* Start Transfer */ - DMA2D_StartTransfer(); + lcdSetFlushCb(startLcdRefresh); } extern "C" void LTDC_IRQHandler(void) { - // clear interrupt flag - LTDC->ICR = LTDC_ICR_CLIF; - + __HAL_LTDC_CLEAR_FLAG(&hltdc, LTDC_FLAG_LI); + __HAL_LTDC_DISABLE_IT(&hltdc, LTDC_IT_LI); _frame_addr_reloaded = 1; } -#pragma GCC pop_options - diff --git a/radio/src/targets/pl18/lcd_driver.h b/radio/src/targets/pl18/lcd_driver.h index 27519c68607..d1c4c756c64 100644 --- a/radio/src/targets/pl18/lcd_driver.h +++ b/radio/src/targets/pl18/lcd_driver.h @@ -22,8 +22,6 @@ #ifndef __LCD_DRIVER_H__ #define __LCD_DRIVER_H__ -#include "board.h" - #define HBP ( 24 ) // TODO use names from FlySky #define VBP ( 10 ) @@ -33,45 +31,6 @@ #define HFP ( 140 - HBP ) #define VFP ( 22 - VBP ) - -#define PORT_LCD_CS ( GPIOE ) -#define LCD_CS_PIN ( GPIO_Pin_4 ) -#define PIN_LCD_CS ( 4 ) - -#define PORT_LCD_CLK ( GPIOE ) -#define LCD_CLK_PIN ( GPIO_Pin_2 ) -#define PIN_LCD_CLK ( 2 ) - -#define PORT_LCD_MOSI ( GPIOE ) -#define LCD_MOSI_PIN ( GPIO_Pin_6 ) -#define PIN_LCD_MOSI ( 6 ) - -#define PORT_LCD_MISO ( GPIOE ) -#define LCD_MISO_PIN ( GPIO_Pin_5 ) -#define PIN_LCD_MISO ( 5 ) - -#define PORT_LCD_DE ( GPIOK ) -#define LCD_DE_PIN ( GPIO_Pin_7 ) -#define PIN_LCD_DE ( 7 ) - -#define PORT_LCD_RESET ( GPIOG ) -#define LCD_RESET_PIN ( GPIO_Pin_9 ) -#define PIN_LCD_RESET ( 9 ) - -#define PORT_LCD_HSYNC ( GPIOI ) -#define LCD_HSYNC_PIN ( GPIO_Pin_12 ) -#define PIN_LCD_HSYNC ( 12 ) - -#define PORT_LCD_VSYNC ( GPIOI ) -#define LCD_VSYNC_PIN ( GPIO_Pin_13 ) -#define PIN_LCD_VSYNC ( 13 ) - -#define PORT_LCD_DOTCLK ( GPIOG ) -#define LCD_DOTCLK_PIN ( GPIO_Pin_7 ) -#define PIN_LCD_DOTCLK ( 7 ) - -#define SUPPORTED_LCD_CNT ( 5 ) - #define LCD_ST7796S_ID ( 0x7796 ) #define LCD_ILI9481_ID ( 0x9481 ) #define LCD_ILI9486_ID ( 0x9486 ) @@ -83,42 +42,29 @@ typedef void (*lcdSpiInitFucPtr)(void); typedef unsigned int LcdReadIDFucPtr( void ); -extern void GPIO_SetDirection( GPIO_TypeDef *GPIOx, unsigned char Pin, unsigned char IsInput ); - extern lcdSpiInitFucPtr lcdInitFunction; extern lcdSpiInitFucPtr lcdOffFunction; extern lcdSpiInitFucPtr lcdOnFunction; -#define SET_IO_INPUT( PORT, PIN ) GPIO_SetDirection( PORT, PIN, 1 ) -#define SET_IO_OUTPUT( PORT, PIN ) GPIO_SetDirection( PORT, PIN, 0 ) - -#define LCD_NRST_HIGH() GPIO_WriteBit(LCD_NRST_GPIO, LCD_NRST_GPIO_PIN, Bit_SET) -#define LCD_NRST_LOW() GPIO_WriteBit(LCD_NRST_GPIO, LCD_NRST_GPIO_PIN, Bit_RESET) - -#define LCD_CS_HIGH() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_CS_GPIO_PIN, Bit_SET) -#define LCD_CS_LOW() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_CS_GPIO_PIN, Bit_RESET) - -#define LCD_SCK_HIGH() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_SCK_GPIO_PIN, Bit_SET) -#define LCD_SCK_LOW() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_SCK_GPIO_PIN, Bit_RESET) +#define SET_IO_INPUT( PORT, PIN ) LL_GPIO_SetPinMode( PORT, PIN, LL_GPIO_MODE_INPUT ) +#define SET_IO_OUTPUT( PORT, PIN ) LL_GPIO_SetPinMode( PORT, PIN, LL_GPIO_MODE_OUTPUT ) -#define LCD_MOSI_HIGH() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN, Bit_SET) -#define LCD_MOSI_LOW() GPIO_WriteBit(LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN, Bit_RESET) +#define LCD_NRST_HIGH() LL_GPIO_SetOutputPin(LCD_NRST_GPIO, LCD_NRST_GPIO_PIN) +#define LCD_NRST_LOW() LL_GPIO_ResetOutputPin(LCD_NRST_GPIO, LCD_NRST_GPIO_PIN) -#define SET_LCD_CS() GPIO_WriteBit(PORT_LCD_CS, LCD_CS_PIN, Bit_SET) -#define CLR_LCD_CS() GPIO_WriteBit(PORT_LCD_CS, LCD_CS_PIN, Bit_RESET) -#define SET_LCD_CS_OUTPUT() SET_IO_OUTPUT( PORT_LCD_CS, PIN_LCD_CS ) +#define LCD_CS_HIGH() LL_GPIO_SetOutputPin(LCD_SPI_GPIO, LCD_SPI_CS_GPIO_PIN) +#define LCD_CS_LOW() LL_GPIO_ResetOutputPin(LCD_SPI_GPIO, LCD_SPI_CS_GPIO_PIN) -#define SET_LCD_CLK() GPIO_WriteBit( PORT_LCD_CLK, LCD_CLK_PIN, Bit_SET ) -#define CLR_LCD_CLK() GPIO_WriteBit( PORT_LCD_CLK, LCD_CLK_PIN, Bit_RESET ) -#define SET_LCD_CLK_OUTPUT() SET_IO_OUTPUT( PORT_LCD_CLK, PIN_LCD_CLK ) +#define LCD_SCK_HIGH() LL_GPIO_SetOutputPin(LCD_SPI_GPIO, LCD_SPI_SCK_GPIO_PIN) +#define LCD_SCK_LOW() LL_GPIO_ResetOutputPin(LCD_SPI_GPIO, LCD_SPI_SCK_GPIO_PIN) -#define SET_LCD_DATA() GPIO_WriteBit( PORT_LCD_MOSI, LCD_MOSI_PIN, Bit_SET ) -#define CLR_LCD_DATA() GPIO_WriteBit( PORT_LCD_MOSI, LCD_MOSI_PIN, Bit_RESET ) -#define SET_LCD_DATA_INPUT() SET_IO_INPUT( PORT_LCD_MOSI, PIN_LCD_MOSI ) -#define SET_LCD_DATA_OUTPUT() SET_IO_OUTPUT( PORT_LCD_MOSI, PIN_LCD_MOSI ) +#define LCD_MOSI_HIGH() LL_GPIO_SetOutputPin(LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN) +#define LCD_MOSI_LOW() LL_GPIO_ResetOutputPin(LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN) -#define READ_LCD_DATA_PIN() GPIO_ReadInputDataBit(PORT_LCD_MOSI, LCD_MOSI_PIN) +#define LCD_MOSI_AS_INPUT() SET_IO_INPUT( LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN ) +#define LCD_MOSI_AS_OUTPUT() SET_IO_OUTPUT( LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN ) +#define LCD_READ_DATA_PIN() LL_GPIO_IsInputPinSet(LCD_SPI_GPIO, LCD_SPI_MOSI_GPIO_PIN) #if 1 From 40851fc9945544795aee7f9a756fca706453508b Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 27 Oct 2023 16:34:43 +0800 Subject: [PATCH 26/57] Fixed trims listing in diagnostics screen. --- radio/src/gui/colorlcd/radio_diagkeys.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/radio/src/gui/colorlcd/radio_diagkeys.cpp b/radio/src/gui/colorlcd/radio_diagkeys.cpp index 734488bcaa2..d8dee02bb92 100644 --- a/radio/src/gui/colorlcd/radio_diagkeys.cpp +++ b/radio/src/gui/colorlcd/radio_diagkeys.cpp @@ -25,7 +25,11 @@ #include "hal/rotary_encoder.h" +#if defined(PCBPL18) +static const uint8_t _trimMap[MAX_TRIMS * 2] = {8, 9, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 0, 1, 6, 7}; +#else static const uint8_t _trimMap[MAX_TRIMS * 2] = {6, 7, 4, 5, 2, 3, 0, 1, 8, 9, 10, 11}; +#endif static EnumKeys get_ith_key(uint8_t i) { From 6ffbebe3ad915e68d7134b11f83f3c9061c9cf6c Mon Sep 17 00:00:00 2001 From: Richard Li Date: Sat, 28 Oct 2023 16:16:05 +0800 Subject: [PATCH 27/57] Added PL18EV support. --- .github/workflows/actions.yml | 3 ++- .github/workflows/nightly.yml | 2 +- radio/src/targets/pl18/CMakeLists.txt | 15 ++++++++++---- radio/src/targets/pl18/board.h | 6 ++++++ radio/src/targets/pl18/hal.h | 28 ++++++++++++++++++++++++--- radio/util/hw_defs/switch_config.py | 14 ++++++++++++-- tools/build-flysky.py | 1 + tools/build-gh.sh | 3 +++ 8 files changed, 61 insertions(+), 11 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index af6e5946e4f..91807761949 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -47,6 +47,7 @@ jobs: - nv14 - el18 - pl18 + - pl18ev - t12 - t16 - t18 @@ -91,7 +92,7 @@ jobs: matrix: target: - nv14;el18 - - pl18 + - pl18;pl18ev - t12 - t16;t18 - t8;zorro;pocket;mt12;commando8 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1e0e2d15d2e..4406634ecd4 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -16,7 +16,7 @@ jobs: matrix: target: - nv14;el18 - - pl18 + - pl18;pl18ev - t12 - t16 - t18 diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 26ff8f2bf0a..bf22abcde82 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -41,15 +41,22 @@ set(SIZE_TARGET_MEM_DEFINE "MEM_SIZE_SDRAM2=8192") #set(PCB_RF_BAUD 921600 CACHE STRING "INTERNAL_MODULE_BAUDRATE: ${RF_BAUD_RATE}") #set_property(CACHE PCB_RF_BAUD PROPERTY STRINGS ${RF_BAUD_RATE}) -set(FLAVOUR pl18) add_definitions(-DPCBPL18 -DPCBFLYSKY) add_definitions(-DBATTERY_CHARGE) add_definitions(-DSOFTWARE_VOLUME) add_definitions(-DSPI_FLASH) -# defines existing internal modules -set(INTERNAL_MODULES MULTI CACHE STRING "Internal modules") -set(DEFAULT_INTERNAL_MODULE MULTIMODULE CACHE STRING "Default internal module") +if(PCBREV STREQUAL PL18EV) + set(FLAVOUR pl18ev) + add_definitions(-DRADIO_PL18EV) +else() + set(FLAVOUR pl18) + add_definitions(-DRADIO_PL18) + + # Defines internal modules for PL18 via UART7 + set(INTERNAL_MODULES MULTI CACHE STRING "Internal modules") + set(DEFAULT_INTERNAL_MODULE MULTIMODULE CACHE STRING "Default internal module") +endif() set(BITMAPS_TARGET pl18_bitmaps) set(FONTS_TARGET x12_fonts) diff --git a/radio/src/targets/pl18/board.h b/radio/src/targets/pl18/board.h index 9906c41ac25..7001ef805f9 100644 --- a/radio/src/targets/pl18/board.h +++ b/radio/src/targets/pl18/board.h @@ -181,9 +181,15 @@ bool isBacklightEnabled(); #if !defined(SIMU) void usbJoystickUpdate(); #endif +#if (PCBREV == PL18EV) +#define USB_NAME "FlySky PL18EV" +#define USB_MANUFACTURER 'F', 'l', 'y', 'S', 'k', 'y', ' ', ' ' /* 8 bytes */ +#define USB_PRODUCT 'P', 'L', '1', '8', 'E', 'V', ' ', ' ' /* 8 Bytes */ +#else #define USB_NAME "FlySky PL18" #define USB_MANUFACTURER 'F', 'l', 'y', 'S', 'k', 'y', ' ', ' ' /* 8 bytes */ #define USB_PRODUCT 'P', 'L', '1', '8', ' ', ' ', ' ', ' ' /* 8 Bytes */ +#endif #if defined(__cplusplus) && !defined(SIMU) } diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index cf582ece2b9..a54cfefcd1c 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -192,8 +192,11 @@ #define ADC_GPIO_PIN_POT3 LL_GPIO_PIN_8 // PF.08 VRC #define ADC_GPIO_PIN_SLIDER1 LL_GPIO_PIN_9 // PF.09 VRD/LS #define ADC_GPIO_PIN_SLIDER2 LL_GPIO_PIN_7 // PA.07 VRE/RS -//#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 -//#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 + +#if defined(RADIO_PL18EV) +#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 +#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 +#endif #define ADC_GPIO_PIN_SWB LL_GPIO_PIN_1 // PC.01 #define ADC_GPIO_PIN_SWD LL_GPIO_PIN_0 // PC.00 @@ -263,6 +266,22 @@ #define ADC_VREF_PREC2 660 +#if defined(RADIO_PL18EV) +#define ADC_DIRECTION { \ + 0,0,0,0, /* gimbals */ \ + 0,0,0, /* pots */ \ + -1,-1, /* sliders */ \ + 0,0, /* ext1 & 2 */ \ + 0, /* vbat */ \ + 0, /* rtc_bat */ \ + -1, /* SWB */ \ + -1, /* SWD */ \ + 0, /* SWE */ \ + 0, /* SWF */ \ + 0, /* SWG */ \ + 0 /* SWH */ \ + } +#else #define ADC_DIRECTION { \ 0,0,0,0, /* gimbals */ \ 0,0,0, /* pots */ \ @@ -277,7 +296,8 @@ 0, /* SWG */ \ 0 /* SWH */ \ } - +#endif + // Power #define PWR_RCC_AHB1Periph RCC_AHB1Periph_GPIOI #define PWR_ON_GPIO GPIOI @@ -525,6 +545,7 @@ #define FLYSKY_HALL_DMA_Stream_TX LL_DMA_STREAM_4 // Internal Module +#if defined(RADIO_PL18) #define INTMODULE_RCC_AHB1Periph (RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_DMA1) #define INTMODULE_PWR_GPIO GPIOI #define INTMODULE_PWR_GPIO_PIN GPIO_Pin_0 // PI.00 @@ -550,6 +571,7 @@ #define INTMODULE_TIMER_IRQn TIM3_IRQn #define INTMODULE_TIMER_IRQHandler TIM3_IRQHandler #define INTMODULE_TIMER_FREQ (PERI1_FREQUENCY * TIMER_MULT_APB1) +#endif // External Module #define EXTMODULE diff --git a/radio/util/hw_defs/switch_config.py b/radio/util/hw_defs/switch_config.py index dc83f543e61..3d42dbed49e 100644 --- a/radio/util/hw_defs/switch_config.py +++ b/radio/util/hw_defs/switch_config.py @@ -32,13 +32,23 @@ "pl18": { "SA": { "default": "2POS" }, "SB": { "default": "3POS" }, - "SC": { "default": "3POS" }, - "SD": { "default": "2POS" }, + "SC": { "default": "2POS" }, + "SD": { "default": "3POS" }, "SE": { "default": "3POS" }, "SF": { "default": "2POS" }, "SG": { "default": "3POS" }, "SH": { "default": "TOGGLE" } }, + "pl18ev": { + "SA": { "default": "2POS" }, + "SB": { "default": "3POS" }, + "SC": { "default": "2POS" }, + "SD": { "default": "3POS" }, + "SE": { "default": "3POS" }, + "SF": { "default": "2POS" }, + "SG": { "default": "3POS" }, + "SH": { "default": "3POS" } + }, "lr3pro": { # left side "SA": {"default": "3POS", "display": [0, 0]}, diff --git a/tools/build-flysky.py b/tools/build-flysky.py index b7f0a3b4d77..22be920ccee 100644 --- a/tools/build-flysky.py +++ b/tools/build-flysky.py @@ -12,6 +12,7 @@ "NV14": { "PCB": "NV14" }, "EL18": { "PCB": "NV14", "PCBREV": "EL18" }, "PL18": { "PCB": "PL18" }, + "PL18EV": { "PCB": "PL18", "PCBREV": "PL18EV" }, } translations = [ diff --git a/tools/build-gh.sh b/tools/build-gh.sh index 790229b31a8..7af0d5c435f 100755 --- a/tools/build-gh.sh +++ b/tools/build-gh.sh @@ -194,6 +194,9 @@ do pl18) BUILD_OPTIONS+="-DPCB=PL18" ;; + pl18ev) + BUILD_OPTIONS+="-DPCB=PL18" -DPCBREV=PL18EV" + ;; commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" ;; From bb7e8f6c5d820606cae5e23b5159f384d8da063b Mon Sep 17 00:00:00 2001 From: Richard Li Date: Sat, 28 Oct 2023 18:02:06 +0800 Subject: [PATCH 28/57] Fixed PL18EV build problem. --- fw.json | 1 + .../src/boards/generic_stm32/module_ports.cpp | 4 +- radio/util/hw_defs/legacy_names.py | 54 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/fw.json b/fw.json index 00c53fe8f08..b92436535d2 100644 --- a/fw.json +++ b/fw.json @@ -4,6 +4,7 @@ ["Flysky EL18", "el18-"], ["Flysky NV14", "nv14-"], ["Flysky PL18", "pl18-"], + ["Flysky PL18EV", "pl18ev-"], ["FrSky Horus X10", "x10-"], ["FrSky Horus X10 Access", "x10-access-"], ["FrSky Horus X12s", "x12s-"], diff --git a/radio/src/boards/generic_stm32/module_ports.cpp b/radio/src/boards/generic_stm32/module_ports.cpp index e4d37be48d7..e7e07fd3d74 100644 --- a/radio/src/boards/generic_stm32/module_ports.cpp +++ b/radio/src/boards/generic_stm32/module_ports.cpp @@ -28,6 +28,7 @@ #include "board.h" #include "dataconstants.h" +#if defined (HARDWARE_INTERNAL_MODULE) #if defined(INTMODULE_USART) #define INTMODULE_USART_IRQ_PRIORITY 5 @@ -110,7 +111,8 @@ extern "C" void INTMODULE_TIMER_IRQHandler() DEFINE_STM32_SOFTSERIAL_PORT(InternalModule, intmoduleTimer); -#endif +#endif // INTMODULE_USART +#endif // HARDWARE_INTERNAL_MODULE #include "module_timer_driver.h" diff --git a/radio/util/hw_defs/legacy_names.py b/radio/util/hw_defs/legacy_names.py index 0c941db0a60..07afa6337c0 100644 --- a/radio/util/hw_defs/legacy_names.py +++ b/radio/util/hw_defs/legacy_names.py @@ -342,6 +342,60 @@ "targets": { "pl18" }, + "inputs": { + "LH": { + "yaml": "Rud" + }, + "LV": { + "yaml": "Ele" + }, + "RV": { + "yaml": "Thr" + }, + "RH": { + "yaml": "Ail" + }, + "P1": { + "yaml": "POT1", + "lua": "s1", + "label": "S1", + "short_label": "1", + "description": "Potentiometer 1" + }, + "P2": { + "yaml": "POT2", + "lua": "s2", + "label": "S2", + "short_label": "2", + "description": "Potentiometer 2" + }, + "P3": { + "yaml": "POT3", + "lua": "s3", + "label": "S3", + "short_label": "3", + "description": "Potentiometer 3" + }, + "SL1": { + "yaml": "LS", + "lua": "ls", + "label": "LS", + "short_label": "L", + "description": "Left slider" + }, + "SL2": { + "yaml": "RS", + "lua": "rs", + "label": "RS", + "short_label": "R", + "description": "Right slider" + }, + } + }, + { + "targets": { + "pl18ev" + }, "inputs": { "LH": { "yaml": "Rud" From a10b3817cd43a821dad4008d83f5410cf53d7258 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Sun, 29 Oct 2023 10:52:28 +0800 Subject: [PATCH 29/57] Missing PL18EV definition in commit-tests.sh --- tools/commit-tests.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/commit-tests.sh b/tools/commit-tests.sh index 83230496b3e..978ce488167 100755 --- a/tools/commit-tests.sh +++ b/tools/commit-tests.sh @@ -151,6 +151,9 @@ do pl18) BUILD_OPTIONS+="-DPCB=PL18" ;; + pl18ev) + BUILD_OPTIONS+="-DPCB=PL18" -DPCBREV=PL18EV" + ;; commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" ;; From c15570eb4ce9ce4a51137e37d94452e8ad306164 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Sun, 29 Oct 2023 13:15:57 +1000 Subject: [PATCH 30/57] fix: Erronous quote --- tools/commit-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/commit-tests.sh b/tools/commit-tests.sh index 978ce488167..1d9c9634f6d 100755 --- a/tools/commit-tests.sh +++ b/tools/commit-tests.sh @@ -152,7 +152,7 @@ do BUILD_OPTIONS+="-DPCB=PL18" ;; pl18ev) - BUILD_OPTIONS+="-DPCB=PL18" -DPCBREV=PL18EV" + BUILD_OPTIONS+="-DPCB=PL18 -DPCBREV=PL18EV" ;; commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" From fda50b6179a34225cfa720832a7736a43848c66c Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Sun, 29 Oct 2023 13:23:52 +1000 Subject: [PATCH 31/57] chore: Remove unused graphics --- radio/src/bitmaps/480x272/splash_480x320.png | Bin 51772 -> 0 bytes .../src/bitmaps/480x272/splash_chr_480x320.png | Bin 46524 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 radio/src/bitmaps/480x272/splash_480x320.png delete mode 100644 radio/src/bitmaps/480x272/splash_chr_480x320.png diff --git a/radio/src/bitmaps/480x272/splash_480x320.png b/radio/src/bitmaps/480x272/splash_480x320.png deleted file mode 100644 index 43e59de80d46ccbc32677de7ab94ccf9e7344ee9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51772 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 P00000NkvXXu0mjflbqc( diff --git a/radio/src/bitmaps/480x272/splash_chr_480x320.png b/radio/src/bitmaps/480x272/splash_chr_480x320.png deleted file mode 100644 index 7cc10bc0b21c24226f44c783a137be92ec1dd1b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46524 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 From 7818eed91dfaeaeff6595bafa06d5ba5e4cab71f Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Sun, 29 Oct 2023 13:45:32 +1000 Subject: [PATCH 32/57] fix: Another erronous quote :laughing: --- tools/build-gh.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build-gh.sh b/tools/build-gh.sh index 7af0d5c435f..16f0aa512ba 100755 --- a/tools/build-gh.sh +++ b/tools/build-gh.sh @@ -195,7 +195,7 @@ do BUILD_OPTIONS+="-DPCB=PL18" ;; pl18ev) - BUILD_OPTIONS+="-DPCB=PL18" -DPCBREV=PL18EV" + BUILD_OPTIONS+="-DPCB=PL18 -DPCBREV=PL18EV" ;; commando8) BUILD_OPTIONS+="-DPCB=X7 -DPCBREV=COMMANDO8" From b471014b3b5f5b7a30b98ad7478237798888cd87 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 30 Oct 2023 10:49:00 +0800 Subject: [PATCH 33/57] Fine tuned PL18EV stick ends mapping. --- radio/src/targets/pl18/hal.h | 24 ++++++++++++++++-------- radio/util/hw_defs/switch_config.py | 4 +++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index a54cfefcd1c..90cd5f2b1fa 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -204,15 +204,20 @@ #define ADC_GPIO_PIN_SWF LL_GPIO_PIN_0 // PB.00 #define ADC_GPIO_PIN_SWG LL_GPIO_PIN_1 // PB.01 #define ADC_GPIO_PIN_SWH LL_GPIO_PIN_10 // PF.10 +#if defined(RADIO_PL18EV) +#define ADC_GPIO_PIN_SWI LL_GPIO_PIN_3 // PA.03 +#define ADC_GPIO_PIN_SWJ LL_GPIO_PIN_5 // PA.05 +#endif #define ADC_GPIO_PIN_BATT LL_GPIO_PIN_5 // PC.05 -#define ADC_GPIOA_PINS (ADC_GPIO_PIN_POT1 | ADC_GPIO_PIN_SLIDER2 | ADC_GPIO_PIN_EXT1) +#define ADC_GPIOA_PINS (ADC_GPIO_PIN_POT1 | ADC_GPIO_PIN_SLIDER2 | \ + ADC_GPIO_PIN_EXT1 | ADC_GPIO_PIN_SWI | ADC_GPIO_PIN_SWJ) #define ADC_GPIOB_PINS (ADC_GPIO_PIN_SWF | ADC_GPIO_PIN_SWG) -#define ADC_GPIOC_PINS \ - (ADC_GPIO_PIN_POT2 | ADC_GPIO_PIN_BATT | ADC_GPIO_PIN_SWB | ADC_GPIO_PIN_SWD | ADC_GPIO_PIN_SWE) -#define ADC_GPIOF_PINS \ - (ADC_GPIO_PIN_POT3 | ADC_GPIO_PIN_SLIDER1 | ADC_GPIO_PIN_EXT2 | ADC_GPIO_PIN_SWH) +#define ADC_GPIOC_PINS (ADC_GPIO_PIN_POT2 | ADC_GPIO_PIN_BATT | \ + ADC_GPIO_PIN_SWB | ADC_GPIO_PIN_SWD | ADC_GPIO_PIN_SWE) +#define ADC_GPIOF_PINS (ADC_GPIO_PIN_POT3 | ADC_GPIO_PIN_SLIDER1 | \ + ADC_GPIO_PIN_EXT2 | ADC_GPIO_PIN_SWH) #define ADC_CHANNEL_STICK_LH #define ADC_CHANNEL_STICK_LV @@ -238,6 +243,8 @@ #define ADC_CHANNEL_SWF LL_ADC_CHANNEL_8 // ADC12_IN8 -> ADC1_IN8 #define ADC_CHANNEL_SWG LL_ADC_CHANNEL_9 // ADC12_IN9 -> ADC1_IN9 #define ADC_CHANNEL_SWH LL_ADC_CHANNEL_8 // ADC3_IN8 -> ADC3_IN8 +#define ADC_CHANNEL_SWI LL_ADC_CHANNEL_3 // ADC123_IN2 -> ADC1_IN3 +#define ADC_CHANNEL_SWJ LL_ADC_CHANNEL_5 // ADC12_IN5 -> ADC1_IN5 #define ADC_CHANNEL_BATT LL_ADC_CHANNEL_15 // ADC12_IN15 -> ADC1_IN15 #define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 @@ -271,7 +278,7 @@ 0,0,0,0, /* gimbals */ \ 0,0,0, /* pots */ \ -1,-1, /* sliders */ \ - 0,0, /* ext1 & 2 */ \ + 0,0, /* ext1&2 */ \ 0, /* vbat */ \ 0, /* rtc_bat */ \ -1, /* SWB */ \ @@ -279,14 +286,15 @@ 0, /* SWE */ \ 0, /* SWF */ \ 0, /* SWG */ \ - 0 /* SWH */ \ + 0, /* SWH */ \ + 0, /* SWI */ \ + 0 /* SWJ */ \ } #else #define ADC_DIRECTION { \ 0,0,0,0, /* gimbals */ \ 0,0,0, /* pots */ \ -1,-1, /* sliders */ \ - /* 0,0,*/ /* ext1 & 2 */ \ 0, /* vbat */ \ 0, /* rtc_bat */ \ -1, /* SWB */ \ diff --git a/radio/util/hw_defs/switch_config.py b/radio/util/hw_defs/switch_config.py index 3d42dbed49e..de3461f53ca 100644 --- a/radio/util/hw_defs/switch_config.py +++ b/radio/util/hw_defs/switch_config.py @@ -47,7 +47,9 @@ "SE": { "default": "3POS" }, "SF": { "default": "2POS" }, "SG": { "default": "3POS" }, - "SH": { "default": "3POS" } + "SH": { "default": "3POS" }, + "SI": { "default": "3POS" }, + "SJ": { "default": "3POS" } }, "lr3pro": { # left side From 41c50e2c4f2e4225e170b3c9a321c39f7abe2986 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 31 Oct 2023 10:33:47 +0800 Subject: [PATCH 34/57] Fixed a typo in comment. --- radio/src/targets/pl18/hal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 90cd5f2b1fa..a48f57b94bc 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -243,7 +243,7 @@ #define ADC_CHANNEL_SWF LL_ADC_CHANNEL_8 // ADC12_IN8 -> ADC1_IN8 #define ADC_CHANNEL_SWG LL_ADC_CHANNEL_9 // ADC12_IN9 -> ADC1_IN9 #define ADC_CHANNEL_SWH LL_ADC_CHANNEL_8 // ADC3_IN8 -> ADC3_IN8 -#define ADC_CHANNEL_SWI LL_ADC_CHANNEL_3 // ADC123_IN2 -> ADC1_IN3 +#define ADC_CHANNEL_SWI LL_ADC_CHANNEL_3 // ADC123_IN3 -> ADC1_IN3 #define ADC_CHANNEL_SWJ LL_ADC_CHANNEL_5 // ADC12_IN5 -> ADC1_IN5 #define ADC_CHANNEL_BATT LL_ADC_CHANNEL_15 // ADC12_IN15 -> ADC1_IN15 From 8a22e7f38804043694a8a159304dd84d620110d1 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 31 Oct 2023 10:56:08 +0800 Subject: [PATCH 35/57] Fixed another problem of PL18EV mapping. --- radio/src/targets/pl18/board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radio/src/targets/pl18/board.h b/radio/src/targets/pl18/board.h index 7001ef805f9..b8fdfc271ca 100644 --- a/radio/src/targets/pl18/board.h +++ b/radio/src/targets/pl18/board.h @@ -181,7 +181,7 @@ bool isBacklightEnabled(); #if !defined(SIMU) void usbJoystickUpdate(); #endif -#if (PCBREV == PL18EV) +#if defined(RADIO_PL18EV) #define USB_NAME "FlySky PL18EV" #define USB_MANUFACTURER 'F', 'l', 'y', 'S', 'k', 'y', ' ', ' ' /* 8 bytes */ #define USB_PRODUCT 'P', 'L', '1', '8', 'E', 'V', ' ', ' ' /* 8 Bytes */ From e8aa970eb5207a44bb8727c7e96a529fe16d3c11 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 31 Oct 2023 15:55:48 +0800 Subject: [PATCH 36/57] Cleaned up touch driver. --- radio/src/targets/pl18/CMakeLists.txt | 2 +- radio/src/targets/pl18/battery_driver.cpp | 2 - radio/src/targets/pl18/board.cpp | 4 +- radio/src/targets/pl18/touch_driver.cpp | 470 ++++++++++++++++++++++ radio/src/targets/pl18/touch_driver.h | 31 ++ 5 files changed, 504 insertions(+), 5 deletions(-) create mode 100644 radio/src/targets/pl18/touch_driver.cpp create mode 100644 radio/src/targets/pl18/touch_driver.h diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index bf22abcde82..588babf1625 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -61,7 +61,7 @@ endif() set(BITMAPS_TARGET pl18_bitmaps) set(FONTS_TARGET x12_fonts) set(LCD_DRIVER lcd_driver.cpp) -set(TOUCH_DRIVER tp_cst340.cpp) +set(TOUCH_DRIVER touch_driver.cpp) set(HARDWARE_TOUCH YES) set(RADIO_DEPENDENCIES ${RADIO_DEPENDENCIES} ${BITMAPS_TARGET}) set(FIRMWARE_DEPENDENCIES datacopy) diff --git a/radio/src/targets/pl18/battery_driver.cpp b/radio/src/targets/pl18/battery_driver.cpp index 7aa1e9dc703..b12a1ea7ff2 100644 --- a/radio/src/targets/pl18/battery_driver.cpp +++ b/radio/src/targets/pl18/battery_driver.cpp @@ -353,7 +353,6 @@ void drawChargingInfo(uint16_t chargeState) { lcd->drawFilledRect((LCD_W - BATTERY_CONNECTOR_W) / 2, BATTERY_TOP - BATTERY_CONNECTOR_H, BATTERY_CONNECTOR_W, BATTERY_CONNECTOR_H, SOLID, COLOR_THEME_PRIMARY2); } #define CHARGE_INFO_DURATION 500 -void TouchInit(); //this method should be called by timer interrupt or by GPIO interrupt void handle_battery_charge(uint32_t last_press_time) @@ -401,7 +400,6 @@ void handle_battery_charge(uint32_t last_press_time) lcdInit(); lcdInitDisplayDriver(); lcdInited = true; - TouchInit(); } else { lcdOn(); diff --git a/radio/src/targets/pl18/board.cpp b/radio/src/targets/pl18/board.cpp index 7642f34b578..25e457de942 100644 --- a/radio/src/targets/pl18/board.cpp +++ b/radio/src/targets/pl18/board.cpp @@ -37,7 +37,7 @@ #include "timers_driver.h" #include "battery_driver.h" -#include "tp_cst340.h" +#include "touch_driver.h" #include "watchdog_driver.h" #include "bitmapbuffer.h" @@ -160,7 +160,7 @@ void boardInit() battery_charge_init(); flysky_gimbal_init(); timersInit(); - TouchInit(); + touchPanelInit(); usbInit(); uint32_t press_start = 0; diff --git a/radio/src/targets/pl18/touch_driver.cpp b/radio/src/targets/pl18/touch_driver.cpp new file mode 100644 index 00000000000..413f876b9d3 --- /dev/null +++ b/radio/src/targets/pl18/touch_driver.cpp @@ -0,0 +1,470 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "stm32_hal_ll.h" +#include "stm32_hal.h" +#include "stm32_i2c_driver.h" +#include "stm32_gpio_driver.h" +#include "stm32_exti_driver.h" + +#include "hal.h" +#include "timers_driver.h" +#include "delays_driver.h" +#include "touch_driver.h" + +#include "debug.h" + +#define TAP_TIME 25 +#define I2C_TIMEOUT_MAX 5 // 5 ms + +// FT6236 definitions +#define TOUCH_FT6236_I2C_ADDRESS (0x70>>1) +#define TOUCH_FT6236_REG_TH_GROUP 0x80 +#define TOUCH_FT6236_REG_PERIODACTIVE 0x88 +#define TOUCH_FT6236_REG_LIB_VER_H 0xa1 +#define TOUCH_FT6236_REG_LIB_VER_L 0xa2 +#define TOUCH_FT6236_REG_CIPHER 0xa3 +#define TOUCH_FT6236_REG_FIRMID 0xa6 +#define TOUCH_FT6236_REG_FOCALTECH_ID 0xa8 +#define TOUCH_FT6236_REG_RELEASE_CODE_ID 0xaf +#define TOUCH_FT6206_REG_TD_STAT 0x02 +#define TOUCH_FT6206_REG_P1_XH 0x03 +#define TOUCH_FT6206_EVT_SHIFT 6 +#define TOUCH_FT6206_EVT_MASK (3 << TOUCH_FT6206_EVT_SHIFT) +#define TOUCH_FT6206_EVT_CONTACT 0x02 +#define TOUCH_FT6206_MASK_TD_STAT 0x0f + +// CST340 definitions +#define TOUCH_CST340_I2C_ADDRESS 0x1a +#define TOUCH_CST340_REG_FINGER1 0x00 +#define TOUCH_CST340_EVT_CONTACT 0x06 + +// CHSC5445 definitions +#define TOUCH_CHSC5448_I2C_ADDRESS 0x2e +#define TOUCH_CHSC5448_REG_ADDR 0x2c000020 +#define TOUCH_CHSC5448_EVT_CONTACT 0x08 +#define TOUCH_CHSC5448_MAX_POINTS 5 + +typedef enum {TC_NONE, TC_FT6236, TC_CST340, TC_CHSC5448} TouchController; + +#if defined(DEBUG) +const char TOUCH_CONTROLLER_STR[][10] = {"", "FT6236", "CST340", "CHSC5448"}; +#endif + +TouchController touchController = TC_NONE; + +struct TouchControllerDescriptor +{ + bool (*hasTouchEvent)(); + bool (*touchRead)(uint16_t * X, uint16_t * Y); + void (*printDebugInfo)(); + bool needTranspose; +}; + +union rpt_point_t +{ + struct + { + unsigned char x_l8; + unsigned char y_l8; + unsigned char z; + unsigned char x_h4:4; + unsigned char y_h4:4; + unsigned char id:4; + unsigned char event:4; + }rp; + unsigned char data[5]; +}; + +extern uint8_t TouchControllerType; + +static const TouchControllerDescriptor *tcd = nullptr; +static TouchState internalTouchState = {}; +volatile static bool touchEventOccured; +static tmr10ms_t downTime = 0; +static tmr10ms_t tapTime = 0; +static short tapCount = 0; + +static void _touch_exti_isr(void) +{ + touchEventOccured = true; +} + +static void _touch_exti_stop(void) +{ + stm32_exti_disable(TOUCH_INT_EXTI_Line); +} + +static void _touch_exti_config(void) +{ + __HAL_RCC_SYSCFG_CLK_ENABLE(); + LL_SYSCFG_SetEXTISource(TOUCH_INT_EXTI_Port, TOUCH_INT_EXTI_SysCfgLine); + + stm32_exti_enable(TOUCH_INT_EXTI_Line, LL_EXTI_TRIGGER_FALLING, _touch_exti_isr); +} + +static void _touch_gpio_config(void) +{ + LL_GPIO_InitTypeDef gpioInit; + LL_GPIO_StructInit(&gpioInit); + + stm32_gpio_enable_clock(TOUCH_RST_GPIO); + stm32_gpio_enable_clock(TOUCH_INT_GPIO); + + gpioInit.Mode = LL_GPIO_MODE_OUTPUT; + gpioInit.Speed = LL_GPIO_SPEED_FREQ_LOW; + gpioInit.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + gpioInit.Pull = LL_GPIO_PULL_NO; + + gpioInit.Pin = TOUCH_RST_GPIO_PIN; + LL_GPIO_Init(TOUCH_RST_GPIO, &gpioInit); + LL_GPIO_SetOutputPin(TOUCH_RST_GPIO, TOUCH_RST_GPIO_PIN); + + gpioInit.Pin = TOUCH_INT_GPIO_PIN; + gpioInit.Mode = LL_GPIO_MODE_INPUT; + gpioInit.Pull = LL_GPIO_PULL_UP; + gpioInit.OutputType = LL_GPIO_OUTPUT_OPENDRAIN; + LL_GPIO_Init(TOUCH_INT_GPIO, &gpioInit); + LL_GPIO_SetOutputPin(TOUCH_INT_GPIO, TOUCH_INT_GPIO_PIN); +} + +static void _touch_reset() +{ + LL_GPIO_ResetOutputPin(TOUCH_RST_GPIO, TOUCH_RST_GPIO_PIN); + delay_ms(10); + LL_GPIO_SetOutputPin(TOUCH_RST_GPIO, TOUCH_RST_GPIO_PIN); + delay_ms(300); +} + +static void _i2c_init(void) +{ + TRACE("Touch I2C Init"); + + if (stm32_i2c_init(TOUCH_I2C_BUS, TOUCH_I2C_CLK_RATE) < 0) { + TRACE("Touch I2C Init ERROR: stm32_i2c_init failed"); + return; + } +} + +static void _i2c_reInit(void) +{ + stm32_i2c_deinit(TOUCH_I2C_BUS); + _i2c_init(); +} + +static int _i2c_read(uint8_t addr, uint32_t reg, uint8_t regSize, uint8_t* data, uint16_t len, uint32_t timeout) +{ + if (regSize > 2) { + if(stm32_i2c_master_tx(TOUCH_I2C_BUS, addr, (uint8_t*) ®, regSize, 3) < 0) + return false; + delay_us(5); + if(stm32_i2c_master_rx(TOUCH_I2C_BUS, addr, data, len, I2C_TIMEOUT_MAX) < 0) + return false; + return true; + } else { + return stm32_i2c_read(TOUCH_I2C_BUS, addr, reg, regSize, data, len, timeout); + } +} + +static uint8_t _i2c_readRetry(uint8_t addr, uint32_t reg, uint8_t regSize) +{ + uint8_t result; + uint8_t tryCount = 3; + while (_i2c_read(addr, reg, regSize, &result, 1, I2C_TIMEOUT_MAX) < 0) { + if (--tryCount == 0) break; + _i2c_reInit(); + } + return result; +} + +static uint16_t _i2c_readMultipleRetry(uint8_t addr, uint32_t reg, uint8_t regSize, uint8_t * buffer, uint16_t length) +{ + uint8_t tryCount = 3; + while (_i2c_read(addr, reg, regSize, buffer, length, I2C_TIMEOUT_MAX) < 0) { + if (--tryCount == 0) break; + _i2c_reInit(); + } + return length; +} + +static bool ft6236TouchRead(uint16_t * X, uint16_t * Y) +{ + // Read register FT6206_TD_STAT_REG to check number of touches detection + uint8_t nbTouch = _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6206_REG_TD_STAT, 1); + nbTouch &= TOUCH_FT6206_MASK_TD_STAT; + bool hasTouch = nbTouch > 0; + + if (hasTouch) { + uint8_t dataxy[4]; + // Read X and Y positions and event + _i2c_readMultipleRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6206_REG_P1_XH, 1, dataxy, sizeof(dataxy)); + + // Send back ready X position to caller + *X = ((dataxy[0] & 0x0f) << 8) | dataxy[1]; + // Send back ready Y position to caller + *Y = ((dataxy[2] & 0x0f) << 8) | dataxy[3]; + + uint8_t event = (dataxy[0] & TOUCH_FT6206_EVT_MASK) >> TOUCH_FT6206_EVT_SHIFT; + return event == TOUCH_FT6206_EVT_CONTACT; + } + return false; +} + +static bool ft6236HasTouchEvent() +{ + return touchEventOccured; +} + +static void ft6236PrintDebugInfo() +{ +#if defined(DEBUG) + TRACE("ft6x36: thrhld = %d", _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_TH_GROUP, 1) * 4); + TRACE("ft6x36: rep rate=", _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_PERIODACTIVE, 1) * 10); + TRACE("ft6x36: fw lib 0x%02X %02X", _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_LIB_VER_H, 1), + _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_LIB_VER_L, 1)); + TRACE("ft6x36: fw v 0x%02X", _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_FIRMID, 1)); + TRACE("ft6x36: CHIP ID 0x%02X", _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_CIPHER, 1)); + TRACE("ft6x36: CTPM ID 0x%02X", _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_FOCALTECH_ID, 1)); + TRACE("ft6x36: rel code 0x%02X", _i2c_readRetry(TOUCH_FT6236_I2C_ADDRESS, TOUCH_FT6236_REG_RELEASE_CODE_ID, 1)); +#endif + +} + +static bool cst340TouchRead(uint16_t * X, uint16_t * Y) +{ + uint8_t data[4]; + + // Read X and Y positions + _i2c_readMultipleRetry(TOUCH_CST340_I2C_ADDRESS, TOUCH_CST340_REG_FINGER1, 1, data, sizeof(data)); + + // Send back X position to caller + if (X) *X = ((data[1]<<4) + ((data[3]>>4)&0x0f)); + // Send back Y position to caller + if (Y) *Y = ((data[2]<<4) + ((data[3])&0x0f)); + + return data[0] == TOUCH_CST340_EVT_CONTACT; +} + +static bool cst340HasTouchEvent() +{ + static bool lastHasTouch = false; + bool ret = touchEventOccured; + if (ret) { + uint8_t hasTouch = cst340TouchRead(nullptr, nullptr); + if (!hasTouch && !lastHasTouch) { + TRACE("Interrupt occurs without touch event!!"); + touchEventOccured = false; + ret = false; + } + lastHasTouch = hasTouch; + } + + return ret; +} + +static void cst340PrintDebugInfo() +{ + // TODO, when necessary +} + +static bool chsc5448TouchRead(uint16_t * X, uint16_t * Y) +{ + static uint8_t readbuffer[84]; + const uint8_t reportSize = ((TOUCH_CHSC5448_MAX_POINTS * 5 + 2) + 3) & 0xfc; + + int ptCnt = 0; + union rpt_point_t* ppt; + + _i2c_readMultipleRetry(TOUCH_CHSC5448_I2C_ADDRESS, TOUCH_CHSC5448_REG_ADDR, 4, readbuffer, reportSize); + ptCnt = readbuffer[1] & 0x0f; + ppt = (union rpt_point_t*)&readbuffer[2]; + *X = ((ppt->rp.x_h4 & 0x0f) << 8) | ppt->rp.x_l8; + *Y = ((ppt->rp.y_h4 & 0x0f) << 8) | ppt->rp.y_l8; + uint8_t event = ppt->rp.event; + + return ptCnt > 0 && event == TOUCH_CHSC5448_EVT_CONTACT; +} + +static bool chsc5448HasTouchEvent() +{ + return touchEventOccured; +} + +static void chsc5448PrintDebugInfo() +{ + // TODO, when necessary +} + +static const TouchControllerDescriptor FT6236 = +{ + .hasTouchEvent = ft6236HasTouchEvent, + .touchRead = ft6236TouchRead, + .printDebugInfo = ft6236PrintDebugInfo, + .needTranspose = true, +}; + +static const TouchControllerDescriptor CST340 = +{ + .hasTouchEvent = cst340HasTouchEvent, + .touchRead = cst340TouchRead, + .printDebugInfo = cst340PrintDebugInfo, + .needTranspose = true, +}; + +static const TouchControllerDescriptor CHSC5448 = +{ + .hasTouchEvent = chsc5448HasTouchEvent, + .touchRead = chsc5448TouchRead, + .printDebugInfo = chsc5448PrintDebugInfo, + .needTranspose = false, +}; + +void _detect_touch_controller() +{ + if (stm32_i2c_is_dev_ready(TOUCH_I2C_BUS, TOUCH_CST340_I2C_ADDRESS, 3, I2C_TIMEOUT_MAX) == 0) { + touchController = TC_CST340; + tcd = &CST340; + TouchControllerType = 0; + } else if (stm32_i2c_is_dev_ready(TOUCH_I2C_BUS, TOUCH_CHSC5448_I2C_ADDRESS, 3, I2C_TIMEOUT_MAX) == 0) { + touchController = TC_CHSC5448; + tcd = &CHSC5448; + TouchControllerType = 0; + } else { + touchController = TC_FT6236; + tcd = &FT6236; + TouchControllerType = 1; + } +} + +void touchPanelDeInit() +{ + _touch_exti_stop(); +} + +bool touchPanelInit() +{ + _touch_gpio_config(); + _i2c_init(); + _touch_reset(); + _detect_touch_controller(); + _touch_exti_config(); + + tcd->printDebugInfo(); + + return touchController != TC_NONE; +} + +bool touchPanelEventOccured() +{ + return tcd->hasTouchEvent(); +} + +struct TouchState touchPanelRead() +{ + if (!touchEventOccured) return internalTouchState; + + touchEventOccured = false; + + tmr10ms_t now = get_tmr10ms(); + internalTouchState.tapCount = 0; + unsigned short touchX; + unsigned short touchY; + bool hasTouchContact = tcd->touchRead(&touchX, &touchY); + + if (tcd->needTranspose) { + // Touch sensor is rotated by 90 deg + unsigned short tmp = touchY; + touchY = 319 - touchX; + touchX = tmp; + } + + if (hasTouchContact) { + int dx = touchX - internalTouchState.x; + int dy = touchY - internalTouchState.y; + internalTouchState.x = touchX; + internalTouchState.y = touchY; + + if (internalTouchState.event == TE_NONE || internalTouchState.event == TE_UP || internalTouchState.event == TE_SLIDE_END) { + internalTouchState.startX = internalTouchState.x; + internalTouchState.startY = internalTouchState.y; + internalTouchState.event = TE_DOWN; + } + else if (internalTouchState.event == TE_DOWN) { + if (dx >= SLIDE_RANGE || dx <= -SLIDE_RANGE || dy >= SLIDE_RANGE || dy <= -SLIDE_RANGE) { + internalTouchState.event = TE_SLIDE; + internalTouchState.deltaX = (short) dx; + internalTouchState.deltaY = (short) dy; + } + else { + internalTouchState.event = TE_DOWN; + internalTouchState.deltaX = 0; + internalTouchState.deltaY = 0; + } + } + else if (internalTouchState.event == TE_SLIDE) { + internalTouchState.event = TE_SLIDE; //no change + internalTouchState.deltaX = (short) dx; + internalTouchState.deltaY = (short) dy; + } + + if (internalTouchState.event == TE_DOWN && downTime == 0) { + downTime = now; + } + } + else { + if (internalTouchState.event == TE_DOWN) { + internalTouchState.event = TE_UP; + if (now - downTime <= TAP_TIME) { + if (now - tapTime > TAP_TIME) { + tapCount = 1; + } else { + tapCount++; + } + internalTouchState.tapCount = tapCount; + tapTime = now; + } else { + internalTouchState.tapCount = 0; // not a tap + } + downTime = 0; + } else { + tapCount = 0; + internalTouchState.tapCount = 0; + internalTouchState.event = TE_SLIDE_END; + } + } + + TouchState ret = internalTouchState; + internalTouchState.deltaX = 0; + internalTouchState.deltaY = 0; + if (internalTouchState.event == TE_UP || internalTouchState.event == TE_SLIDE_END) + internalTouchState.event = TE_NONE; + +#if defined(DEBUG) + TRACE("%s: event=%d,X=%d,Y=%d", TOUCH_CONTROLLER_STR[touchController], ret.event, ret.x, ret.y); +#endif + + return ret; +} + +struct TouchState getInternalTouchState() +{ + return internalTouchState; +} diff --git a/radio/src/targets/pl18/touch_driver.h b/radio/src/targets/pl18/touch_driver.h new file mode 100644 index 00000000000..6b0fb01a8f6 --- /dev/null +++ b/radio/src/targets/pl18/touch_driver.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "touch.h" + +extern void touchPanelDeInit(); +extern bool touchPanelInit(); + +struct TouchState touchPanelRead(); +bool touchPanelEventOccured(); +struct TouchState getInternalTouchState(); From 72af39bcba62e5cd0bc830325653c3dae9d11fb3 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 2 Nov 2023 11:18:18 +0800 Subject: [PATCH 37/57] Fixed rebase problems due to changes in PR #3870. --- radio/src/storage/yaml/yaml_datastructs_pl18.cpp | 3 ++- radio/util/hw_defs/pot_config.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index 41afcb49841..7dc0354fa34 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -297,7 +297,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_UNSIGNED( "ppmunit", 2 ), YAML_CUSTOM("semver",nullptr,w_semver), YAML_CUSTOM("board",nullptr,w_board), - YAML_ARRAY("calib", 48, 22, struct_CalibData, NULL), + YAML_ARRAY("calib", 48, 20, struct_CalibData, NULL), YAML_PADDING( 16 ), YAML_SIGNED( "currModel", 8 ), YAML_UNSIGNED( "contrast", 8 ), @@ -368,6 +368,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_ARRAY("slidersConfig", 0, MAX_POTS, struct_sliderConfig, nullptr), YAML_ARRAY("potsConfig", 4, 16, struct_potConfig, nullptr), YAML_ARRAY("switchConfig", 2, 32, struct_switchConfig, nullptr), + YAML_ARRAY("flexSwitches", 0, MAX_FLEX_SWITCHES, struct_flexSwitch, flex_sw_valid), YAML_STRING("currModelFilename", 17), YAML_UNSIGNED( "modelQuickSelect", 1 ), YAML_UNSIGNED( "blOffBright", 7 ), diff --git a/radio/util/hw_defs/pot_config.py b/radio/util/hw_defs/pot_config.py index 637ac16717e..c9337255868 100644 --- a/radio/util/hw_defs/pot_config.py +++ b/radio/util/hw_defs/pot_config.py @@ -14,6 +14,20 @@ "P1": {"default": "POT_CENTER"}, "P2": {"default": "POT_CENTER"} }, + "pl18": { + "P1": {"default": "POT_CENTER"}, + "P2": {"default": "POT_CENTER"}, + "P3": {"default": "POT_CENTER"}, + "SL1": {"default": "SLIDER"}, + "SL2": {"default": "SLIDER"} + }, + "pl18ev": { + "P1": {"default": "POT_CENTER"}, + "P2": {"default": "POT_CENTER"}, + "P3": {"default": "POT_CENTER"}, + "SL1": {"default": "SLIDER"}, + "SL2": {"default": "SLIDER"} + }, "mt12": { "P1": {"default": "POT"}, "P2": {"default": "POT"}, From 1177c556637708fe7653f882e7ce27a3e01cc8ba Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 2 Nov 2023 13:10:08 +0800 Subject: [PATCH 38/57] Map PL18EV stick end pots and switched to EXT1-4. --- radio/src/targets/pl18/hal.h | 40 ++++++++++++++++-------------- radio/util/hw_defs/legacy_names.py | 14 +++++++++++ radio/util/hw_defs/pot_config.py | 6 ++++- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index a48f57b94bc..832a397854b 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -194,8 +194,10 @@ #define ADC_GPIO_PIN_SLIDER2 LL_GPIO_PIN_7 // PA.07 VRE/RS #if defined(RADIO_PL18EV) -#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_2 // PA.02 -#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_6 // PF.06 +#define ADC_GPIO_PIN_EXT1 LL_GPIO_PIN_5 // PA.05 +#define ADC_GPIO_PIN_EXT2 LL_GPIO_PIN_2 // PA.02 +#define ADC_GPIO_PIN_EXT3 LL_GPIO_PIN_6 // PF.06 +#define ADC_GPIO_PIN_EXT4 LL_GPIO_PIN_3 // PA.03 #endif #define ADC_GPIO_PIN_SWB LL_GPIO_PIN_1 // PC.01 @@ -204,20 +206,20 @@ #define ADC_GPIO_PIN_SWF LL_GPIO_PIN_0 // PB.00 #define ADC_GPIO_PIN_SWG LL_GPIO_PIN_1 // PB.01 #define ADC_GPIO_PIN_SWH LL_GPIO_PIN_10 // PF.10 -#if defined(RADIO_PL18EV) -#define ADC_GPIO_PIN_SWI LL_GPIO_PIN_3 // PA.03 -#define ADC_GPIO_PIN_SWJ LL_GPIO_PIN_5 // PA.05 -#endif +//#if defined(RADIO_PL18EV) +//#define ADC_GPIO_PIN_SWI LL_GPIO_PIN_3 // PA.03 +//#define ADC_GPIO_PIN_SWJ LL_GPIO_PIN_5 // PA.05 +//#endif #define ADC_GPIO_PIN_BATT LL_GPIO_PIN_5 // PC.05 #define ADC_GPIOA_PINS (ADC_GPIO_PIN_POT1 | ADC_GPIO_PIN_SLIDER2 | \ - ADC_GPIO_PIN_EXT1 | ADC_GPIO_PIN_SWI | ADC_GPIO_PIN_SWJ) + ADC_GPIO_PIN_EXT1 | ADC_GPIO_PIN_EXT2 | ADC_GPIO_PIN_EXT4) #define ADC_GPIOB_PINS (ADC_GPIO_PIN_SWF | ADC_GPIO_PIN_SWG) #define ADC_GPIOC_PINS (ADC_GPIO_PIN_POT2 | ADC_GPIO_PIN_BATT | \ ADC_GPIO_PIN_SWB | ADC_GPIO_PIN_SWD | ADC_GPIO_PIN_SWE) #define ADC_GPIOF_PINS (ADC_GPIO_PIN_POT3 | ADC_GPIO_PIN_SLIDER1 | \ - ADC_GPIO_PIN_EXT2 | ADC_GPIO_PIN_SWH) + ADC_GPIO_PIN_EXT3 | ADC_GPIO_PIN_SWH) #define ADC_CHANNEL_STICK_LH #define ADC_CHANNEL_STICK_LV @@ -230,11 +232,13 @@ #define ADC_CHANNEL_SLIDER1 LL_ADC_CHANNEL_7 // ADC3_IN7 -> ADC3_IN7 #define ADC_CHANNEL_SLIDER2 LL_ADC_CHANNEL_7 // ADC12_IN7 -> ADC1_IN7 -// Right stick end pot on PL18EV -#define ADC_CHANNEL_EXT1 LL_ADC_CHANNEL_2 // ADC123_IN2 -> ADC3_IN2 +// Left, right stick end pot on PL18EV +#define ADC_CHANNEL_EXT1 LL_ADC_CHANNEL_5 // ADC12_IN5 -> ADC1_IN5 +#define ADC_CHANNEL_EXT2 LL_ADC_CHANNEL_2 // ADC123_IN2 -> ADC1_IN2 -// Left stick end pot on PL18EV -#define ADC_CHANNEL_EXT2 LL_ADC_CHANNEL_4 // ADC3_IN4 -> ADC3_IN4 +// Left, right stick end buttons on PL18EV +#define ADC_CHANNEL_EXT3 LL_ADC_CHANNEL_4 // ADC3_IN4 -> ADC3_IN4 +#define ADC_CHANNEL_EXT4 LL_ADC_CHANNEL_3 // ADC123_IN3 -> ADC1_IN3 // Analog switches #define ADC_CHANNEL_SWB LL_ADC_CHANNEL_11 // ADC123_IN11 -> ADC3_IN11 @@ -243,8 +247,8 @@ #define ADC_CHANNEL_SWF LL_ADC_CHANNEL_8 // ADC12_IN8 -> ADC1_IN8 #define ADC_CHANNEL_SWG LL_ADC_CHANNEL_9 // ADC12_IN9 -> ADC1_IN9 #define ADC_CHANNEL_SWH LL_ADC_CHANNEL_8 // ADC3_IN8 -> ADC3_IN8 -#define ADC_CHANNEL_SWI LL_ADC_CHANNEL_3 // ADC123_IN3 -> ADC1_IN3 -#define ADC_CHANNEL_SWJ LL_ADC_CHANNEL_5 // ADC12_IN5 -> ADC1_IN5 +//#define ADC_CHANNEL_SWI LL_ADC_CHANNEL_3 // ADC123_IN3 -> ADC1_IN3 +//#define ADC_CHANNEL_SWJ LL_ADC_CHANNEL_5 // ADC12_IN5 -> ADC1_IN5 #define ADC_CHANNEL_BATT LL_ADC_CHANNEL_15 // ADC12_IN15 -> ADC1_IN15 #define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 @@ -253,7 +257,7 @@ #define ADC_EXT ADC3 #define ADC_EXT_CHANNELS \ - { ADC_CHANNEL_POT3, ADC_CHANNEL_SLIDER1, ADC_CHANNEL_EXT1, ADC_CHANNEL_EXT2, \ + { ADC_CHANNEL_POT3, ADC_CHANNEL_SLIDER1, ADC_CHANNEL_EXT3, \ ADC_CHANNEL_SWB, ADC_CHANNEL_SWD, ADC_CHANNEL_SWE, ADC_CHANNEL_SWH \ } @@ -278,7 +282,7 @@ 0,0,0,0, /* gimbals */ \ 0,0,0, /* pots */ \ -1,-1, /* sliders */ \ - 0,0, /* ext1&2 */ \ + 0,0,0,0, /* ext1-4 */ \ 0, /* vbat */ \ 0, /* rtc_bat */ \ -1, /* SWB */ \ @@ -286,9 +290,7 @@ 0, /* SWE */ \ 0, /* SWF */ \ 0, /* SWG */ \ - 0, /* SWH */ \ - 0, /* SWI */ \ - 0 /* SWJ */ \ + 0 /* SWH */ \ } #else #define ADC_DIRECTION { \ diff --git a/radio/util/hw_defs/legacy_names.py b/radio/util/hw_defs/legacy_names.py index 07afa6337c0..a313326a791 100644 --- a/radio/util/hw_defs/legacy_names.py +++ b/radio/util/hw_defs/legacy_names.py @@ -458,6 +458,20 @@ "short_label": "E2", "description": "Ext 2" }, + "EXT3": { + "yaml": "EXT3", + "lua": "ext3", + "label": "EXT3", + "short_label": "E3", + "description": "Ext 3" + }, + "EXT4": { + "yaml": "EXT4", + "lua": "ext4", + "label": "EXT4", + "short_label": "E4", + "description": "Ext 4" + } } }, { diff --git a/radio/util/hw_defs/pot_config.py b/radio/util/hw_defs/pot_config.py index c9337255868..ac1e4c29d3e 100644 --- a/radio/util/hw_defs/pot_config.py +++ b/radio/util/hw_defs/pot_config.py @@ -26,7 +26,11 @@ "P2": {"default": "POT_CENTER"}, "P3": {"default": "POT_CENTER"}, "SL1": {"default": "SLIDER"}, - "SL2": {"default": "SLIDER"} + "SL2": {"default": "SLIDER"}, + "EXT1": {"default": "POT_CENTER"}, + "EXT2": {"default": "POT_CENTER"}, + "EXT3": {"default": "MULTIPOS"}, + "EXT4": {"default": "MULTIPOS"} }, "mt12": { "P1": {"default": "POT"}, From fb26b695ad9d04222968f75aafd1b8eef959d6fa Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 6 Nov 2023 09:56:46 +0800 Subject: [PATCH 39/57] Fixed rebase problem due to PR #4027. --- radio/src/targets/pl18/board.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/radio/src/targets/pl18/board.h b/radio/src/targets/pl18/board.h index b8fdfc271ca..1523e931103 100644 --- a/radio/src/targets/pl18/board.h +++ b/radio/src/targets/pl18/board.h @@ -29,6 +29,8 @@ #include "hal.h" #include "hal/serial_port.h" +#include "watchdog_driver.h" + #define FLASHSIZE 0x200000 #define BOOTLOADER_SIZE 0x20000 #define FIRMWARE_ADDRESS 0x08000000 From 3fd40af4e32902eefd8f0dcf77da482d16ac1597 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Tue, 7 Nov 2023 21:08:35 +1000 Subject: [PATCH 40/57] chore: Update tests post #3279 merge --- radio/src/tests/primitives_EN_480x320.png | Bin 10452 -> 10233 bytes radio/src/tests/transparency_EN_480x320.png | Bin 10019 -> 9925 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/radio/src/tests/primitives_EN_480x320.png b/radio/src/tests/primitives_EN_480x320.png index 365a2a20fb2443c9d589542db84763eef94ce98d..5f36385a4dfaff50ad33f43fde05b99ba8f05df0 100644 GIT binary patch delta 6198 zcmai2X;>3i+ny!+%9;fN35#rk1Pu@YWfE5Rs8k~&9hGWC#0V~+ykv&3$z~9$h)aX0 zAGX-WiWc_)6cE$~%WF}Q22e};p`y@=itU%$zSs5s`{u{Fu5;$h^PKyB&Uwx=ckj}- zOAH>6H&HoludbdvLB!oI_ZCMah6o%QYfMjDHL*fA-eV(RgIP3_u^^_c#NK&v1mT2C zpC2JhA&LA#NPRvIlLGT>n^_w~1{;f}*hH)k3`%$Qc{99iXGUZ3yU2^|!}I z0j3XM9mYi)RyD-a#*h#>YaI)kj_KcObkewg`oq0y+YYzm|ub1uF zS4fP}tfmr``BBPf=tZdHU?Q1XF!;IUc#!`O{rdtXTNPc^+^xwXxz^HgP{(x&qfoq3 zLi%GL-L3!Z2MvvN*amHRT4nEze0br!azpZyO zqs?Q0+`ve)gBJW=Xv0h@2v^-H^x$IUamJl!CUxKpT+s$G*JatlX|3=X%I_a=&nwrn z(GCg+#Q7}7tlyI`wT_g)-TGV9kxOfD<~c`tu1J~$dP{#Pafe-j2BvB{D57wOw5Je3%D_1LH!3MbIm4pW}J zKVJxh3DmN<2ITR+h+ygjq+&|o-~ zkgX8RP~QXRl!{c^UC@Fh@k^oYzZTkY3&moL{j)tcH=(gvhuutO(^Wm^AXz1?i98}e zPnT?zMJbh#u7n)M9a&_Av1h|)^p<9yTvbn}f4p6?`HR-)A# z!1I8frgqw}a5msOt@1g!wk%lSktV}R@DQZ~yoGe1YM5;O!eAJtrQ4df7RJdX1$ah-cTGNna5POm-;U61xi|H$L40T8# zx#pmN*W6C|j*b#0(ZsyvjRf;`iOGQT#hN>@s$bITfR&VmBbuAj|Iku){yFPF8Vv~< zY}-esrW4)kjGLTYwOJ!HBJTShR(?~h+Y9PDX5TyPy9a|WsHulcgV2?Kv4cb-87Q5+ z_KW6mcO)9DsmFv|`9d%SaD>UY)8JbhI~8Kfg(>l~E1r3xxrGZ^I_T%fX@q^gNF=fk zIYOics)9^Dq!359Gky~OpVht^L&_~U0;hAMio5Y^HA{zqrQFe2WC|LDK2audCg!Rb zb{xc4)%(aN3-b=x;3G#$0zhH48=BNYV@)&ivB6Vra4G{35*dtjE`Q|bi{i9Df$PDU zFprbJ+T4XNx-SJ<}A>xD~^F{ym8??{OQe- zIb8n)2g%!#;hP0)elBrtx>*bIjHLqXly8HEfafa}Ev>C!K%cn(;wuZObBgVZU9%sP z;vu;`qvS%mNk#eH6?-^6M`oBaGH5T62a{}L!a{VRn z?%ns?_$Q1h-1X#wSY=G9gl)?)F2`v@9!A~h-dWzHxkYqaE$?S zw-dy+Xz5_deFe;nD~x0jd5&t}aWk0O2tHB~+fpQq8ZFcp2Y1aYreUSJm6{d!cHB+q zs@@J&0{Z}MAF296dcVXgAri|F!zTvmy&Tb*A5=?eG@8Ls34~GZ0omZ@C~ysJVGChL z-K-P)bc4x4cS#+P@+Ue8?jp*mfmj;To>|<69h$EI6Q;nT>o_HqHU;cR{LCT!%37`2 z0PvO&UiV7*xgsi5Ejx;)EW#Q$bOfndHRbYJtrTqomBxaZ}W{qt2+P<^M2NH094^L|6)bzACd&$PNa0brKYWb z>5Vb)fCa{;0vt5;4NU^IgCzFNKi8@(lY?M-Z^Wb<(6llSesZ3_1bG@xObjm)6KPi)3#7yqvWU#CET zkwoKcW9y6WxvHu!a0To!g8AvN1yM|8LIVw%koWnO<(&<2D^Uk}4RBid=6PaZ-7UvrAS_stOS;~TT3Ap46lU498q zL3N$-p#f)*^CnX8dSlP+EI>~_OSQyS^JA$u7yE6wlBS7&LLB9C zuW98(7>PI?8DHwGnY%k#1^o7J%nM!*?w>Lh9ws+$q>s*m76B)9DQg&vzf*J!S(fqD zBCx+0Ja8DHm^N?Ktu=yQID!|pxRH6%5oCRV$T;BMQ2zTc)g*ik56p$VcJe4svSS** zyBa@+tpnsr#^|FzfWyG(YdDjpa!}tU*Z2T&C}v+nq(!d3$)Rg^IR|uERsgeRrKB@P z{EQ2bF7#`BpF#Qf^jZ@XL8_~h80+fXfG#782#V9OZ7K1azH)2XW}(BKTOyIC%tYHV zk%~}`Q(M0uIXw^V%ZlZQ6ZYU&i-h6nKoNx1e?cA4CN*n8b(jEU)evm*r7AQ_*~+df zp!Ur}WbwFyQJq)qVDmX~4Kk&G?i{dy&X;pA=PL|5h~qBV2VWj1G^7jeZprOBaTx*! zIFK-qdE}PL0{nIsb87=RND`es`-;K&E-MQZ(j6aM5yuCIN@I>TXTUNrYnY;qrj1zO z(7D)#;I*a6*eyGCx)b(3nW%M^lSrfA&of~;)Olx#2{=f!pO5(pkF{G4_{D31ALwTF z?I+YUH|zxV%X_BTQ5WgyATwmzSZkMLH$oZnlBCfBmvLjuP@pI@L?j=}gXIo@==1eJ zd7i|p0xMveH1MnIT7UT%uq;}61oZD$==Xbjrsr7XKpJ%YZJi|VCJu&4O0oJ&k=L}1 z^4EZT)xN$L8?rNx(EeI^9ClQnYu`mP34EH%&0t z;OI;JU8J(9#BLjbj-&W&9i;vxV=5b%a506DgU2cCvZxxS zsTW)P5bH?6=a5w3mU=D9Y!EVCA6T@~rIXr0il+bf4lai>^UapwZ{GmsV}T+UTl2*h zn$8{<#^v^Bjl*)HWHZZ~0AyN)G=lE61zljHemB~T+pQxu?8xgXVzQiw7(PKO@R!3C z2|&>~%;Ownnzvx8g*Mu~pcwE@oF-T20&Q%$95@zDp61+0-_%}i-<-4=OfIA~zQ+r$ zfdg0bqrDPRO6?Ysu=5^rPobSI%>i4KYe9oFxkE5)N{d;|USkWCd&E0GXe0H_nbZ!q zvn@vFD{omJPtuM^?H-)mMn%<%6?fpz@NyQ+WH#RVUJ3PT2eyH-8_$j4!%{66aiU$y zC|pCcOg9EDEG8xbAMVcToMG2Ivvaq9XDv9HELUB3JYPr+(Iw)vrg#KjOU;=kFsatB z9VtFLV*RzbyL?`;R4qqVh6CMs5R*gY-gp)k-@-UgyAbNq+ot;CN#&*|)J)N6iU-%JuQ+{2?Rchum!ZV+9FXT^%aL3) zDt?{it&=%;&Pc)TuXM?B14T}h4Qr-AGB*glNTF{;|KtYJWtpZ$G z(-?miSY($h2mO2Hrt@{!Z*0@=txm^WW)Nf1ja7=$xOh^_W7uDVxf^ZZ00sW*?pGFy zwm(Oi51WI+caEA&LD=mZP>~96;q>3pk;H1kzKrNewOrsBWB7Na0L;@=>p>re^~Rcd z6eKxDj(ewfVY_y&RmLjGi#JG=SP`g!q)m7wlopDlMjJOfX65=9_h6noIvH$qGjOaA zBZr!Ph3?kRZHAIgTT`a{&)7ALdW`Rofao8x;5t&=Y z>uK{RsWpRQlvj`zOnD-el@ zJU}*lL;u+$vU#CuQ)dfUo4p6}U%%BDd;k~Hxw8@GkL7-c4_xmO-^x2%47DV-nOra z#CZ%2)3UF!E9vw*4@8Dw>8^C_XUePR_uc-TqvL4j!;1%$Y57vg_yObg+#x`kf_=`D zRs!9hS^6eQ?77%cu4NLwu?tz1H2lnP1z@f2%x6oF4Bm2_)xS7rX`l2dU@Ctdg{*Hs z<{UbpmE~)I@sEyUc9C{xjJ%R2w7+Z6~X6#W!fq}`yRrnS`nBa>4 zfx{<>W4Rdl(sL_$Ei$$)%^6xCjz3u}T1OOICFVoLm@UJ<5D8zLZH~(tqk5!hd1ut# zGu`TDJJ%#)FY+7bHrceSM5@+a)9YcAU*3s@XhZ8pJ>JHTL@Z?Or*f-CGta@2?rZ7d zSq9uC{3qcYPR==U7{zecrj1N7ybqQkRYG*%+QQZO6pc<`GxJ+gTDUjxUCO}h79hE5 zV(3m$&+%+|0Whpr*w-7zsj&wiET5i@7w%2*^w)m#O7d^_&I3HyzO=_3>$L&yrxJP2 zH5X=UW*@f7Rv8DnH6*rG!V{Kv5Aq?yQyXNNl0ap-)DRN(d>y)4(07;=qCK4)20xM! zXLFM}F=s8tRkP8Z_Hp)vfrIxzy9HjTML;v@xxe`Cd-grf1}$c)NkBf+@juBcDK9)a z1!1o{_mk@Ezm5H&&CI|RvKv9Q+he0p8R=tVh(ae8xAsg(t12aYonA(wl_}sQ4)v97 zXJ6#ukH}5@28`#K`;ukUQT>ikZw&Oj{`e_YV=OU9$7(9~wXLMsk9VF-e=JR$UUnd<-c8yYg>GlTF zx2t305@MI%)6i6NNU^KU^?OsR+WBo&T|(C?iLrHk$M>s(g-at4^s7+qPvZr2TP-~AX+=A^ z(hU=J*5e^gkabl~kio_{xJp!Ca988*{EtpOU-^-X4`o$;BA@A?Q-xxmYh#(nw%+4) zy)g4HFV>x6Pzv&|>f5$e_ z`kpeU_CRb^p(x)8JIN h_@7Y!zXM_(0A%yK%-E+twgJ{>Ud-a?ua#)t{{ffdlCl5* delta 6341 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 V*dWSqMgZHIKQA%nOq44Be*h9$<_Q1* diff --git a/radio/src/tests/transparency_EN_480x320.png b/radio/src/tests/transparency_EN_480x320.png index 54827cd16112e62f8a6a05e64fddbbd43ad7a28b..07088b77f4e7b009ae91822911f0942602367fe2 100644 GIT binary patch delta 6154 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 From 56c02695e451cda4ddca671f3c7c5ffbe397cf60 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Fri, 10 Nov 2023 19:04:30 +1000 Subject: [PATCH 41/57] fix: Regenerate yaml post #4267 --- radio/src/storage/yaml/yaml_datastructs_pl18.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index 7dc0354fa34..7dd9bea86fb 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -133,7 +133,6 @@ const struct YamlIdStr enum_LogicalSwitchesFunctions[] = { { LS_FUNC_VALMOSTEQUAL, "FUNC_VALMOSTEQUAL" }, { LS_FUNC_VPOS, "FUNC_VPOS" }, { LS_FUNC_VNEG, "FUNC_VNEG" }, - { LS_FUNC_RANGE, "FUNC_RANGE" }, { LS_FUNC_APOS, "FUNC_APOS" }, { LS_FUNC_ANEG, "FUNC_ANEG" }, { LS_FUNC_AND, "FUNC_AND" }, @@ -377,7 +376,7 @@ static const struct YamlNode struct_RadioData[] = { YAML_CUSTOM("rotEncDirection",r_rotEncDirection,nullptr), YAML_UNSIGNED( "rotEncMode", 2 ), YAML_SIGNED( "uartSampleMode", 2 ), - YAML_UNSIGNED( "stickDeadZone", 3 ), + YAML_PADDING( 3 ), YAML_UNSIGNED( "audioMuteEnable", 1 ), YAML_STRING("selectedTheme", 26), YAML_UNSIGNED( "radioThemesDisabled", 1 ), From 3812c9c19570c7587f2f68a2cb81b3cbb9fafe92 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Fri, 10 Nov 2023 19:07:09 +1000 Subject: [PATCH 42/57] chore: Update PL18 test image post #4290 --- radio/src/tests/bitmap_480x320.png | Bin 7162 -> 0 bytes radio/src/tests/images/color/bitmap_480x320.png | Bin 0 -> 23258 bytes .../{ => images/color}/clipping_480x320.png | Bin .../tests/{ => images/color}/lines_480x320.png | Bin .../tests/{ => images/color}/masks_480x320.png | Bin .../color}/primitives_EN_480x320.png | Bin .../color}/transparency_EN_480x320.png | Bin .../tests/{ => images/color}/vline_480x320.png | Bin 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 radio/src/tests/bitmap_480x320.png create mode 100644 radio/src/tests/images/color/bitmap_480x320.png rename radio/src/tests/{ => images/color}/clipping_480x320.png (100%) rename radio/src/tests/{ => images/color}/lines_480x320.png (100%) rename radio/src/tests/{ => images/color}/masks_480x320.png (100%) rename radio/src/tests/{ => images/color}/primitives_EN_480x320.png (100%) rename radio/src/tests/{ => images/color}/transparency_EN_480x320.png (100%) rename radio/src/tests/{ => images/color}/vline_480x320.png (100%) diff --git a/radio/src/tests/bitmap_480x320.png b/radio/src/tests/bitmap_480x320.png deleted file mode 100644 index 000c0fb07763fbd3424c4a28869d0731f27128db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7162 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 D+eN~3 diff --git a/radio/src/tests/images/color/bitmap_480x320.png b/radio/src/tests/images/color/bitmap_480x320.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d818510e9bf355b6c060f88fbacdc106dc64d1 GIT binary patch literal 23258 zcmY(qdt6N08$Z7G-g9g2(PgTsnaZV-nrhUP)SgP^KJG~}kxYa^G6;K`Zd4Q@oiHin z;G86cj)2&riF*h0{|k(ACv);e|ht>%oTvh;%OoB1)AuU$;N)9&Pl-0w(|eaN3uOGwRSGC zmsrF9`OtY$LRkZ?`M&>cvrDL-R1W|D7sU3I(4~fw+pDCM&|oF`K{n!l{QgfP7>lW- zPAO#kx1}7&zz6t^Di^!|4k#ykCHrd={yPD9yOfiij;k2|`N$mwmrNXJ{p9)I!GAQQ z|IUn@UT2EkEacz0vV)X~nJcAM`~UlOG1({CUHra(yMv|_(*B)@loD>DZMFEM+HUpl z&}75<|6iA2oqYf4|7#VRy~OX9M`3o@|5=qXm+X`5PWo@V68oQtSi_~MxQex1`rH3q z3EB9cWo2K{hduarjcgrXVA~^@)$s2+!h@ARx-0y*9f|(WL^#mAAmtB}M*q8#AC3Q6 z*8gMwbU7oNyD@uQ@4vfSYA+$X8+Z5L_Qe00NOn4K{y!#l|99yB$G+qq{IB`z#FWqs zS?IyR|LzsLjV~a(JJ|oj?S$|Csd1zeE2&_9ge=f6X7mhf9KmCAT&I^&?t5 z<41SXHvj8aq(}abAE472|1oLvzeE48eShr1|C(RI(6-v|YmN2)uTSZul!(xk=|g@I!Lo@&Wj`am8opTO949j{Y zw}T_H;$Bn;j&kO*-H+wXP*B%!IwlQ7wuL>5ioszbrD0l>0zc*3tORM}fthuj7 z1Xt~iYiYB(c8dLTwnyQ81=U{&ThrJMw7@ZDGVGWO4Kh(8R&#zvjyi%3#pqKP#U-@c zCo*=wg`u?1QEBxO{cV)B%9_7SKf}>_U=4NeGHyZ$i=oawTwACAVt=-q?ObuuyGYxg zOynucO$dV;>`T;UxC&&MY%!E?X3~eQ8RF%_kyuRZk;f%eY5cak+MgYjxS5ITd!FGY zmd-}!KTlZ%bS%sDxXltHp-YS|KKiDPo<3I+5sVd_v>NsMkj*`jV3#C3VuZ0VSE5$( z(ZX59i(UkGK2r7l#h9^}?AFATo1mpBcHn}Do(cXS=Qk6Btk0RDSY5s}C?rMOO>=7u zs`xbE)JV97=4Qps^xm{FI|^MHFZ*YB5N(v#I%0A;)E+@Q+{9B6D_^A86-}XpMqdad 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 literal 0 HcmV?d00001 diff --git a/radio/src/tests/clipping_480x320.png b/radio/src/tests/images/color/clipping_480x320.png similarity index 100% rename from radio/src/tests/clipping_480x320.png rename to radio/src/tests/images/color/clipping_480x320.png diff --git a/radio/src/tests/lines_480x320.png b/radio/src/tests/images/color/lines_480x320.png similarity index 100% rename from radio/src/tests/lines_480x320.png rename to radio/src/tests/images/color/lines_480x320.png diff --git a/radio/src/tests/masks_480x320.png b/radio/src/tests/images/color/masks_480x320.png similarity index 100% rename from radio/src/tests/masks_480x320.png rename to radio/src/tests/images/color/masks_480x320.png diff --git a/radio/src/tests/primitives_EN_480x320.png b/radio/src/tests/images/color/primitives_EN_480x320.png similarity index 100% rename from radio/src/tests/primitives_EN_480x320.png rename to radio/src/tests/images/color/primitives_EN_480x320.png diff --git a/radio/src/tests/transparency_EN_480x320.png b/radio/src/tests/images/color/transparency_EN_480x320.png similarity index 100% rename from radio/src/tests/transparency_EN_480x320.png rename to radio/src/tests/images/color/transparency_EN_480x320.png diff --git a/radio/src/tests/vline_480x320.png b/radio/src/tests/images/color/vline_480x320.png similarity index 100% rename from radio/src/tests/vline_480x320.png rename to radio/src/tests/images/color/vline_480x320.png From 328175b6c5ab4449300ab65b7604ebdf926f20ef Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 13 Nov 2023 14:22:10 +0800 Subject: [PATCH 43/57] Fixed SPI flash problem in reading the chip ID. --- .../src/targets/common/arm/stm32/spi_flash.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/radio/src/targets/common/arm/stm32/spi_flash.cpp b/radio/src/targets/common/arm/stm32/spi_flash.cpp index 100ce9559ec..c9e25567837 100644 --- a/radio/src/targets/common/arm/stm32/spi_flash.cpp +++ b/radio/src/targets/common/arm/stm32/spi_flash.cpp @@ -223,10 +223,19 @@ static bool flash_read_sfdp(SpiFlashDescriptor* desc) return true; } +void flashSpiSync() +{ + uint8_t status; + do { + flash_do_cmd(FLASH_CMD_STATUS, 0, &status, 1); + } while (status & 0x01); +} + bool flashSpiInit(void) { stm32_spi_init(&_flash_spi); delay_ms(1); + flashSpiSync(); if (!flash_read_id(&_flashDescriptor)) { return false; @@ -239,14 +248,6 @@ bool flashSpiInit(void) return true; } -void flashSpiSync() -{ - uint8_t status; - do { - flash_do_cmd(FLASH_CMD_STATUS, 0, &status, 1); - } while (status & 0x01); -} - uint32_t flashSpiGetSize() { return (1UL << _flashDescriptor.log2Size); From 56e9460fe715b1cc4c9f2cc11f9c5dcb620c4bf8 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 14 Nov 2023 11:14:43 +0800 Subject: [PATCH 44/57] Fixed PL18EV ADC channels mapping. --- radio/src/targets/pl18/hal.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 832a397854b..92e31fdb567 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -206,10 +206,6 @@ #define ADC_GPIO_PIN_SWF LL_GPIO_PIN_0 // PB.00 #define ADC_GPIO_PIN_SWG LL_GPIO_PIN_1 // PB.01 #define ADC_GPIO_PIN_SWH LL_GPIO_PIN_10 // PF.10 -//#if defined(RADIO_PL18EV) -//#define ADC_GPIO_PIN_SWI LL_GPIO_PIN_3 // PA.03 -//#define ADC_GPIO_PIN_SWJ LL_GPIO_PIN_5 // PA.05 -//#endif #define ADC_GPIO_PIN_BATT LL_GPIO_PIN_5 // PC.05 @@ -226,19 +222,23 @@ #define ADC_CHANNEL_STICK_RV #define ADC_CHANNEL_STICK_RH +// Each ADC cannot map more than 8 channels, otherwise it will cause problems + #define ADC_CHANNEL_POT1 LL_ADC_CHANNEL_6 // ADC12_IN6 -> ADC1_IN6 #define ADC_CHANNEL_POT2 LL_ADC_CHANNEL_14 // ADC12_IN14 -> ADC1_IN14 #define ADC_CHANNEL_POT3 LL_ADC_CHANNEL_6 // ADC3_IN6 -> ADC3_IN6 #define ADC_CHANNEL_SLIDER1 LL_ADC_CHANNEL_7 // ADC3_IN7 -> ADC3_IN7 #define ADC_CHANNEL_SLIDER2 LL_ADC_CHANNEL_7 // ADC12_IN7 -> ADC1_IN7 +#if defined(RADIO_PL18EV) // Left, right stick end pot on PL18EV #define ADC_CHANNEL_EXT1 LL_ADC_CHANNEL_5 // ADC12_IN5 -> ADC1_IN5 #define ADC_CHANNEL_EXT2 LL_ADC_CHANNEL_2 // ADC123_IN2 -> ADC1_IN2 // Left, right stick end buttons on PL18EV #define ADC_CHANNEL_EXT3 LL_ADC_CHANNEL_4 // ADC3_IN4 -> ADC3_IN4 -#define ADC_CHANNEL_EXT4 LL_ADC_CHANNEL_3 // ADC123_IN3 -> ADC1_IN3 +#define ADC_CHANNEL_EXT4 LL_ADC_CHANNEL_3 // ADC123_IN3 -> ADC3_IN3 +#endif // Analog switches #define ADC_CHANNEL_SWB LL_ADC_CHANNEL_11 // ADC123_IN11 -> ADC3_IN11 @@ -247,17 +247,19 @@ #define ADC_CHANNEL_SWF LL_ADC_CHANNEL_8 // ADC12_IN8 -> ADC1_IN8 #define ADC_CHANNEL_SWG LL_ADC_CHANNEL_9 // ADC12_IN9 -> ADC1_IN9 #define ADC_CHANNEL_SWH LL_ADC_CHANNEL_8 // ADC3_IN8 -> ADC3_IN8 -//#define ADC_CHANNEL_SWI LL_ADC_CHANNEL_3 // ADC123_IN3 -> ADC1_IN3 -//#define ADC_CHANNEL_SWJ LL_ADC_CHANNEL_5 // ADC12_IN5 -> ADC1_IN5 #define ADC_CHANNEL_BATT LL_ADC_CHANNEL_15 // ADC12_IN15 -> ADC1_IN15 + +#if !defined(RADIO_PL18EV) +// Disabled for PL18EV because 2 ADC 16 channels are fully mapped already #define ADC_CHANNEL_RTC_BAT LL_ADC_CHANNEL_VBAT // ADC1_IN18 +#endif #define ADC_MAIN ADC1 #define ADC_EXT ADC3 #define ADC_EXT_CHANNELS \ - { ADC_CHANNEL_POT3, ADC_CHANNEL_SLIDER1, ADC_CHANNEL_EXT3, \ + { ADC_CHANNEL_POT3, ADC_CHANNEL_SLIDER1, ADC_CHANNEL_EXT3, ADC_CHANNEL_EXT4, \ ADC_CHANNEL_SWB, ADC_CHANNEL_SWD, ADC_CHANNEL_SWE, ADC_CHANNEL_SWH \ } @@ -284,7 +286,6 @@ -1,-1, /* sliders */ \ 0,0,0,0, /* ext1-4 */ \ 0, /* vbat */ \ - 0, /* rtc_bat */ \ -1, /* SWB */ \ -1, /* SWD */ \ 0, /* SWE */ \ From 8f59a385e1f48e7a2f6a34bfc093e40a46c3734d Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 14 Nov 2023 15:39:31 +0800 Subject: [PATCH 45/57] Added companion build for pl18ev. --- tools/build-companion.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/build-companion.sh b/tools/build-companion.sh index 54c7a0f545d..d1dc928b62e 100755 --- a/tools/build-companion.sh +++ b/tools/build-companion.sh @@ -70,7 +70,7 @@ declare -a simulator_plugins=(x9lite x9lites tlite tpro lr3pro x9d x9dp x9dp2019 x9e xlite xlites - nv14 pl18 + nv14 pl18 pl18ev x10 x10-access x12s t16 t18 tx16s) @@ -173,6 +173,9 @@ do pl18) BUILD_OPTIONS+="-DPCB=PL18" ;; + pl18ev) + BUILD_OPTIONS+="-DPCB=PL18 -DPCBREV=PL18EV" + ;; *) echo "Unknown target: $target_name" exit 1 From 17f1f4fc10d7a6a3de73af34d4fea3305e26cc45 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 14 Nov 2023 15:39:59 +0800 Subject: [PATCH 46/57] Amended default pot configuration. --- radio/util/hw_defs/pot_config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/radio/util/hw_defs/pot_config.py b/radio/util/hw_defs/pot_config.py index ac1e4c29d3e..f80cd6fa142 100644 --- a/radio/util/hw_defs/pot_config.py +++ b/radio/util/hw_defs/pot_config.py @@ -15,15 +15,15 @@ "P2": {"default": "POT_CENTER"} }, "pl18": { - "P1": {"default": "POT_CENTER"}, - "P2": {"default": "POT_CENTER"}, - "P3": {"default": "POT_CENTER"}, + "P1": {"default": "POT"}, + "P2": {"default": "POT"}, + "P3": {"default": "POT"}, "SL1": {"default": "SLIDER"}, "SL2": {"default": "SLIDER"} }, "pl18ev": { "P1": {"default": "POT_CENTER"}, - "P2": {"default": "POT_CENTER"}, + "P2": {"default": "POT"}, "P3": {"default": "POT_CENTER"}, "SL1": {"default": "SLIDER"}, "SL2": {"default": "SLIDER"}, From 10a80b3079c0c9d423bdda617aa00f66c5ae05eb Mon Sep 17 00:00:00 2001 From: Richard Li Date: Wed, 15 Nov 2023 11:48:32 +0800 Subject: [PATCH 47/57] Refined PL18 bootloader. --- radio/src/targets/pl18/CMakeLists.txt | 8 +- .../src/targets/pl18/bootloader/boot_menu.cpp | 117 +++++++----------- radio/src/translations/cn.h | 10 ++ radio/src/translations/cz.h | 10 ++ radio/src/translations/da.h | 10 ++ radio/src/translations/de.h | 10 ++ radio/src/translations/en.h | 10 ++ radio/src/translations/es.h | 10 ++ radio/src/translations/fi.h | 10 ++ radio/src/translations/fr.h | 10 ++ radio/src/translations/he.h | 10 ++ radio/src/translations/it.h | 10 ++ radio/src/translations/jp.h | 10 ++ radio/src/translations/nl.h | 10 ++ radio/src/translations/pl.h | 10 ++ radio/src/translations/pt.h | 10 ++ radio/src/translations/ru.h | 10 ++ radio/src/translations/se.h | 10 ++ radio/src/translations/tw.h | 10 ++ 19 files changed, 221 insertions(+), 74 deletions(-) diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 588babf1625..580ab7b5de0 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -83,7 +83,10 @@ add_definitions(-DGPS_USART_BAUDRATE=${INTERNAL_GPS_BAUDRATE}) add_definitions(-DPWR_BUTTON_${PWR_BUTTON}) add_definitions(-DCROSSFIRE_NATIVE) add_definitions(-DHARDWARE_EXTERNAL_MODULE) -add_definitions(-DWIRELESS_CHARGER) + +if(WIRELESS_CHARGER) + add_definitions(-DWIRELESS_CHARGER) +endif() if(STICKS_DEAD_ZONE) add_definitions(-DSTICK_DEAD_ZONE) @@ -93,9 +96,6 @@ if(NOT UNEXPECTED_SHUTDOWN) add_definitions(-DNO_UNEXPECTED_SHUTDOWN) endif() -if(STICKS_DEAD_ZONE) - add_definitions(-DSTICK_DEAD_ZONE) -endif() set(AFHDS3 ON) # VCP CLI diff --git a/radio/src/targets/pl18/bootloader/boot_menu.cpp b/radio/src/targets/pl18/bootloader/boot_menu.cpp index 9b7333b27b4..3acc373092c 100644 --- a/radio/src/targets/pl18/bootloader/boot_menu.cpp +++ b/radio/src/targets/pl18/bootloader/boot_menu.cpp @@ -69,7 +69,7 @@ static void bootloaderDrawTitle(const char* text) static void bootloaderDrawFooter() { - lcd->drawSolidFilledRect(DEFAULT_PADDING, LCD_H - (DOUBLE_PADDING + 4), LCD_W - DOUBLE_PADDING, 2, BL_FOREGROUND); + lcd->drawSolidFilledRect(DEFAULT_PADDING, LCD_H - (DEFAULT_PADDING + 10), LCD_W - DOUBLE_PADDING, 2, BL_FOREGROUND); } static void bootloaderDrawBackground() @@ -87,84 +87,64 @@ void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) bootloaderDrawTitle(BOOTLOADER_TITLE); - lcd->drawText(62, 75, LV_SYMBOL_CHARGE, BL_FOREGROUND); - coord_t pos = lcd->drawText(84, 75, "Write Firmware", BL_FOREGROUND); + lcd->drawText(102, 75, LV_SYMBOL_CHARGE, BL_FOREGROUND); + coord_t pos = lcd->drawText(124, 75, TR_BL_WRITE_FW, BL_FOREGROUND); pos += 8; #if defined(SPI_FLASH) - lcd->drawText(60, 110, LV_SYMBOL_WARNING, BL_FOREGROUND); - pos = lcd->drawText(84, 110, "Erase Flash Storage", BL_FOREGROUND); + lcd->drawText(102, 110, LV_SYMBOL_SD_CARD, BL_FOREGROUND); + pos = lcd->drawText(124, 110, TR_BL_ERASE_FLASH, BL_FOREGROUND); pos += 8; - lcd->drawText(60, 145, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); - lcd->drawText(84, 145, "Exit", BL_FOREGROUND); + lcd->drawText(100, 145, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); + lcd->drawText(124, 145, TR_BL_EXIT, BL_FOREGROUND); #else - lcd->drawText(60, 110, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); - lcd->drawText(84, 110, "Exit", BL_FOREGROUND); + lcd->drawText(100, 110, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); + lcd->drawText(124, 110, TR_BL_EXIT, BL_FOREGROUND); #endif - pos -= 79; - lcd->drawSolidRect(79, 72 + (opt*35), pos, 26, 2, BL_SELECTED); + pos -= 92; + lcd->drawSolidRect(92, 72 + (opt * 35), pos, 26, 2, BL_SELECTED); - lcd->drawBitmap(center - 55, 165, (const BitmapBuffer*)&BMP_PLUG_USB); - lcd->drawText(center, 250, "Or plug in a USB cable", CENTERED | BL_FOREGROUND); - lcd->drawText(center, 275, "for mass storage", CENTERED | BL_FOREGROUND); + lcd->drawBitmap(60, 214, (const BitmapBuffer*)&BMP_PLUG_USB); + lcd->drawText(195, 223, TR_BL_USB_PLUGIN, BL_FOREGROUND); + lcd->drawText(195, 248, TR_BL_USB_MASS_STORE, BL_FOREGROUND); bootloaderDrawFooter(); - lcd->drawText(center, LCD_H - DOUBLE_PADDING, - "Current Firmware:", CENTERED | BL_FOREGROUND); - lcd->drawText(center, LCD_H - DEFAULT_PADDING, - getFirmwareVersion(nullptr), CENTERED | BL_FOREGROUND); + lcd->drawText(center, LCD_H - DEFAULT_PADDING, getFirmwareVersion(), CENTERED | BL_FOREGROUND); } - -// #if defined(SPI_FLASH) && defined(SDCARD) -// else if (st == ST_SELECT_STORAGE) { -// bootloaderDrawTitle(LV_SYMBOL_DIRECTORY " select storage"); -// lcd->drawText(62, 75, LV_SYMBOL_DIRECTORY, BL_FOREGROUND); -// coord_t pos = lcd->drawText(84, 75, "Internal", BL_FOREGROUND); -// pos += 8; -// lcd->drawText(60, 110, LV_SYMBOL_SD_CARD, BL_FOREGROUND); -// lcd->drawText(84, 110, "SD Card", BL_FOREGROUND); -// pos -= 79; -// lcd->drawSolidRect(79, (opt == 0) ? 72 : 107, pos, 26, 2, BL_SELECTED); -// bootloaderDrawFooter(); -// lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, -// "[R TRIM] to select storage", BL_FOREGROUND); -// lcd->drawText(DOUBLE_PADDING, LCD_H - DEFAULT_PADDING, -// LV_SYMBOL_NEW_LINE " [L TRIM] to exit", BL_FOREGROUND); -// } -// #endif - #if defined(SPI_FLASH) else if (st == ST_CLEAR_FLASH_CHECK) { - bootloaderDrawTitle("erase internal flash storage"); + bootloaderDrawTitle(TR_BL_ERASE_INT_FLASH); - lcd->drawText(62, 75, LV_SYMBOL_DRIVE, BL_FOREGROUND); - coord_t pos = lcd->drawText(84, 75, "Erase Flash Storage", BL_FOREGROUND); + lcd->drawText(102, 75, LV_SYMBOL_SD_CARD, BL_FOREGROUND); + coord_t pos = lcd->drawText(124, 75, TR_BL_ERASE_FLASH, BL_FOREGROUND); pos += 8; - lcd->drawText(60, 110, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); - lcd->drawText(84, 110, "Exit", BL_FOREGROUND); + lcd->drawText(100, 110, LV_SYMBOL_NEW_LINE, BL_FOREGROUND); + lcd->drawText(124, 110, TR_BL_EXIT, BL_FOREGROUND); - pos -= 79; - lcd->drawSolidRect(79, (opt == 0) ? 72 : 107, pos, 26, 2, BL_SELECTED); + pos -= 92; + lcd->drawSolidRect(92, 72 + (opt * 35), pos, 26, 2, BL_SELECTED); bootloaderDrawFooter(); - lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, "Hold [R TRIM] long to erase storage", BL_FOREGROUND); - lcd->drawText(DOUBLE_PADDING, LCD_H - DEFAULT_PADDING, LV_SYMBOL_NEW_LINE "[L TRIM] to exit", BL_FOREGROUND); + lcd->drawText(DEFAULT_PADDING, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_SD_CARD TR_BL_ERASE_KEY, BL_FOREGROUND); + lcd->drawText(305, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_NEW_LINE TR_BL_EXIT_KEY, BL_FOREGROUND); } else if (st == ST_CLEAR_FLASH) { - bootloaderDrawTitle("erasing internal flash storage"); + bootloaderDrawTitle(TR_BL_ERASE_INT_FLASH); - lcd->drawText(62, 75, "This may take up to 150s", BL_FOREGROUND); + lcd->drawText(center, 75, TR_BL_ERASE_FLASH_MSG, CENTERED | BL_FOREGROUND); bootloaderDrawFooter(); } #endif else if (st == ST_USB) { lcd->drawBitmap(center - 26, 98, (const BitmapBuffer*)&BMP_USB_PLUGGED); - lcd->drawText(center, 168, "USB Connected", CENTERED | BL_FOREGROUND); + lcd->drawText(center, 168, TR_BL_USB_CONNECTED, CENTERED | BL_FOREGROUND); } else if (st == ST_FILE_LIST || st == ST_DIR_CHECK || st == ST_FLASH_CHECK || st == ST_FLASHING || st == ST_FLASH_DONE) { @@ -187,9 +167,9 @@ void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) } else if (st == ST_DIR_CHECK) { if (opt == FR_NO_PATH) { lcd->drawText(20, MESSAGE_TOP, - LV_SYMBOL_CLOSE " Directory is missing", BL_FOREGROUND); + LV_SYMBOL_CLOSE TR_BL_DIR_MISSING, BL_FOREGROUND); } else { - lcd->drawText(20, MESSAGE_TOP, LV_SYMBOL_CLOSE " Directory is empty", + lcd->drawText(20, MESSAGE_TOP, LV_SYMBOL_CLOSE TR_BL_DIR_EMPTY, BL_FOREGROUND); } } else if (st == ST_FLASH_CHECK) { @@ -206,25 +186,24 @@ void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) lcd->drawText(LCD_W / 4 + DEFAULT_PADDING, MESSAGE_TOP - DEFAULT_PADDING, - "Fork:", RIGHT | BL_FOREGROUND); + TR_BL_FORK, RIGHT | BL_FOREGROUND); lcd->drawSizedText(LCD_W / 4 + 6 + DEFAULT_PADDING, MESSAGE_TOP - DEFAULT_PADDING, tag.fork, 6, BL_FOREGROUND); lcd->drawText(LCD_W / 4 + DEFAULT_PADDING, MESSAGE_TOP, - "Version:", RIGHT | BL_FOREGROUND); + TR_BL_VERSION, RIGHT | BL_FOREGROUND); lcd->drawText(LCD_W / 4 + 6 + DEFAULT_PADDING, MESSAGE_TOP, tag.version, BL_FOREGROUND); lcd->drawText(LCD_W / 4 + DEFAULT_PADDING, MESSAGE_TOP + DEFAULT_PADDING, - "Radio:", RIGHT | BL_FOREGROUND); + TR_BL_RADIO, RIGHT | BL_FOREGROUND); lcd->drawText(LCD_W / 4 + 6 + DEFAULT_PADDING, MESSAGE_TOP + DEFAULT_PADDING, tag.flavour, BL_FOREGROUND); - lcd->drawText(LCD_W - DOUBLE_PADDING, MESSAGE_TOP - 10, - LV_SYMBOL_OK, BL_GREEN); + lcd->drawText(DOUBLE_PADDING, MESSAGE_TOP, LV_SYMBOL_OK, BL_GREEN); } } @@ -232,27 +211,24 @@ void bootloaderDrawScreen(BootloaderState st, int opt, const char* str) if (st != ST_DIR_CHECK && (st != ST_FLASH_CHECK || opt == FC_OK)) { - lcd->drawText(DEFAULT_PADDING, LCD_H - DOUBLE_PADDING - 2, - LV_SYMBOL_CHARGE, BL_FOREGROUND); - if (st == ST_FILE_LIST) { - lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, - "[R TRIM] to select file", BL_FOREGROUND); + lcd->drawText(DEFAULT_PADDING, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_CHARGE TR_BL_SELECT_KEY, BL_FOREGROUND); } else if (st == ST_FLASH_CHECK && opt == FC_OK) { - lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, - "Hold [R TRIM] long to flash", BL_FOREGROUND); + lcd->drawText(DEFAULT_PADDING, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_CHARGE TR_BL_FLASH_KEY, BL_FOREGROUND); } else if (st == ST_FLASHING) { - lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, - "Writing Firmware ...", BL_FOREGROUND); + lcd->drawText(DEFAULT_PADDING, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_CHARGE TR_BL_WRITING_FW, BL_FOREGROUND); } else if (st == ST_FLASH_DONE) { - lcd->drawText(DOUBLE_PADDING, LCD_H - DOUBLE_PADDING, - "Writing Completed", BL_FOREGROUND); + lcd->drawText(DEFAULT_PADDING, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_CHARGE TR_BL_WRITING_COMPL, BL_FOREGROUND); } } if (st != ST_FLASHING) { - lcd->drawText(DOUBLE_PADDING, LCD_H - DEFAULT_PADDING, - LV_SYMBOL_NEW_LINE " [L TRIM] to exit", BL_FOREGROUND); + lcd->drawText(305, LCD_H - DEFAULT_PADDING, + LV_SYMBOL_NEW_LINE TR_BL_EXIT_KEY, BL_FOREGROUND); } } } @@ -263,7 +239,8 @@ void bootloaderDrawFilename(const char* str, uint8_t line, bool selected) lcd->drawText(DEFAULT_PADDING + 30, 75 + (line * 25), str, BL_FOREGROUND); if (selected) { - lcd->drawSolidRect(DEFAULT_PADDING + 25, 72 + (line * 25), LCD_W - (DEFAULT_PADDING + 25) - 28, 26, 2, BL_SELECTED); + lcd->drawSolidRect(DEFAULT_PADDING + 25, 72 + (line * 25), + LCD_W - (DEFAULT_PADDING + 25) - 28, 26, 2, BL_SELECTED); } } uint32_t bootloaderGetMenuItemCount(int baseCount) diff --git a/radio/src/translations/cn.h b/radio/src/translations/cn.h index d4b4af337e5..4f144dc0653 100644 --- a/radio/src/translations/cn.h +++ b/radio/src/translations/cn.h @@ -1032,6 +1032,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/cz.h b/radio/src/translations/cz.h index 660ee700c8f..a3e8df750a3 100644 --- a/radio/src/translations/cz.h +++ b/radio/src/translations/cz.h @@ -1053,6 +1053,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] pro vybrani souboru" #define TR_BL_FLASH_KEY "Drzet dlouze [R TRIM] pro nahrani" #define TR_BL_EXIT_KEY " [L TRIM] pro ukonceni" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/da.h b/radio/src/translations/da.h index 6b234030acb..ecffd00d5e1 100644 --- a/radio/src/translations/da.h +++ b/radio/src/translations/da.h @@ -1046,6 +1046,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] for at bruge fil" #define TR_BL_FLASH_KEY "[R TRIM] lang til for at starte" #define TR_BL_EXIT_KEY "[L TRIM] for at forlade" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/de.h b/radio/src/translations/de.h index bd56056ea42..e406ceabaf6 100644 --- a/radio/src/translations/de.h +++ b/radio/src/translations/de.h @@ -1034,6 +1034,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] um Datei auszuwählen" #define TR_BL_FLASH_KEY "Halte [R TRIM] gedrückt, zum schreiben" #define TR_BL_EXIT_KEY " [L TRIM] zum beenden" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // Taranis Info Zeile Anzeigen diff --git a/radio/src/translations/en.h b/radio/src/translations/en.h index a5e67d903e3..43750aa4d8f 100644 --- a/radio/src/translations/en.h +++ b/radio/src/translations/en.h @@ -1042,6 +1042,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/es.h b/radio/src/translations/es.h index 835072d661c..254c9866112 100644 --- a/radio/src/translations/es.h +++ b/radio/src/translations/es.h @@ -1044,6 +1044,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/fi.h b/radio/src/translations/fi.h index acc72488422..6814f004e32 100644 --- a/radio/src/translations/fi.h +++ b/radio/src/translations/fi.h @@ -1057,6 +1057,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/fr.h b/radio/src/translations/fr.h index 6773574f577..c72e0d68ab3 100644 --- a/radio/src/translations/fr.h +++ b/radio/src/translations/fr.h @@ -1058,6 +1058,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] pour sélect. fichier" #define TR_BL_FLASH_KEY "Appui long [R TRIM] pour flasher" #define TR_BL_EXIT_KEY " [L TRIM] pour quitter" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/he.h b/radio/src/translations/he.h index 234335035be..a2c47087b76 100644 --- a/radio/src/translations/he.h +++ b/radio/src/translations/he.h @@ -1050,6 +1050,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] ליציאה" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/it.h b/radio/src/translations/it.h index a84e61c3b1d..2489dd2f376 100644 --- a/radio/src/translations/it.h +++ b/radio/src/translations/it.h @@ -1041,6 +1041,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] per scegliere il file" #define TR_BL_FLASH_KEY "Tener premuto [R TRIM] per scrivere" #define TR_BL_EXIT_KEY " [L TRIM] per uscire" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/jp.h b/radio/src/translations/jp.h index 1e961499e0a..540897e2037 100644 --- a/radio/src/translations/jp.h +++ b/radio/src/translations/jp.h @@ -1037,6 +1037,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/nl.h b/radio/src/translations/nl.h index b92c9a84230..e7850c53634 100644 --- a/radio/src/translations/nl.h +++ b/radio/src/translations/nl.h @@ -1049,6 +1049,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/pl.h b/radio/src/translations/pl.h index a04fba07090..0ddc68d059c 100644 --- a/radio/src/translations/pl.h +++ b/radio/src/translations/pl.h @@ -1039,6 +1039,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] aby wybrac plik" #define TR_BL_FLASH_KEY "Przytrzymaj [R TRIM] aby flashowac" #define TR_BL_EXIT_KEY " [L TRIM] aby wyjsc" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/pt.h b/radio/src/translations/pt.h index d67c3aaa6a8..44d38b0295b 100644 --- a/radio/src/translations/pt.h +++ b/radio/src/translations/pt.h @@ -1045,6 +1045,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/ru.h b/radio/src/translations/ru.h index eff4b3afc01..9679e3aa863 100644 --- a/radio/src/translations/ru.h +++ b/radio/src/translations/ru.h @@ -1044,6 +1044,16 @@ #define TR_BL_SELECT_KEY "[П ТРИМ] для выбора файла" #define TR_BL_FLASH_KEY "Удерживайте [П ТРИМ] для прошивки" #define TR_BL_EXIT_KEY "[Л ТРИМ] для выхода" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/se.h b/radio/src/translations/se.h index 5af95224792..c3b427a1ecb 100644 --- a/radio/src/translations/se.h +++ b/radio/src/translations/se.h @@ -1071,6 +1071,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] foer att vaelja fil" #define TR_BL_FLASH_KEY "Tryck [R TRIM] foer att flasha" #define TR_BL_EXIT_KEY " [L TRIM] för att avsluta" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen diff --git a/radio/src/translations/tw.h b/radio/src/translations/tw.h index c589312a46e..0792092d191 100644 --- a/radio/src/translations/tw.h +++ b/radio/src/translations/tw.h @@ -1037,6 +1037,16 @@ #define TR_BL_SELECT_KEY "[R TRIM] to select file" #define TR_BL_FLASH_KEY "Hold [R TRIM] long to flash" #define TR_BL_EXIT_KEY " [L TRIM] to exit" +#elif defined(PCBPL18) + // Bootloader PL18 specific - Ascii only + #define TR_BL_RF_USB_ACCESS "RF USB access" + #define TR_BL_ERASE_INT_FLASH "Erase Internal Flash Storage" + #define TR_BL_ERASE_FLASH "Erase Flash Storage" + #define TR_BL_ERASE_FLASH_MSG "This may take up to 200s" + #define TR_BL_SELECT_KEY " [TR4 Dn] to select file" + #define TR_BL_FLASH_KEY " Hold [TR4 Dn] long to flash" + #define TR_BL_ERASE_KEY " Hold [TR4 Dn] long to erase" + #define TR_BL_EXIT_KEY " [TR4 Up] to exit" #endif // About screen From 3c2a92116a55777243b000a81b7537156973cc72 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 17 Nov 2023 12:53:29 +0800 Subject: [PATCH 48/57] Prevent calling to FrFTL when it is not initialized. --- .../src/targets/common/arm/stm32/diskio_spi_flash.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp b/radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp index bdd7db86785..3f8dc014b6f 100644 --- a/radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp +++ b/radio/src/targets/common/arm/stm32/diskio_spi_flash.cpp @@ -30,6 +30,7 @@ #include "drivers/frftl.h" static FrFTL _frftl; +static bool frftlInitDone = false; static bool flashRead(uint32_t addr, uint8_t* buf, uint32_t len) { @@ -89,6 +90,7 @@ static DSTATUS spi_flash_initialize(BYTE lun) if (!ftlInit(&_frftl, &_frftl_cb, flashSizeMB)) { return STA_NOINIT; } + frftlInitDone = true; #endif return 0; @@ -102,7 +104,7 @@ static DSTATUS spi_flash_status (BYTE lun) static DRESULT spi_flash_read(BYTE lun, BYTE * buff, DWORD sector, UINT count) { #if defined(USE_FLASH_FTL) - while(count) { + while(frftlInitDone && count) { if(!ftlRead(&_frftl, sector, (uint8_t*)buff)) { return RES_ERROR; @@ -122,7 +124,7 @@ static DRESULT spi_flash_read(BYTE lun, BYTE * buff, DWORD sector, UINT count) static DRESULT spi_flash_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count) { #if defined(USE_FLASH_FTL) - if (!ftlWrite(&_frftl, sector, count, (uint8_t*)buff)) { + if (frftlInitDone && !ftlWrite(&_frftl, sector, count, (uint8_t*)buff)) { return RES_ERROR; } #else @@ -166,7 +168,7 @@ static DRESULT spi_flash_ioctl(BYTE lun, BYTE ctrl, void *buff) case CTRL_SYNC: #if defined(USE_FLASH_FTL) - if (!ftlSync(&_frftl)) { + if (frftlInitDone && !ftlSync(&_frftl)) { res = RES_ERROR; } #else @@ -176,7 +178,7 @@ static DRESULT spi_flash_ioctl(BYTE lun, BYTE ctrl, void *buff) case CTRL_TRIM: #if defined(USE_FLASH_FTL) - if (!ftlTrim(&_frftl, *(DWORD*)buff, 1 + *((DWORD*)buff + 1) - *(DWORD*)buff)) { + if (frftlInitDone && !ftlTrim(&_frftl, *(DWORD*)buff, 1 + *((DWORD*)buff + 1) - *(DWORD*)buff)) { res = RES_ERROR; } #endif From bd0b2bdc292e8771b72bd5915d9d5c390fedc92e Mon Sep 17 00:00:00 2001 From: Richard Li Date: Sat, 18 Nov 2023 13:40:48 +0800 Subject: [PATCH 49/57] Added LED Strip. --- radio/src/targets/pl18/CMakeLists.txt | 3 +++ radio/src/targets/pl18/board.cpp | 15 +++++++++++++++ radio/src/targets/pl18/hal.h | 15 +++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index 580ab7b5de0..e614af6ce30 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -45,6 +45,7 @@ add_definitions(-DPCBPL18 -DPCBFLYSKY) add_definitions(-DBATTERY_CHARGE) add_definitions(-DSOFTWARE_VOLUME) add_definitions(-DSPI_FLASH) +add_definitions(-DSTM32_SUPPORT_32BIT_TIMERS) if(PCBREV STREQUAL PL18EV) set(FLAVOUR pl18ev) @@ -152,6 +153,8 @@ set(FIRMWARE_SRC targets/common/arm/stm32/dma2d.cpp targets/common/arm/stm32/spi_flash.cpp targets/common/arm/stm32/diskio_spi_flash.cpp + targets/common/arm/stm32/stm32_ws2812.cpp + boards/generic_stm32/rgb_leds.cpp drivers/frftl.cpp ) diff --git a/radio/src/targets/pl18/board.cpp b/radio/src/targets/pl18/board.cpp index 25e457de942..6a4a68535e8 100644 --- a/radio/src/targets/pl18/board.cpp +++ b/radio/src/targets/pl18/board.cpp @@ -21,6 +21,9 @@ #include "stm32_adc.h" +#include "stm32_ws2812.h" +#include "boards/generic_stm32/rgb_leds.h" + #include "board.h" #include "boards/generic_stm32/module_ports.h" @@ -163,6 +166,18 @@ void boardInit() touchPanelInit(); usbInit(); +#if defined(LED_STRIP_GPIO) + extern const stm32_pulse_timer_t _led_timer; + + ws2812_init(&_led_timer, LED_STRIP_LENGTH); + for (uint8_t i = 0; i < LED_STRIP_LENGTH; i++) { + ws2812_set_color(i, 0, 0, 50); + } + ws2812_update(&_led_timer); + +// stm32_pulse_set_cmp_val(&_led_timer, 998); // 998us +#endif + uint32_t press_start = 0; uint32_t press_end = 0; diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 92e31fdb567..2ac7b9d8864 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -555,6 +555,21 @@ #define FLYSKY_HALL_DMA_Stream_RX LL_DMA_STREAM_2 #define FLYSKY_HALL_DMA_Stream_TX LL_DMA_STREAM_4 +// LED Strip +#define LED_STRIP_LENGTH 4 +#define LED_STRIP_GPIO GPIOH +#define LED_STRIP_GPIO_PIN_DATA LL_GPIO_PIN_12 // PH.12 / TIM5_CH3 +#define LED_STRIP_GPIO_PIN_AF LL_GPIO_AF_2 // TIM3/4/5 +#define LED_STRIP_TIMER TIM5 +#define LED_STRIP_TIMER_FREQ (PERI1_FREQUENCY * TIMER_MULT_APB1) +#define LED_STRIP_TIMER_CHANNEL LL_TIM_CHANNEL_CH3 +#define LED_STRIP_TIMER_DMA DMA1 +#define LED_STRIP_TIMER_DMA_CHANNEL LL_DMA_CHANNEL_6 +#define LED_STRIP_TIMER_DMA_STREAM LL_DMA_STREAM_0 +#define LED_STRIP_TIMER_DMA_IRQn DMA1_Stream0_IRQn +#define LED_STRIP_TIMER_DMA_IRQHandler DMA1_Stream0_IRQHandler +#define LED_STRIP_REFRESH_PERIOD 50 //ms + // Internal Module #if defined(RADIO_PL18) #define INTMODULE_RCC_AHB1Periph (RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_DMA1) From 780658507095f27f53bf54a9b84d7b7c1fc12541 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 20 Nov 2023 12:37:46 +0800 Subject: [PATCH 50/57] Use LED strip for status LED. --- radio/src/targets/pl18/CMakeLists.txt | 1 + radio/src/targets/pl18/board.cpp | 6 +-- radio/src/targets/pl18/board.h | 7 ++++ radio/src/targets/pl18/hal.h | 9 ++-- radio/src/targets/pl18/led_driver.cpp | 60 +++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 radio/src/targets/pl18/led_driver.cpp diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index e614af6ce30..a977593af02 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -144,6 +144,7 @@ set(FIRMWARE_TARGET_SRC key_driver.cpp battery_driver.cpp backlight_driver.cpp + led_driver.cpp sdram_driver.c ) diff --git a/radio/src/targets/pl18/board.cpp b/radio/src/targets/pl18/board.cpp index 6a4a68535e8..dcd5f356016 100644 --- a/radio/src/targets/pl18/board.cpp +++ b/radio/src/targets/pl18/board.cpp @@ -166,18 +166,14 @@ void boardInit() touchPanelInit(); usbInit(); -#if defined(LED_STRIP_GPIO) extern const stm32_pulse_timer_t _led_timer; ws2812_init(&_led_timer, LED_STRIP_LENGTH); for (uint8_t i = 0; i < LED_STRIP_LENGTH; i++) { - ws2812_set_color(i, 0, 0, 50); + ws2812_set_color(i, 0, 0, 0); } ws2812_update(&_led_timer); -// stm32_pulse_set_cmp_val(&_led_timer, 998); // 998us -#endif - uint32_t press_start = 0; uint32_t press_end = 0; diff --git a/radio/src/targets/pl18/board.h b/radio/src/targets/pl18/board.h index 1523e931103..aab6ce9a7d6 100644 --- a/radio/src/targets/pl18/board.h +++ b/radio/src/targets/pl18/board.h @@ -140,6 +140,13 @@ const etx_serial_port_t* auxSerialGetPort(int port_nr); #define AUX_SERIAL_POWER_ON() #define AUX_SERIAL_POWER_OFF() +// LED driver +void ledInit(); +void ledOff(); +void ledRed(); +void ledBlue(); +void ledGreen(); + // LCD driver void lcdSetInitalFrameBuffer(void* fbAddress); void lcdInit(); diff --git a/radio/src/targets/pl18/hal.h b/radio/src/targets/pl18/hal.h index 2ac7b9d8864..ce3f32c9623 100644 --- a/radio/src/targets/pl18/hal.h +++ b/radio/src/targets/pl18/hal.h @@ -351,12 +351,6 @@ #define SPORT_UPDATE_RCC_AHB1Periph 0 #define HAS_SPORT_UPDATE_CONNECTOR() (false) -// Led -// #define STATUS_LEDS -#define LED_RCC_AHB1Periph RCC_AHB1Periph_GPIOI -#define LED_GPIO GPIOI -#define LED_GPIO_PIN GPIO_Pin_5 // PI.05 - // Serial Port (DEBUG) // We will temporarily used the PPM and the HEARTBEAT PINS #define AUX_SERIAL_RCC_AHB1Periph (RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOE) @@ -570,6 +564,9 @@ #define LED_STRIP_TIMER_DMA_IRQHandler DMA1_Stream0_IRQHandler #define LED_STRIP_REFRESH_PERIOD 50 //ms +#define STATUS_LEDS + + // Internal Module #if defined(RADIO_PL18) #define INTMODULE_RCC_AHB1Periph (RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOI | RCC_AHB1Periph_DMA1) diff --git a/radio/src/targets/pl18/led_driver.cpp b/radio/src/targets/pl18/led_driver.cpp new file mode 100644 index 00000000000..8b909202fd0 --- /dev/null +++ b/radio/src/targets/pl18/led_driver.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * 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 "board.h" +#include "boards/generic_stm32/rgb_leds.h" + +void ledInit() +{ + // Do nothing +} + +void ledOff() +{ + for (uint8_t i = 0; i < LED_STRIP_LENGTH; i++) { + rgbSetLedColor(i, 0, 0, 0); + } + rgbLedColorApply(); +} + +void ledRed() +{ + for (uint8_t i = 0; i < LED_STRIP_LENGTH; i++) { + rgbSetLedColor(i, 50, 0, 0); + } + rgbLedColorApply(); +} + +void ledGreen() +{ + for (uint8_t i = 0; i < LED_STRIP_LENGTH; i++) { + rgbSetLedColor(i, 0, 50, 0); + } + rgbLedColorApply(); +} + +void ledBlue() +{ + for (uint8_t i = 0; i < LED_STRIP_LENGTH; i++) { + rgbSetLedColor(i, 0, 0, 50); + } + rgbLedColorApply(); +} From 2ba55ffe964d63357ab388d91c30547e298238e8 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Mon, 20 Nov 2023 11:48:53 +0800 Subject: [PATCH 51/57] Fixed rebase problems due to changes in PR #4331. --- radio/src/targets/pl18/libopenui_config.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/radio/src/targets/pl18/libopenui_config.h b/radio/src/targets/pl18/libopenui_config.h index 90e0d3b6eca..5a5abf70a3f 100644 --- a/radio/src/targets/pl18/libopenui_config.h +++ b/radio/src/targets/pl18/libopenui_config.h @@ -21,15 +21,6 @@ #pragma once -constexpr uint32_t ALERT_FRAME_TOP = 70; -constexpr uint32_t ALERT_FRAME_HEIGHT = (LCD_H - 2 * ALERT_FRAME_TOP); -constexpr uint32_t ALERT_BITMAP_TOP = ALERT_FRAME_TOP + 15; -constexpr uint32_t ALERT_BITMAP_LEFT = 15; -constexpr uint32_t ALERT_TITLE_TOP = ALERT_FRAME_TOP + 10; -constexpr uint32_t ALERT_TITLE_LEFT = 186; -constexpr uint32_t ALERT_MESSAGE_TOP = ALERT_TITLE_TOP + 90; -constexpr uint32_t ALERT_MESSAGE_LEFT = ALERT_TITLE_LEFT; - constexpr coord_t INPUT_EDIT_CURVE_WIDTH = 132; constexpr coord_t INPUT_EDIT_CURVE_HEIGHT = INPUT_EDIT_CURVE_WIDTH; constexpr coord_t MENUS_MAX_HEIGHT = (MENUS_LINE_HEIGHT * 8) + 8; From 219ade50d5890b336b047d4b99f81e893315a1d5 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 21 Nov 2023 13:09:21 +0800 Subject: [PATCH 52/57] Fixed some typo in comments. --- radio/src/targets/pl18/lcd_driver.cpp | 2 +- radio/src/targets/pl18/touch_driver.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/radio/src/targets/pl18/lcd_driver.cpp b/radio/src/targets/pl18/lcd_driver.cpp index 5f5bfe3038e..4a3a84cebfa 100644 --- a/radio/src/targets/pl18/lcd_driver.cpp +++ b/radio/src/targets/pl18/lcd_driver.cpp @@ -147,7 +147,7 @@ static void lcdSpiConfig(void) { GPIO_InitStructure.Pin = LCD_NRST_GPIO_PIN; LL_GPIO_Init(LCD_NRST_GPIO, &GPIO_InitStructure); - /* Set the chip select pin aways low */ + /* Set the chip select pin always low */ LCD_CS_LOW(); } diff --git a/radio/src/targets/pl18/touch_driver.cpp b/radio/src/targets/pl18/touch_driver.cpp index 413f876b9d3..c672aa925bb 100644 --- a/radio/src/targets/pl18/touch_driver.cpp +++ b/radio/src/targets/pl18/touch_driver.cpp @@ -57,7 +57,7 @@ #define TOUCH_CST340_REG_FINGER1 0x00 #define TOUCH_CST340_EVT_CONTACT 0x06 -// CHSC5445 definitions +// CHSC5448 definitions #define TOUCH_CHSC5448_I2C_ADDRESS 0x2e #define TOUCH_CHSC5448_REG_ADDR 0x2c000020 #define TOUCH_CHSC5448_EVT_CONTACT 0x08 From 84544cb2cb12bc9692aa70bcb4e900b0c5d8d1ec Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 23 Nov 2023 15:51:02 +0800 Subject: [PATCH 53/57] Added LUA functionalities setup for RGB LEDs --- radio/src/gui/colorlcd/special_functions.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/radio/src/gui/colorlcd/special_functions.cpp b/radio/src/gui/colorlcd/special_functions.cpp index 20b2056ce45..1d35d1b0a43 100644 --- a/radio/src/gui/colorlcd/special_functions.cpp +++ b/radio/src/gui/colorlcd/special_functions.cpp @@ -180,20 +180,21 @@ class SpecialFunctionEditPage : public Page case FUNC_PLAY_TRACK: case FUNC_BACKGND_MUSIC: case FUNC_PLAY_SCRIPT: + case FUNC_RGB_LED: new StaticText(line, rect_t{}, STR_VALUE, 0, COLOR_THEME_PRIMARY1); new FileChoice( line, rect_t{}, - func == FUNC_PLAY_SCRIPT - ? SCRIPTS_FUNCS_PATH + func == FUNC_PLAY_SCRIPT || func == FUNC_RGB_LED + ? (func == FUNC_PLAY_SCRIPT ? SCRIPTS_FUNCS_PATH : SCRIPTS_RGB_PATH) : std::string(SOUNDS_PATH, SOUNDS_PATH_LNG_OFS) + std::string(currentLanguagePack->id, 2), - func == FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, + (func == FUNC_PLAY_SCRIPT || func == FUNC_RGB_LED) ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), [=]() { return std::string(cfn->play.name, ZLEN(cfn->play.name)); }, [=](std::string newValue) { strncpy(cfn->play.name, newValue.c_str(), sizeof(cfn->play.name)); SET_DIRTY(); - if (func == FUNC_PLAY_SCRIPT) + if (func == FUNC_PLAY_SCRIPT || func == FUNC_RGB_LED) LUA_LOAD_MODEL_SCRIPTS(); }, true); // strip extension From 0c82d086695266b209f43aab1f70ccd3a611d81a Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 23 Nov 2023 09:12:00 +0800 Subject: [PATCH 54/57] Fixed problems in LCD detection --- radio/src/targets/pl18/lcd_driver.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/radio/src/targets/pl18/lcd_driver.cpp b/radio/src/targets/pl18/lcd_driver.cpp index 4a3a84cebfa..7067e9d4cb7 100644 --- a/radio/src/targets/pl18/lcd_driver.cpp +++ b/radio/src/targets/pl18/lcd_driver.cpp @@ -234,12 +234,10 @@ unsigned char LCD_ReadByte(void) { LCD_MOSI_AS_INPUT(); for (i = 0; i < 8; i++) { LCD_SCK_LOW(); - LCD_DELAY(); - LCD_DELAY(); + lcdDelay(); ReceiveData <<= 1; LCD_SCK_HIGH(); - LCD_DELAY(); - LCD_DELAY(); + lcdDelay(); if (LCD_READ_DATA_PIN()) { ReceiveData |= 0x01; } @@ -253,8 +251,8 @@ unsigned char LCD_ReadRegister(unsigned char Register) { unsigned char ReadData = 0; lcdWriteByte(0, Register); - LCD_DELAY(); - LCD_DELAY(); + lcdDelay(); + lcdDelay(); ReadData = LCD_ReadByte(); return (ReadData); } @@ -1281,11 +1279,11 @@ unsigned int LCD_ST7796S_ReadID(void) { LCD_MOSI_AS_INPUT(); LCD_SCK_LOW(); - LCD_DELAY(); - LCD_DELAY(); + lcdDelay(); + lcdDelay(); LCD_SCK_HIGH(); - LCD_DELAY(); - LCD_DELAY(); + lcdDelay(); + lcdDelay(); LCD_ReadByte(); ID += (uint16_t)(LCD_ReadByte())<<8; @@ -2842,7 +2840,7 @@ void lcdInit(void) lcdOnFunction = LCD_HX8357D_On; lcdPixelClock = 12000000; } else if (LCD_ST7796S_ReadID() == LCD_ST7796S_ID ) { - TRACE("LCD INIT (default): ST7796S"); + TRACE("LCD INIT: ST7796S"); boardLcdType = "ST7796S"; lcdInitFunction = LCD_ST7796S_Init; lcdOffFunction = LCD_ST7796S_Off; @@ -2851,7 +2849,7 @@ void lcdInit(void) } else{ TRACE("LCD INIT (default): ST7796S"); - boardLcdType = "ST7796S"; + boardLcdType = "ST7796S (Default)"; lcdInitFunction = LCD_ST7796S_Init; lcdOffFunction = LCD_ST7796S_Off; lcdOnFunction = LCD_ST7796S_On; From f46e1888628c0d67cb4753f5be47fc4d3e6d3c5d Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 28 Nov 2023 12:29:41 +0800 Subject: [PATCH 55/57] chore: Update PL18 test image post #4337 --- .../images/color/primitives_EN_480x320.png | Bin 10233 -> 10452 bytes .../images/color/transparency_EN_480x320.png | Bin 9925 -> 10019 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/radio/src/tests/images/color/primitives_EN_480x320.png b/radio/src/tests/images/color/primitives_EN_480x320.png index 5f36385a4dfaff50ad33f43fde05b99ba8f05df0..365a2a20fb2443c9d589542db84763eef94ce98d 100644 GIT binary patch delta 6341 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 V*dWSqMgZHIKQA%nOq44Be*h9$<_Q1* delta 6198 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 diff --git a/radio/src/tests/images/color/transparency_EN_480x320.png b/radio/src/tests/images/color/transparency_EN_480x320.png index 07088b77f4e7b009ae91822911f0942602367fe2..54827cd16112e62f8a6a05e64fddbbd43ad7a28b 100644 GIT binary patch delta 6463 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 delta 6154 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 Date: Mon, 4 Dec 2023 16:59:47 +0800 Subject: [PATCH 56/57] Fixed build problem post #3846 --- radio/src/dataconstants.h | 2 +- .../storage/yaml/yaml_datastructs_pl18.cpp | 4 +- radio/src/targets/pl18/CMakeLists.txt | 13 +- radio/src/targets/pl18/backlight_driver.cpp | 1 - radio/src/targets/pl18/board.cpp | 44 +- radio/src/targets/pl18/board.h | 39 +- .../src/targets/pl18/startup_stm32f42_43xxx.s | 565 ------------------ radio/src/targets/pl18/stm32_ramboot.ld | 188 ------ radio/src/targets/pl18/stm32f4_flash.ld | 197 ------ .../targets/pl18/stm32f4_flash_bootloader.ld | 195 ------ 10 files changed, 24 insertions(+), 1224 deletions(-) delete mode 100644 radio/src/targets/pl18/startup_stm32f42_43xxx.s delete mode 100644 radio/src/targets/pl18/stm32_ramboot.ld delete mode 100644 radio/src/targets/pl18/stm32f4_flash.ld delete mode 100644 radio/src/targets/pl18/stm32f4_flash_bootloader.ld diff --git a/radio/src/dataconstants.h b/radio/src/dataconstants.h index c1e88d0b9f4..46432f18d72 100644 --- a/radio/src/dataconstants.h +++ b/radio/src/dataconstants.h @@ -612,7 +612,7 @@ enum Functions { #if defined(DEBUG) FUNC_MAX SKIP #else - FUNC_MAX SKIP = FUNC_TEST - 1 + FUNC_MAX SKIP = FUNC_TEST #endif }; diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp index 7dd9bea86fb..283256262bd 100644 --- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp @@ -93,6 +93,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SET_SCREEN, "SET_SCREEN" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, { FUNC_RGB_LED, "RGB_LED" }, + { FUNC_TEST, "TEST" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { @@ -286,7 +287,8 @@ static const struct YamlNode struct_CustomFunctionData[] = { YAML_ENUM("func", 6, enum_Functions), YAML_CUSTOM("def",r_customFn,w_customFn), YAML_PADDING( 48 ), - YAML_PADDING( 8 ), + YAML_PADDING( 1 ), + YAML_PADDING( 7 ), YAML_END }; static const struct YamlNode struct_RadioData[] = { diff --git a/radio/src/targets/pl18/CMakeLists.txt b/radio/src/targets/pl18/CMakeLists.txt index a977593af02..10eb2ed1469 100644 --- a/radio/src/targets/pl18/CMakeLists.txt +++ b/radio/src/targets/pl18/CMakeLists.txt @@ -23,19 +23,14 @@ set(USB_SERIAL ON CACHE BOOL "Enable USB serial (CDC)") set(HARDWARE_EXTERNAL_MODULE YES) set(WIRELESS_CHARGER YES) -if(BOOTLOADER) - set(LINKER_SCRIPT targets/pl18/stm32f4_flash_bootloader.ld) -else() - set(LINKER_SCRIPT targets/pl18/stm32f4_flash.ld) -endif() - #option(STICKS_DEAD_ZONE "Enable sticks dead zone" YES) #option(AFHDS2 "Support for AFHDS2" OFF) - # for size report script set(CPU_TYPE_FULL STM32F429xI) -set(SIZE_TARGET_MEM_DEFINE "MEM_SIZE_SDRAM2=8192") +set(TARGET_LINKER_DIR stm32f429_sdram) +set(TARGET_LINKER_PARAMS "-Wl,--defsym=__SDRAM_START__=0xC0000000") +set(SIZE_TARGET_MEM_DEFINE "MEM_SIZE_SDRAM1=8192") #set(RF_BAUD_RATE 921600 230400 115200 57600 38400 19200 9600 4800 2400 1200) #set(PCB_RF_BAUD 921600 CACHE STRING "INTERNAL_MODULE_BAUDRATE: ${RF_BAUD_RATE}") @@ -73,7 +68,7 @@ set(FLYSKY_GIMBAL ON) add_definitions( -DSTM32F429_439xx -DSTM32F429xx - -DSDRAM -DCOLORLCD -DLIBOPENUI + -DSDRAM -DCCMRAM -DCOLORLCD -DLIBOPENUI -DHARDWARE_TOUCH -DHARDWARE_KEYS -DSOFTWARE_KEYBOARD -DUSE_HATS_AS_KEYS) diff --git a/radio/src/targets/pl18/backlight_driver.cpp b/radio/src/targets/pl18/backlight_driver.cpp index c2299de114b..3ddd078c2c6 100644 --- a/radio/src/targets/pl18/backlight_driver.cpp +++ b/radio/src/targets/pl18/backlight_driver.cpp @@ -91,6 +91,5 @@ bool boardBacklightOn; bool isBacklightEnabled() { - if (globalData.unexpectedShutdown) return true; return boardBacklightOn; } diff --git a/radio/src/targets/pl18/board.cpp b/radio/src/targets/pl18/board.cpp index dcd5f356016..7c4cd43166b 100644 --- a/radio/src/targets/pl18/board.cpp +++ b/radio/src/targets/pl18/board.cpp @@ -30,6 +30,8 @@ #include "hal/adc_driver.h" #include "hal/trainer_driver.h" #include "hal/switch_driver.h" +#include "hal/abnormal_reboot.h" +#include "hal/watchdog_driver.h" #include "globals.h" #include "sdcard.h" @@ -41,7 +43,6 @@ #include "battery_driver.h" #include "touch_driver.h" -#include "watchdog_driver.h" #include "bitmapbuffer.h" #include "colors.h" @@ -60,43 +61,6 @@ extern "C" { // common ADC driver extern const etx_hal_adc_driver_t _adc_driver; -enum PowerReason { - SHUTDOWN_REQUEST = 0xDEADBEEF, - SOFTRESET_REQUEST = 0xCAFEDEAD, -}; - -constexpr uint32_t POWER_REASON_SIGNATURE = 0x0178746F; - -bool UNEXPECTED_SHUTDOWN() -{ -#if defined(SIMU) || defined(NO_UNEXPECTED_SHUTDOWN) - return false; -#else - if (WAS_RESET_BY_WATCHDOG()) - return true; - else if (WAS_RESET_BY_SOFTWARE()) - return RTC->BKP0R != SOFTRESET_REQUEST; - else - return RTC->BKP1R == POWER_REASON_SIGNATURE && RTC->BKP0R != SHUTDOWN_REQUEST; -#endif -} - -void SET_POWER_REASON(uint32_t value) -{ - RTC->BKP0R = value; - RTC->BKP1R = POWER_REASON_SIGNATURE; -} - -void watchdogInit(unsigned int duration) -{ - // IWDG->KR = 0x5555; // Unlock registers - // IWDG->PR = 3; // Divide by 32 => 1kHz clock - // IWDG->KR = 0x5555; // Unlock registers - // IWDG->RLR = duration; // 1.5 seconds nominal - // IWDG->KR = 0xAAAA; // reload - // IWDG->KR = 0xCCCC; // start -} - #if defined(SEMIHOSTING) extern "C" void initialise_monitor_handles(); #endif @@ -250,13 +214,13 @@ void boardOff() if (isChargerActive()) { delay_ms(100); // Add a delay to wait for lcdOff - RTC->BKP0R = SOFTRESET_REQUEST; +// RTC->BKP0R = SOFTRESET_REQUEST; NVIC_SystemReset(); } else #endif { - RTC->BKP0R = SHUTDOWN_REQUEST; +// RTC->BKP0R = SHUTDOWN_REQUEST; pwrOff(); } diff --git a/radio/src/targets/pl18/board.h b/radio/src/targets/pl18/board.h index aab6ce9a7d6..631f0b82d2d 100644 --- a/radio/src/targets/pl18/board.h +++ b/radio/src/targets/pl18/board.h @@ -28,8 +28,7 @@ #include "board_common.h" #include "hal.h" #include "hal/serial_port.h" - -#include "watchdog_driver.h" +#include "hal/watchdog_driver.h" #define FLASHSIZE 0x200000 #define BOOTLOADER_SIZE 0x20000 @@ -100,17 +99,6 @@ void SDRAM_Init(); #define BATTERY_MAX 43 // 4.3V #define BATTERY_DIVIDER 962 -enum EnumPowerupState -{ - BOARD_POWER_OFF = 0xCAFEDEAD, - BOARD_POWER_ON = 0xDEADBEEF, - BOARD_STARTED = 0xBAADF00D, - BOARD_REBOOT = 0xC00010FF, -}; - -bool UNEXPECTED_SHUTDOWN(); -void SET_POWER_REASON(uint32_t value); - #if defined(__cplusplus) && !defined(SIMU) extern "C" { #endif @@ -169,22 +157,19 @@ void backlightEnable(uint8_t dutyCycle); void backlightFullOn(); bool isBacklightEnabled(); -#define BACKLIGHT_ENABLE() \ - { \ - boardBacklightOn = true; \ - backlightEnable(globalData.unexpectedShutdown \ - ? BACKLIGHT_LEVEL_MAX \ - : BACKLIGHT_LEVEL_MAX - currentBacklightBright); \ +#define BACKLIGHT_ENABLE() \ + { \ + boardBacklightOn = true; \ + backlightEnable(BACKLIGHT_LEVEL_MAX - currentBacklightBright); \ } -#define BACKLIGHT_DISABLE() \ - { \ - boardBacklightOn = false; \ - backlightEnable(globalData.unexpectedShutdown ? BACKLIGHT_LEVEL_MAX \ - : ((g_eeGeneral.blOffBright == BACKLIGHT_LEVEL_MIN) && \ - (g_eeGeneral.backlightMode != e_backlight_mode_off)) \ - ? 0 \ - : g_eeGeneral.blOffBright); \ +#define BACKLIGHT_DISABLE() \ + { \ + boardBacklightOn = false; \ + backlightEnable(((g_eeGeneral.blOffBright == BACKLIGHT_LEVEL_MIN) && \ + (g_eeGeneral.backlightMode != e_backlight_mode_off)) \ + ? 0 \ + : g_eeGeneral.blOffBright); \ } #if !defined(SIMU) diff --git a/radio/src/targets/pl18/startup_stm32f42_43xxx.s b/radio/src/targets/pl18/startup_stm32f42_43xxx.s deleted file mode 100644 index 68671dc34cf..00000000000 --- a/radio/src/targets/pl18/startup_stm32f42_43xxx.s +++ /dev/null @@ -1,565 +0,0 @@ -/** - ****************************************************************************** - * @file startup_stm32f40_41xxx.s - * @author MCD Application Team - * @version V1.3.0 - * @date 08-November-2013 - * @brief STM32F40xxx/41xxx Devices vector table for RIDE7 toolchain. - * This module performs: - * - Set the initial SP - * - Set the initial PC == Reset_Handler, - * - Set the vector table entries with the exceptions ISR address - * - Configure the clock system and the external SRAM mounted on - * STM324xG-EVAL board to be used as data memory (optional, - * to be enabled by user) - * - Branches to main in the C library (which eventually - * calls main()). - * After Reset the Cortex-M4 processor is in Thread mode, - * priority is Privileged, and the Stack is set to Main. - ****************************************************************************** - * @attention - * - *

© COPYRIGHT 2013 STMicroelectronics

- * - * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/software_license_agreement_liberty_v2 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ****************************************************************************** - */ - - .syntax unified - .cpu cortex-m3 - .fpu softvfp - .thumb - -.global g_pfnVectors -.global Default_Handler - -/* start address for the initialization values of the .data section. -defined in linker script */ -.word _sidata -/* start address for the .data section. defined in linker script */ -.word _sdata -/* end address for the .data section. defined in linker script */ -.word _edata -/* start address for the .bss section. defined in linker script */ -.word _sbss -/* end address for the .bss section. defined in linker script */ -.word _ebss -/* stack used for SystemInit_ExtMemCtl; always internal RAM used */ - -/** - * @brief This is the code that gets called when the processor first - * starts execution following a reset event. Only the absolutely - * necessary set is performed, after which the application - * supplied main() routine is called. - * @param None - * @retval : None -*/ - - .section .text.Reset_Handler - .weak Reset_Handler - .type Reset_Handler, %function -Reset_Handler: - - bl pwrResetHandler /*jump to WDT reset handler where soft power control pin is turned on as soon as possible */ - -/* Copy the data segment initializers from flash to SRAM */ - movs r1, #0 - b LoopCopyDataInit - -CopyDataInit: - ldr r3, =_sidata - ldr r3, [r3, r1] - str r3, [r0, r1] - adds r1, r1, #4 - -LoopCopyDataInit: - ldr r0, =_sdata - ldr r3, =_edata - adds r2, r0, r1 - cmp r2, r3 - bcc CopyDataInit - ldr r2, =_sbss - b LoopFillZerobss -/* Zero fill the bss segment. */ -FillZerobss: - movs r3, #0 - str r3, [r2], #4 - -LoopFillZerobss: - ldr r3, = _ebss - cmp r2, r3 - bcc FillZerobss - -/*Paint Main Stack */ - ldr r2, = _main_stack_start -PaintMainStack: - movs r3, #0x55555555 - str r3, [r2], #4 -LoopPaintMainStack: - ldr r3, = _estack - cmp r2, r3 - bcc PaintMainStack - -/* Call the clock system intitialization function.*/ - bl SystemInit -/* Call C++ constructors for static objects */ - bl __libc_init_array -/* Call the application's entry point.*/ - bl main - bx lr -.size Reset_Handler, .-Reset_Handler - -/** - * @brief This is the code that gets called when the processor receives an - * unexpected interrupt. This simply enters an infinite loop, preserving - * the system state for examination by a debugger. - * @param None - * @retval None -*/ - .section .text.Default_Handler,"ax",%progbits -Default_Handler: -Infinite_Loop: - b Infinite_Loop - .size Default_Handler, .-Default_Handler -/****************************************************************************** -* -* The minimal vector table for a Cortex M3. Note that the proper constructs -* must be placed on this to ensure that it ends up at physical address -* 0x0000.0000. -* -*******************************************************************************/ - .section .isr_vector,"a",%progbits - .type g_pfnVectors, %object - .size g_pfnVectors, .-g_pfnVectors - - -g_pfnVectors: - .word _estack - .word Reset_Handler - .word NMI_Handler - .word HardFault_Handler - .word MemManage_Handler - .word BusFault_Handler - .word UsageFault_Handler - .word 0 - .word 0 - .word 0 - .word 0 - .word SVC_Handler - .word DebugMon_Handler - .word 0 - .word PendSV_Handler - .word SysTick_Handler - - /* External Interrupts */ - .word WWDG_IRQHandler /* Window WatchDog */ - .word PVD_IRQHandler /* PVD through EXTI Line detection */ - .word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line */ - .word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */ - .word FLASH_IRQHandler /* FLASH */ - .word RCC_IRQHandler /* RCC */ - .word EXTI0_IRQHandler /* EXTI Line0 */ - .word EXTI1_IRQHandler /* EXTI Line1 */ - .word EXTI2_IRQHandler /* EXTI Line2 */ - .word EXTI3_IRQHandler /* EXTI Line3 */ - .word EXTI4_IRQHandler /* EXTI Line4 */ - .word DMA1_Stream0_IRQHandler /* DMA1 Stream 0 */ - .word DMA1_Stream1_IRQHandler /* DMA1 Stream 1 */ - .word DMA1_Stream2_IRQHandler /* DMA1 Stream 2 */ - .word DMA1_Stream3_IRQHandler /* DMA1 Stream 3 */ - .word DMA1_Stream4_IRQHandler /* DMA1 Stream 4 */ - .word DMA1_Stream5_IRQHandler /* DMA1 Stream 5 */ - .word DMA1_Stream6_IRQHandler /* DMA1 Stream 6 */ - .word ADC_IRQHandler /* ADC1, ADC2 and ADC3s */ - .word CAN1_TX_IRQHandler /* CAN1 TX */ - .word CAN1_RX0_IRQHandler /* CAN1 RX0 */ - .word CAN1_RX1_IRQHandler /* CAN1 RX1 */ - .word CAN1_SCE_IRQHandler /* CAN1 SCE */ - .word EXTI9_5_IRQHandler /* External Line[9:5]s */ - .word TIM1_BRK_TIM9_IRQHandler /* TIM1 Break and TIM9 */ - .word TIM1_UP_TIM10_IRQHandler /* TIM1 Update and TIM10 */ - .word TIM1_TRG_COM_TIM11_IRQHandler /* TIM1 Trigger and Commutation and TIM11 */ - .word TIM1_CC_IRQHandler /* TIM1 Capture Compare */ - .word TIM2_IRQHandler /* TIM2 */ - .word TIM3_IRQHandler /* TIM3 */ - .word TIM4_IRQHandler /* TIM4 */ - .word I2C1_EV_IRQHandler /* I2C1 Event */ - .word I2C1_ER_IRQHandler /* I2C1 Error */ - .word I2C2_EV_IRQHandler /* I2C2 Event */ - .word I2C2_ER_IRQHandler /* I2C2 Error */ - .word SPI1_IRQHandler /* SPI1 */ - .word SPI2_IRQHandler /* SPI2 */ - .word USART1_IRQHandler /* USART1 */ - .word USART2_IRQHandler /* USART2 */ - .word USART3_IRQHandler /* USART3 */ - .word EXTI15_10_IRQHandler /* External Line[15:10]s */ - .word RTC_Alarm_IRQHandler /* RTC Alarm (A and B) through EXTI Line */ - .word OTG_FS_WKUP_IRQHandler /* USB OTG FS Wakeup through EXTI line */ - .word TIM8_BRK_TIM12_IRQHandler /* TIM8 Break and TIM12 */ - .word TIM8_UP_TIM13_IRQHandler /* TIM8 Update and TIM13 */ - .word TIM8_TRG_COM_TIM14_IRQHandler /* TIM8 Trigger and Commutation and TIM14 */ - .word TIM8_CC_IRQHandler /* TIM8 Capture Compare */ - .word DMA1_Stream7_IRQHandler /* DMA1 Stream7 */ - .word FSMC_IRQHandler /* FSMC */ - .word SDIO_IRQHandler /* SDIO */ - .word TIM5_IRQHandler /* TIM5 */ - .word SPI3_IRQHandler /* SPI3 */ - .word UART4_IRQHandler /* UART4 */ - .word UART5_IRQHandler /* UART5 */ - .word TIM6_DAC_IRQHandler /* TIM6 and DAC1&2 underrun errors */ - .word TIM7_IRQHandler /* TIM7 */ - .word DMA2_Stream0_IRQHandler /* DMA2 Stream 0 */ - .word DMA2_Stream1_IRQHandler /* DMA2 Stream 1 */ - .word DMA2_Stream2_IRQHandler /* DMA2 Stream 2 */ - .word DMA2_Stream3_IRQHandler /* DMA2 Stream 3 */ - .word DMA2_Stream4_IRQHandler /* DMA2 Stream 4 */ - .word ETH_IRQHandler /* Ethernet */ - .word ETH_WKUP_IRQHandler /* Ethernet Wakeup through EXTI line */ - .word CAN2_TX_IRQHandler /* CAN2 TX */ - .word CAN2_RX0_IRQHandler /* CAN2 RX0 */ - .word CAN2_RX1_IRQHandler /* CAN2 RX1 */ - .word CAN2_SCE_IRQHandler /* CAN2 SCE */ - .word OTG_FS_IRQHandler /* USB OTG FS */ - .word DMA2_Stream5_IRQHandler /* DMA2 Stream 5 */ - .word DMA2_Stream6_IRQHandler /* DMA2 Stream 6 */ - .word DMA2_Stream7_IRQHandler /* DMA2 Stream 7 */ - .word USART6_IRQHandler /* USART6 */ - .word I2C3_EV_IRQHandler /* I2C3 event */ - .word I2C3_ER_IRQHandler /* I2C3 error */ - .word OTG_HS_EP1_OUT_IRQHandler /* USB OTG HS End Point 1 Out */ - .word OTG_HS_EP1_IN_IRQHandler /* USB OTG HS End Point 1 In */ - .word OTG_HS_WKUP_IRQHandler /* USB OTG HS Wakeup through EXTI */ - .word OTG_HS_IRQHandler /* USB OTG HS */ - .word DCMI_IRQHandler /* DCMI */ - .word CRYP_IRQHandler /* CRYP crypto */ - .word HASH_RNG_IRQHandler /* Hash and Rng */ - .word FPU_IRQHandler /* FPU */ - .word UART7_IRQHandler /* UART7 */ - .word UART8_IRQHandler /* UART8 */ - .word SPI4_IRQHandler /* SPI4 */ - .word SPI5_IRQHandler /* SPI5 */ - .word SPI6_IRQHandler /* SPI6 */ - .word SAI1_IRQHandler /* SAI1 */ - .word LTDC_IRQHandler /* LTDC */ - .word LTDC_ER_IRQHandler /* LTDC error */ - .word DMA2D_IRQHandler /* DMA2D */ - -/******************************************************************************* -* -* Provide weak aliases for each Exception handler to the Default_Handler. -* As they are weak aliases, any function with the same name will override -* this definition. -* -*******************************************************************************/ - .weak NMI_Handler - .thumb_set NMI_Handler,Default_Handler - - .weak HardFault_Handler - .thumb_set HardFault_Handler,Default_Handler - - .weak MemManage_Handler - .thumb_set MemManage_Handler,Default_Handler - - .weak BusFault_Handler - .thumb_set BusFault_Handler,Default_Handler - - .weak UsageFault_Handler - .thumb_set UsageFault_Handler,Default_Handler - - .weak SVC_Handler - .thumb_set SVC_Handler,Default_Handler - - .weak DebugMon_Handler - .thumb_set DebugMon_Handler,Default_Handler - - .weak PendSV_Handler - .thumb_set PendSV_Handler,Default_Handler - - .weak SysTick_Handler - .thumb_set SysTick_Handler,Default_Handler - - .weak WWDG_IRQHandler - .thumb_set WWDG_IRQHandler,Default_Handler - - .weak PVD_IRQHandler - .thumb_set PVD_IRQHandler,Default_Handler - - .weak TAMP_STAMP_IRQHandler - .thumb_set TAMP_STAMP_IRQHandler,Default_Handler - - .weak RTC_WKUP_IRQHandler - .thumb_set RTC_WKUP_IRQHandler,Default_Handler - - .weak FLASH_IRQHandler - .thumb_set FLASH_IRQHandler,Default_Handler - - .weak RCC_IRQHandler - .thumb_set RCC_IRQHandler,Default_Handler - - .weak EXTI0_IRQHandler - .thumb_set EXTI0_IRQHandler,Default_Handler - - .weak EXTI1_IRQHandler - .thumb_set EXTI1_IRQHandler,Default_Handler - - .weak EXTI2_IRQHandler - .thumb_set EXTI2_IRQHandler,Default_Handler - - .weak EXTI3_IRQHandler - .thumb_set EXTI3_IRQHandler,Default_Handler - - .weak EXTI4_IRQHandler - .thumb_set EXTI4_IRQHandler,Default_Handler - - .weak DMA1_Stream0_IRQHandler - .thumb_set DMA1_Stream0_IRQHandler,Default_Handler - - .weak DMA1_Stream1_IRQHandler - .thumb_set DMA1_Stream1_IRQHandler,Default_Handler - - .weak DMA1_Stream2_IRQHandler - .thumb_set DMA1_Stream2_IRQHandler,Default_Handler - - .weak DMA1_Stream3_IRQHandler - .thumb_set DMA1_Stream3_IRQHandler,Default_Handler - - .weak DMA1_Stream4_IRQHandler - .thumb_set DMA1_Stream4_IRQHandler,Default_Handler - - .weak DMA1_Stream5_IRQHandler - .thumb_set DMA1_Stream5_IRQHandler,Default_Handler - - .weak DMA1_Stream6_IRQHandler - .thumb_set DMA1_Stream6_IRQHandler,Default_Handler - - .weak ADC_IRQHandler - .thumb_set ADC_IRQHandler,Default_Handler - - .weak CAN1_TX_IRQHandler - .thumb_set CAN1_TX_IRQHandler,Default_Handler - - .weak CAN1_RX0_IRQHandler - .thumb_set CAN1_RX0_IRQHandler,Default_Handler - - .weak CAN1_RX1_IRQHandler - .thumb_set CAN1_RX1_IRQHandler,Default_Handler - - .weak CAN1_SCE_IRQHandler - .thumb_set CAN1_SCE_IRQHandler,Default_Handler - - .weak EXTI9_5_IRQHandler - .thumb_set EXTI9_5_IRQHandler,Default_Handler - - .weak TIM1_BRK_TIM9_IRQHandler - .thumb_set TIM1_BRK_TIM9_IRQHandler,Default_Handler - - .weak TIM1_UP_TIM10_IRQHandler - .thumb_set TIM1_UP_TIM10_IRQHandler,Default_Handler - - .weak TIM1_TRG_COM_TIM11_IRQHandler - .thumb_set TIM1_TRG_COM_TIM11_IRQHandler,Default_Handler - - .weak TIM1_CC_IRQHandler - .thumb_set TIM1_CC_IRQHandler,Default_Handler - - .weak TIM2_IRQHandler - .thumb_set TIM2_IRQHandler,Default_Handler - - .weak TIM3_IRQHandler - .thumb_set TIM3_IRQHandler,Default_Handler - - .weak TIM4_IRQHandler - .thumb_set TIM4_IRQHandler,Default_Handler - - .weak I2C1_EV_IRQHandler - .thumb_set I2C1_EV_IRQHandler,Default_Handler - - .weak I2C1_ER_IRQHandler - .thumb_set I2C1_ER_IRQHandler,Default_Handler - - .weak I2C2_EV_IRQHandler - .thumb_set I2C2_EV_IRQHandler,Default_Handler - - .weak I2C2_ER_IRQHandler - .thumb_set I2C2_ER_IRQHandler,Default_Handler - - .weak SPI1_IRQHandler - .thumb_set SPI1_IRQHandler,Default_Handler - - .weak SPI2_IRQHandler - .thumb_set SPI2_IRQHandler,Default_Handler - - .weak USART1_IRQHandler - .thumb_set USART1_IRQHandler,Default_Handler - - .weak USART2_IRQHandler - .thumb_set USART2_IRQHandler,Default_Handler - - .weak USART3_IRQHandler - .thumb_set USART3_IRQHandler,Default_Handler - - .weak EXTI15_10_IRQHandler - .thumb_set EXTI15_10_IRQHandler,Default_Handler - - .weak RTC_Alarm_IRQHandler - .thumb_set RTC_Alarm_IRQHandler,Default_Handler - - .weak OTG_FS_WKUP_IRQHandler - .thumb_set OTG_FS_WKUP_IRQHandler,Default_Handler - - .weak TIM8_BRK_TIM12_IRQHandler - .thumb_set TIM8_BRK_TIM12_IRQHandler,Default_Handler - - .weak TIM8_UP_TIM13_IRQHandler - .thumb_set TIM8_UP_TIM13_IRQHandler,Default_Handler - - .weak TIM8_TRG_COM_TIM14_IRQHandler - .thumb_set TIM8_TRG_COM_TIM14_IRQHandler,Default_Handler - - .weak TIM8_CC_IRQHandler - .thumb_set TIM8_CC_IRQHandler,Default_Handler - - .weak DMA1_Stream7_IRQHandler - .thumb_set DMA1_Stream7_IRQHandler,Default_Handler - - .weak FSMC_IRQHandler - .thumb_set FSMC_IRQHandler,Default_Handler - - .weak SDIO_IRQHandler - .thumb_set SDIO_IRQHandler,Default_Handler - - .weak TIM5_IRQHandler - .thumb_set TIM5_IRQHandler,Default_Handler - - .weak SPI3_IRQHandler - .thumb_set SPI3_IRQHandler,Default_Handler - - .weak UART4_IRQHandler - .thumb_set UART4_IRQHandler,Default_Handler - - .weak UART5_IRQHandler - .thumb_set UART5_IRQHandler,Default_Handler - - .weak TIM6_DAC_IRQHandler - .thumb_set TIM6_DAC_IRQHandler,Default_Handler - - .weak TIM7_IRQHandler - .thumb_set TIM7_IRQHandler,Default_Handler - - .weak DMA2_Stream0_IRQHandler - .thumb_set DMA2_Stream0_IRQHandler,Default_Handler - - .weak DMA2_Stream1_IRQHandler - .thumb_set DMA2_Stream1_IRQHandler,Default_Handler - - .weak DMA2_Stream2_IRQHandler - .thumb_set DMA2_Stream2_IRQHandler,Default_Handler - - .weak DMA2_Stream3_IRQHandler - .thumb_set DMA2_Stream3_IRQHandler,Default_Handler - - .weak DMA2_Stream4_IRQHandler - .thumb_set DMA2_Stream4_IRQHandler,Default_Handler - - .weak ETH_IRQHandler - .thumb_set ETH_IRQHandler,Default_Handler - - .weak ETH_WKUP_IRQHandler - .thumb_set ETH_WKUP_IRQHandler,Default_Handler - - .weak CAN2_TX_IRQHandler - .thumb_set CAN2_TX_IRQHandler,Default_Handler - - .weak CAN2_RX0_IRQHandler - .thumb_set CAN2_RX0_IRQHandler,Default_Handler - - .weak CAN2_RX1_IRQHandler - .thumb_set CAN2_RX1_IRQHandler,Default_Handler - - .weak CAN2_SCE_IRQHandler - .thumb_set CAN2_SCE_IRQHandler,Default_Handler - - .weak OTG_FS_IRQHandler - .thumb_set OTG_FS_IRQHandler,Default_Handler - - .weak DMA2_Stream5_IRQHandler - .thumb_set DMA2_Stream5_IRQHandler,Default_Handler - - .weak DMA2_Stream6_IRQHandler - .thumb_set DMA2_Stream6_IRQHandler,Default_Handler - - .weak DMA2_Stream7_IRQHandler - .thumb_set DMA2_Stream7_IRQHandler,Default_Handler - - .weak USART6_IRQHandler - .thumb_set USART6_IRQHandler,Default_Handler - - .weak I2C3_EV_IRQHandler - .thumb_set I2C3_EV_IRQHandler,Default_Handler - - .weak I2C3_ER_IRQHandler - .thumb_set I2C3_ER_IRQHandler,Default_Handler - - .weak OTG_HS_EP1_OUT_IRQHandler - .thumb_set OTG_HS_EP1_OUT_IRQHandler,Default_Handler - - .weak OTG_HS_EP1_IN_IRQHandler - .thumb_set OTG_HS_EP1_IN_IRQHandler,Default_Handler - - .weak OTG_HS_WKUP_IRQHandler - .thumb_set OTG_HS_WKUP_IRQHandler,Default_Handler - - .weak OTG_HS_IRQHandler - .thumb_set OTG_HS_IRQHandler,Default_Handler - - .weak DCMI_IRQHandler - .thumb_set DCMI_IRQHandler,Default_Handler - - .weak CRYP_IRQHandler - .thumb_set CRYP_IRQHandler,Default_Handler - - .weak HASH_RNG_IRQHandler - .thumb_set HASH_RNG_IRQHandler,Default_Handler - - .weak FPU_IRQHandler - .thumb_set FPU_IRQHandler,Default_Handler - - .weak UART7_IRQHandler - .thumb_set UART7_IRQHandler,Default_Handler - - .weak UART8_IRQHandler - .thumb_set UART8_IRQHandler,Default_Handler - - .weak SPI4_IRQHandler - .thumb_set SPI4_IRQHandler,Default_Handler - - .weak SPI5_IRQHandler - .thumb_set SPI5_IRQHandler,Default_Handler - - .weak SPI6_IRQHandler - .thumb_set SPI6_IRQHandler,Default_Handler - - .weak SAI1_IRQHandler - .thumb_set SAI1_IRQHandler,Default_Handler - - .weak LTDC_IRQHandler - .thumb_set LTDC_IRQHandler,Default_Handler - - .weak LTDC_ER_IRQHandler - .thumb_set LTDC_ER_IRQHandler,Default_Handler - - .weak DMA2D_IRQHandler - .thumb_set DMA2D_IRQHandler,Default_Handler - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/radio/src/targets/pl18/stm32_ramboot.ld b/radio/src/targets/pl18/stm32_ramboot.ld deleted file mode 100644 index b15c1aa04c4..00000000000 --- a/radio/src/targets/pl18/stm32_ramboot.ld +++ /dev/null @@ -1,188 +0,0 @@ -/* -***************************************************************************** -** -** File : stm32f4_flash.ld -** -** Abstract : Linker script for STM32F439 Device with -** 2MByte FLASH, 192KByte SRAM, 64KByte CCM -** -** Set heap size, stack size and stack location according -** to application requirements. -** -** Set memory bank area and size if external memory is used. -** -** Target : STMicroelectronics STM32 -** -***************************************************************************** -*/ - -/* Entry Point */ -ENTRY(Reset_Handler) - -/* Highest address of the user mode stack */ -_estack = 0x2002FFF0; /* end of 192K RAM */ -_heap_end = 0xC0800000; /* end of 8192K SDRAM */ - -/* Generate a link error if heap and stack don't fit into RAM */ -_Min_Heap_Size = 0; /* required amount of heap */ -_Main_Stack_Size = 1024; /* required amount of stack for interrupt stack (Main stack) */ - -/* Main stack end */ -_main_stack_start = _estack - _Main_Stack_Size; - -/* Specify the memory areas */ -MEMORY -{ - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K - CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 65520 - NO_INIT (xrw) : ORIGIN = 0x1000FFF0, LENGTH = 16 - MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K - SDRAM(xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K -} - -/* Define output sections */ -SECTIONS -{ - /* The startup code goes first into FLASH */ - - /* The program code and other data goes into FLASH */ - .text : - { - . = ALIGN(4); - KEEP(*(.isr_vector)) /* Startup code */ - KEEP(*(.version)) - KEEP(*(.bootversiondata)) - . = ALIGN(4); /* Align the start of the text part */ - *(.text) /* .text sections (code) */ - *(.text*) /* .text* sections (code) */ - *(.rodata) /* .rodata sections (constants, strings, etc.) */ - *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ - *(.glue_7) /* glue arm to thumb code */ - *(.glue_7t) /* glue thumb to arm code */ - *(.eh_frame) - - KEEP (*(.init)) - KEEP (*(.fini)) - - . = ALIGN(4); - _etext = .; /* define a global symbols at end of code */ - } >FLASH - - .ARM.extab : - { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH - .ARM : { - __exidx_start = .; - *(.ARM.exidx*) - __exidx_end = .; - } >FLASH - - .preinit_array : - { - PROVIDE_HIDDEN (__preinit_array_start = .); - KEEP (*(.preinit_array*)) - PROVIDE_HIDDEN (__preinit_array_end = .); - } >FLASH - - .init_array : - { - PROVIDE_HIDDEN (__init_array_start = .); - KEEP (*(SORT(.init_array.*))) - KEEP (*(.init_array*)) - PROVIDE_HIDDEN (__init_array_end = .); - } >FLASH - - .fini_array : - { - PROVIDE_HIDDEN (__fini_array_start = .); - KEEP (*(.fini_array*)) - KEEP (*(SORT(.fini_array.*))) - PROVIDE_HIDDEN (__fini_array_end = .); - } >FLASH - - /* used by the startup to initialize data */ - _sidata = .; - - /* Initialized data sections goes into RAM, load LMA copy after code */ - .data : AT ( _sidata ) - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start */ - *(.data) /* .data sections */ - *(.data*) /* .data* sections */ - . = ALIGN(4); - _edata = .; /* define a global symbol at data end */ - } >CCM - - /* Uninitialized data section */ - . = ALIGN(4); - .bss : - { - /* This is used by the startup in order to initialize the .bss secion */ - _sbss = .; /* define a global symbol at bss start */ - /* __bss_start__ = _sbss; */ - *(.bss) - *(.bss*) - *(COMMON) - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end */ - } >CCM - - /* Non-zeroed data section */ - . = ALIGN(4); - .noinit (NOLOAD) : - { - *(.noinit) - } >NO_INIT - - - /* collect all uninitialized .ccm sections */ - .ram (NOLOAD) : - { - . = ALIGN(4); - _sram = .; - *(.ram) - } >RAM - - /* User_heap_stack section, used to check that there is enough RAM left */ - ._user_heap_stack : - { - . = ALIGN(4); - . = . + _Min_Heap_Size; - . = . + _Main_Stack_Size; - . = ALIGN(4); - } >RAM - - /* MEMORY_bank1 section, code must be located here explicitly */ - /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ - .memory_b1_text : - { - *(.mb1text) /* .mb1text sections (code) */ - *(.mb1text*) /* .mb1text* sections (code) */ - *(.mb1rodata) /* read-only data (constants) */ - *(.mb1rodata*) - } >MEMORY_B1 - - .sdram (NOLOAD) : - { - *(.sdram) - *(.sdram*) - *(.sdram_rodata) - *(.sdram_rodata*) - . = ALIGN(4); - _eram = .; - } >SDRAM - - PROVIDE ( end = _eram ); - PROVIDE ( _end = _eram ); - - /* Remove information from the standard libraries */ - /DISCARD/ : - { - libc.a ( * ) - libm.a ( * ) - libgcc.a ( * ) - } - - .ARM.attributes 0 : { *(.ARM.attributes) } -} diff --git a/radio/src/targets/pl18/stm32f4_flash.ld b/radio/src/targets/pl18/stm32f4_flash.ld deleted file mode 100644 index ac5c7ef74d5..00000000000 --- a/radio/src/targets/pl18/stm32f4_flash.ld +++ /dev/null @@ -1,197 +0,0 @@ -/* -***************************************************************************** -** -** File : stm32f4_flash.ld -** -** Abstract : Linker script for STM32F439 Device with -** 2MByte FLASH, 192KByte SRAM, 64KByte CCM -** -** Set heap size, stack size and stack location according -** to application requirements. -** -** Set memory bank area and size if external memory is used. -** -** Target : STMicroelectronics STM32 -** -***************************************************************************** -*/ - -/* Entry Point */ -ENTRY(Reset_Handler) - -/* Highest address of the user mode stack */ -_heap_end = 0xC0800000; /* end of 8192K SDRAM */ - -/* Generate a link error if heap and stack don't fit into RAM */ -_Min_Heap_Size = 0; /* required amount of heap */ -_Main_Stack_Size = 1024; /* required amount of stack for interrupt stack (Main stack) */ - -/* Main stack end */ -_main_stack_start = _estack - _Main_Stack_Size; - -/* Specify the memory areas */ -MEMORY -{ - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K - CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 65520 - NO_INIT (xrw) : ORIGIN = 0x1000FFF0, LENGTH = 16 - MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K - SDRAM(xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K -} - -/* Main stack end */ -_estack = 0x10000000 + LENGTH(CCM); -_main_stack_start = _estack - _Main_Stack_Size; - -/* Define output sections */ -SECTIONS -{ - /* The startup code goes first into FLASH */ - - /* The program code and other data goes into FLASH */ - .text : - { - FILL(0xFFFF) - - CREATE_OBJECT_SYMBOLS - - - - - _stext = .; /* Provide the name for the start of this section */ - . = ALIGN(4); - KEEP(*(.isr_vector)) /* Startup code */ - KEEP(*(.fwversiondata)) - *(.text) /* .text sections (code) */ - *(.text*) /* .text* sections (code) */ - *(.rodata) /* .rodata sections (constants, strings, etc.) */ - *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ - *(.glue_7) /* glue arm to thumb code */ - *(.glue_7t) /* glue thumb to arm code */ - *(.eh_frame) - - KEEP (*(.init)) - KEEP (*(.fini)) - - . = ALIGN(4); - _etext = .; /* define a global symbols at end of code */ - } >FLASH - - .ARM.extab : - { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH - .ARM : { - __exidx_start = .; - *(.ARM.exidx*) - __exidx_end = .; - } >FLASH - - .preinit_array : - { - PROVIDE_HIDDEN (__preinit_array_start = .); - KEEP (*(.preinit_array*)) - PROVIDE_HIDDEN (__preinit_array_end = .); - } >FLASH - - .init_array : - { - PROVIDE_HIDDEN (__init_array_start = .); - KEEP (*(SORT(.init_array.*))) - KEEP (*(.init_array*)) - PROVIDE_HIDDEN (__init_array_end = .); - } >FLASH - - .fini_array : - { - PROVIDE_HIDDEN (__fini_array_start = .); - KEEP (*(.fini_array*)) - KEEP (*(SORT(.fini_array.*))) - PROVIDE_HIDDEN (__fini_array_end = .); - } >FLASH - - /* used by the startup to initialize data */ - _sidata = .; - - /* Initialized data sections goes into RAM, load LMA copy after code */ - .data : AT ( _sidata ) - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start */ - *(.data) /* .data sections */ - *(.data*) /* .data* sections */ - . = ALIGN(4); - _edata = .; /* define a global symbol at data end */ - } >CCM - - /* Uninitialized data section */ - . = ALIGN(4); - .bss : - { - /* This is used by the startup in order to initialize the .bss secion */ - _sbss = .; /* define a global symbol at bss start */ - /* __bss_start__ = _sbss; */ - *(.bss) - *(.bss*) - *(COMMON) - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end */ - } >CCM - - /* Non-zeroed data section */ - . = ALIGN(4); - .noinit (NOLOAD) : - { - *(.noinit) - } >NO_INIT - - - /* collect all uninitialized .ccm sections */ - .ram (NOLOAD) : - { - . = ALIGN(4); - _sram = .; - *(.ram) - } >RAM - - /* User_heap_stack section, used to check that there is enough RAM left */ - ._user_heap_stack : - { - . = ALIGN(4); - . = . + _Min_Heap_Size; - . = . + _Main_Stack_Size; - . = ALIGN(4); - } >RAM - - /* MEMORY_bank1 section, code must be located here explicitly */ - /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ - .memory_b1_text : - { - *(.mb1text) /* .mb1text sections (code) */ - *(.mb1text*) /* .mb1text* sections (code) */ - *(.mb1rodata) /* read-only data (constants) */ - *(.mb1rodata*) - } >MEMORY_B1 - - .sdram (NOLOAD) : - { - *(.sdram) - *(.sdram*) - *(.sdram_rodata) - *(.sdram_rodata*) - . = ALIGN(4); - _eram = .; - } >SDRAM - - PROVIDE ( end = _eram ); - PROVIDE ( _end = _eram ); - - /* Remove information from the standard libraries */ - /DISCARD/ : - { - libc.a ( * ) - libm.a ( * ) - libgcc.a ( * ) - } - - .ARM.attributes 0 : { *(.ARM.attributes) } -} diff --git a/radio/src/targets/pl18/stm32f4_flash_bootloader.ld b/radio/src/targets/pl18/stm32f4_flash_bootloader.ld deleted file mode 100644 index 5160bfd9e73..00000000000 --- a/radio/src/targets/pl18/stm32f4_flash_bootloader.ld +++ /dev/null @@ -1,195 +0,0 @@ -/* -***************************************************************************** -** -** File : stm32f4_flash.ld -** -** Abstract : Linker script for STM32F439 Device with -** 2MByte FLASH, 192KByte SRAM, 64KByte CCM -** -** Set heap size, stack size and stack location according -** to application requirements. -** -** Set memory bank area and size if external memory is used. -** -** Target : STMicroelectronics STM32 -** -***************************************************************************** -*/ - -/* Entry Point */ -ENTRY(Reset_Handler) - -/* Highest address of the user mode stack */ -_estack = 0x10010000; /* end of 64K CCM */ -_heap_end = 0xC0800000; /* end of 8192K SDRAM */ - -/* Generate a link error if heap and stack don't fit into RAM */ -_Min_Heap_Size = 4096K; /* required amount of heap */ -_Main_Stack_Size = 8192; /* required amount of stack for interrupt stack (Main stack) */ - -/* Main stack end */ -_main_stack_start = _estack - _Main_Stack_Size; - -/* Specify the memory areas */ -MEMORY -{ - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K - CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K - MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K - SDRAM(xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K -} - -/* Define output sections */ -SECTIONS -{ - /* The startup code goes first into FLASH */ - - /* The program code and other data goes into FLASH */ - .text : - { - FILL(0xFFFF) - - CREATE_OBJECT_SYMBOLS - - KEEP(*(.bootloader)) /* Bootloader code */ - - . = 0x20000; /* Set the start of the main program */ - _stext = .; /* Provide the name for the start of this section */ - . = ALIGN(4); - KEEP(*(.isr_vector)) /* Startup code */ - KEEP(*(.fwversiondata)) - *(.text) /* .text sections (code) */ - *(.text*) /* .text* sections (code) */ - *(.rodata) /* .rodata sections (constants, strings, etc.) */ - *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ - *(.glue_7) /* glue arm to thumb code */ - *(.glue_7t) /* glue thumb to arm code */ - *(.eh_frame) - - KEEP (*(.init)) - KEEP (*(.fini)) - - . = ALIGN(4); - _etext = .; /* define a global symbols at end of code */ - } >FLASH - - .ARM.extab : - { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH - .ARM : { - __exidx_start = .; - *(.ARM.exidx*) - __exidx_end = .; - } >FLASH - - .preinit_array : - { - PROVIDE_HIDDEN (__preinit_array_start = .); - KEEP (*(.preinit_array*)) - PROVIDE_HIDDEN (__preinit_array_end = .); - } >FLASH - - .init_array : - { - PROVIDE_HIDDEN (__init_array_start = .); - KEEP (*(SORT(.init_array.*))) - KEEP (*(.init_array*)) - PROVIDE_HIDDEN (__init_array_end = .); - } >FLASH - - .fini_array : - { - PROVIDE_HIDDEN (__fini_array_start = .); - KEEP (*(.fini_array*)) - KEEP (*(SORT(.fini_array.*))) - PROVIDE_HIDDEN (__fini_array_end = .); - } >FLASH - - /* used by the startup to initialize data */ - _sidata = .; - - /* Initialized data sections goes into RAM, load LMA copy after code */ - .data : - { - . = ALIGN(4); - _sdata = .; /* create a global symbol at data start */ - *(.data) /* .data sections */ - *(.data*) /* .data* sections */ - . = ALIGN(4); - _edata = .; /* define a global symbol at data end */ - } >RAM AT> FLASH - - /* Uninitialized data section */ - .bss : - { - . = ALIGN(4); - /* This is used by the startup in order to initialize the .bss secion */ - _sbss = .; /* define a global symbol at bss start */ - /* __bss_start__ = _sbss; */ - *(.bss) - *(.bss*) - *(COMMON) - . = ALIGN(4); - _ebss = .; /* define a global symbol at bss end */ - } >RAM - - /* Non-zeroed data section */ - . = ALIGN(4); - .noinit (NOLOAD) : - { - *(.noinit) - } >RAM - - /* collect all uninitialized .ccm sections */ - .ram (NOLOAD) : - { - . = ALIGN(4); - _sram = .; - *(.ram) - } >RAM - - .ccm (NOLOAD) : - { - . = ALIGN(4); - _sccm = .; - *(.ccm) - . = ALIGN(4); - . = . + _Main_Stack_Size; - _eccm = .; - } >CCM - - /* MEMORY_bank1 section, code must be located here explicitly */ - /* Example: extern int foo(void) __attribute__ ((section (".mb1text"))); */ - .memory_b1_text : - { - *(.mb1text) /* .mb1text sections (code) */ - *(.mb1text*) /* .mb1text* sections (code) */ - *(.mb1rodata) /* read-only data (constants) */ - *(.mb1rodata*) - } >MEMORY_B1 - - .sdram (NOLOAD) : - { - *(.sdram) - *(.sdram*) - *(.sdram_rodata) - *(.sdram_rodata*) - _eram = .; - . = ALIGN(4); - . = . + _Min_Heap_Size; - . = ALIGN(4); - } >SDRAM - - PROVIDE ( end = _eram ); - PROVIDE ( _end = _eram ); - - /* Remove information from the standard libraries */ - /DISCARD/ : - { - libc.a ( * ) - libm.a ( * ) - libgcc.a ( * ) - } - - .ARM.attributes 0 : { *(.ARM.attributes) } -} From 1b6d7b35da4afd087761d7ef08d1beaa6d388dc1 Mon Sep 17 00:00:00 2001 From: Peter Feerick Date: Wed, 6 Dec 2023 08:37:35 +1000 Subject: [PATCH 57/57] fix: Unnecessary changes, typos, formatting --- companion/src/CMakeLists.txt | 4 ++-- companion/src/companion.qrc | 2 +- companion/src/firmwares/opentx/opentxinterface.cpp | 2 +- companion/src/simulation/simulateduiwidget.h | 2 +- radio/src/boards/generic_stm32/inputs.cpp | 2 +- radio/src/dataconstants.h | 4 ++-- radio/src/debug.h | 3 +-- radio/src/opentx.h | 4 ---- radio/src/sdcard.cpp | 4 ++-- radio/src/simu.cpp | 2 +- 10 files changed, 12 insertions(+), 17 deletions(-) diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt index c88b42061c3..7cb6f9242a8 100644 --- a/companion/src/CMakeLists.txt +++ b/companion/src/CMakeLists.txt @@ -316,9 +316,9 @@ elseif(PCB STREQUAL X10 AND PCBREV STREQUAL TX16S) set(FLAVOUR tx16s) elseif(PCB STREQUAL X10 AND PCBREV STREQUAL T18) set(FLAVOUR t18) - elseif(PCB STREQUAL NV14 AND PCBREV STREQUAL EL18) +elseif(PCB STREQUAL NV14 AND PCBREV STREQUAL EL18) set(FLAVOUR el18) - elseif(PCB STREQUAL PL18) +elseif(PCB STREQUAL PL18) set(FLAVOUR pl18) else() string(TOLOWER ${PCB} FLAVOUR) diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc index 075d53d6638..c28331e9a68 100644 --- a/companion/src/companion.qrc +++ b/companion/src/companion.qrc @@ -292,7 +292,7 @@ images/simulator/NV14/left.png images/simulator/NV14/right.png images/simulator/NV14/top.png - images/simulator/PL18/bottom.png + images/simulator/NV14/bottom.png images/simulator/PL18/left.png images/simulator/PL18/right.png images/simulator/PL18/top.png diff --git a/companion/src/firmwares/opentx/opentxinterface.cpp b/companion/src/firmwares/opentx/opentxinterface.cpp index ca8c88377b7..192fb4c32b7 100644 --- a/companion/src/firmwares/opentx/opentxinterface.cpp +++ b/companion/src/firmwares/opentx/opentxinterface.cpp @@ -1251,7 +1251,7 @@ void registerOpenTxFirmwares() registerOpenTxFirmware(firmware); /* FlySky PL18 board */ - firmware = new OpenTxFirmware(FIRMWAREID("pl18"), QCoreApplication::translate("Firmware", "FlySky PL18"), BOARD_FLYSKY_PL18); + firmware = new OpenTxFirmware(FIRMWAREID("pl18"), Firmware::tr("FlySky PL18"), BOARD_FLYSKY_PL18); addOpenTxFrskyOptions(firmware); firmware->addOption("bluetooth", Firmware::tr("Support for bluetooth module")); addOpenTxRfOptions(firmware, FLEX + AFHDS3); diff --git a/companion/src/simulation/simulateduiwidget.h b/companion/src/simulation/simulateduiwidget.h index 110e9da75b5..11f99a33743 100644 --- a/companion/src/simulation/simulateduiwidget.h +++ b/companion/src/simulation/simulateduiwidget.h @@ -396,7 +396,7 @@ class SimulatedUIWidgetNV14: public SimulatedUIWidget Ui::SimulatedUIWidgetNV14 * ui; }; -class SimulatedUIWidgetEL18 : public SimulatedUIWidget +class SimulatedUIWidgetEL18: public SimulatedUIWidget { Q_OBJECT diff --git a/radio/src/boards/generic_stm32/inputs.cpp b/radio/src/boards/generic_stm32/inputs.cpp index 0af70b757ea..c1c3341c049 100644 --- a/radio/src/boards/generic_stm32/inputs.cpp +++ b/radio/src/boards/generic_stm32/inputs.cpp @@ -43,7 +43,7 @@ __weak uint32_t readTrims() { uint32_t trims = _read_trims(); - #if defined(PCBXLITE) +#if defined(PCBXLITE) if (_read_keys() & (1 << KEY_SHIFT)) return ((trims & 0x03) << 6) | ((trims & 0x0c) << 2); #endif diff --git a/radio/src/dataconstants.h b/radio/src/dataconstants.h index 46432f18d72..c69b8071d12 100644 --- a/radio/src/dataconstants.h +++ b/radio/src/dataconstants.h @@ -409,9 +409,9 @@ enum PotsWarnMode { #endif #if NUM_TRIMS > 6 - #define MAX_TRIMS 8 +#define MAX_TRIMS 8 #else - #define MAX_TRIMS 6 +#define MAX_TRIMS 6 #endif #define MAX_XPOTS_POSITIONS (MAX_POTS * XPOTS_MULTIPOS_COUNT) diff --git a/radio/src/debug.h b/radio/src/debug.h index 02d3fc52b15..a5f84ddbec1 100644 --- a/radio/src/debug.h +++ b/radio/src/debug.h @@ -46,10 +46,9 @@ EXTERN_C(extern volatile uint32_t g_tmr10ms); #define debugPrintf(...) #endif - #define TRACE_TIME_FORMAT "%dms: " #define TRACE_TIME_VALUE (g_tmr10ms * 10) - + #define TRACE_NOCRLF(...) debugPrintf(__VA_ARGS__) #define TRACE(f_, ...) debugPrintf((TRACE_TIME_FORMAT f_ CRLF), TRACE_TIME_VALUE, ##__VA_ARGS__) #define DUMP(data, size) dump(data, size) diff --git a/radio/src/opentx.h b/radio/src/opentx.h index 437b2a72f58..ffb7cc60477 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -203,10 +203,6 @@ extern uint8_t heartbeat; #include "keys.h" #include "pwr.h" -// #if defined(PCBFRSKY) || defined(PCBNV14) || defined(PCBPL18) -// extern uint8_t potsPos[NUM_XPOTS]; -// #endif - bool trimDown(uint8_t idx); #if defined(KEYS_GPIO_REG_BIND) diff --git a/radio/src/sdcard.cpp b/radio/src/sdcard.cpp index 10a03799e8a..7b45c20d9f7 100644 --- a/radio/src/sdcard.cpp +++ b/radio/src/sdcard.cpp @@ -545,7 +545,7 @@ void sdMount() void sdDone() { TRACE("sdDone"); - + if (sdMounted()) { audioQueue.stopSD(); @@ -557,7 +557,7 @@ void sdDone() f_close(&g_bluetoothFile); #endif - f_mount(nullptr, "", 0); // unmount SD + f_mount(nullptr, "", 0); // unmount SD } storageDeInit(); diff --git a/radio/src/simu.cpp b/radio/src/simu.cpp index d1e9d9cde2a..e06ab5936ed 100644 --- a/radio/src/simu.cpp +++ b/radio/src/simu.cpp @@ -570,7 +570,7 @@ void OpenTxSim::refreshDisplay() setPixel(x, y, color); #else #if LCD_DEPTH == 4 - pixel_t* p = &simuLcdBuf[y / 2 * LCD_W + x]; + pixel_t *p = &simuLcdBuf[y / 2 * LCD_W + x]; uint8_t z = (y & 1) ? (*p >> 4) : (*p & 0x0F); if (z) { FXColor color;