From 100bea644c5ccbba335fae122267c1244578db6c Mon Sep 17 00:00:00 2001 From: Bernd Herzog Date: Sun, 12 May 2024 14:55:11 +0200 Subject: [PATCH] Version independent external apps (standalone apps) (#2145) This pull requests adds a new type of external app to the firmware: The standalone app. Pros: Will work after an upgrade. Size of image is only limited by shared heap size of M0 (application) (64kb total). Cons: No full access to all functions in the main firmware. One well defined (and versioned) API handles all communication. The Pacman app was converted to be the first the the new kind. --- firmware/CMakeLists.txt | 6 +- firmware/application/CMakeLists.txt | 1 + .../application/apps/ui_standalone_view.cpp | 82 ++++++ .../ui_standalone_view.hpp} | 24 +- firmware/application/external/external.cmake | 5 - firmware/application/external/external.ld | 7 - .../application/external/pacman/ui_pacman.cpp | 65 ----- .../ui_external_items_menu_loader.cpp | 107 ++++++++ .../ui_external_items_menu_loader.hpp | 2 + firmware/common/external_app.hpp | 9 +- firmware/common/standalone_app.hpp | 74 ++++++ firmware/standalone/CMakeLists.txt | 11 + firmware/standalone/pacman/CMakeLists.txt | 233 ++++++++++++++++++ .../pacman/DrawIndexedMap.h | 10 +- .../pacman/PacmanTiles.h | 0 .../external => standalone}/pacman/crntsc.h | 0 .../external => standalone}/pacman/crpal.h | 0 firmware/standalone/pacman/external.ld | 118 +++++++++ .../external => standalone}/pacman/main.cpp | 57 +++-- firmware/standalone/pacman/pacman.cpp | 80 ++++++ firmware/standalone/pacman/pacman.hpp | 33 +++ .../pacman/playfield.hpp | 2 + firmware/tools/copy_external_apps.sh | 1 + 23 files changed, 807 insertions(+), 120 deletions(-) create mode 100644 firmware/application/apps/ui_standalone_view.cpp rename firmware/application/{external/pacman/ui_pacman.hpp => apps/ui_standalone_view.hpp} (63%) delete mode 100644 firmware/application/external/pacman/ui_pacman.cpp create mode 100644 firmware/common/standalone_app.hpp create mode 100644 firmware/standalone/CMakeLists.txt create mode 100644 firmware/standalone/pacman/CMakeLists.txt rename firmware/{application/external => standalone}/pacman/DrawIndexedMap.h (86%) rename firmware/{application/external => standalone}/pacman/PacmanTiles.h (100%) rename firmware/{application/external => standalone}/pacman/crntsc.h (100%) rename firmware/{application/external => standalone}/pacman/crpal.h (100%) create mode 100644 firmware/standalone/pacman/external.ld rename firmware/{application/external => standalone}/pacman/main.cpp (53%) create mode 100644 firmware/standalone/pacman/pacman.cpp create mode 100644 firmware/standalone/pacman/pacman.hpp rename firmware/{application/external => standalone}/pacman/playfield.hpp (99%) diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index cfa680f8..0097ec59 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -50,6 +50,7 @@ endif() add_subdirectory(application) add_subdirectory(baseband) +add_subdirectory(standalone) add_subdirectory(test) # NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake? @@ -88,7 +89,7 @@ add_custom_target( program-external-apps WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${PROJECT_SOURCE_DIR}/tools/copy_external_apps.sh - DEPENDS program + DEPENDS program standalone_apps ) add_custom_command( @@ -100,8 +101,9 @@ add_custom_command( COMMAND cp ${FIRMWARE_FILENAME} firmware_tar/FIRMWARE/portapack-mayhem_${VERSION_NOHASH}.bin COMMAND mkdir -p firmware_tar/APPS COMMAND cp application/*.ppma firmware_tar/APPS + COMMAND cp standalone/*/*.ppmp firmware_tar/APPS COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} * - DEPENDS firmware ${FIRMWARE_FILENAME} + DEPENDS firmware ${FIRMWARE_FILENAME} standalone_apps # Dont use VERBATIM here as it prevents usage of globbing (*) # There shouldnt be any funny business in the filenames above :) ) diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 2aef23ea..117a9bbf 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -325,6 +325,7 @@ set(CPPSRC # apps/ui_spectrum_painter.cpp apps/ui_ss_viewer.cpp apps/ui_sstvtx.cpp + apps/ui_standalone_view.cpp apps/ui_subghzd.cpp # apps/ui_test.cpp apps/ui_text_editor.cpp diff --git a/firmware/application/apps/ui_standalone_view.cpp b/firmware/application/apps/ui_standalone_view.cpp new file mode 100644 index 00000000..cbab847c --- /dev/null +++ b/firmware/application/apps/ui_standalone_view.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_standalone_view.hpp" +#include "irq_controls.hpp" + +namespace ui { + +void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) { + // TODO: collect memory on terminate, once this is used + chThdCreateFromHeap(NULL, stack_size, priority, fn, arg); +} + +void fill_rectangle(int x, int y, int width, int height, uint16_t color) { + ui::Painter painter; + painter.fill_rectangle({x, y, width, height}, ui::Color(color)); +} + +void* alloc(size_t size) { + void* p = chHeapAlloc(0x0, size); + if (p == nullptr) + chDbgPanic("Out of Memory"); + return p; +} + +uint64_t get_switches_state_ulong() { + return get_switches_state().to_ulong(); +} + +standalone_application_api_t api = { + /* .malloc = */ &alloc, + /* .calloc = */ &calloc, + /* .realloc = */ &realloc, + /* .free = */ &chHeapFree, + /* .create_thread = */ &create_thread, + /* .fill_rectangle = */ &fill_rectangle, + /* .swizzled_switches = */ &swizzled_switches, + /* .get_switches_state = */ &get_switches_state_ulong, +}; + +StandaloneView::StandaloneView(NavigationView& nav, std::unique_ptr app_image) + : nav_(nav), _app_image(std::move(app_image)) { + get_application_information()->initialize(api); + add_children({&dummy}); +} + +void StandaloneView::focus() { + dummy.focus(); +} + +void StandaloneView::paint(Painter& painter) { + (void)painter; +} + +void StandaloneView::frame_sync() { + // skip first refresh + if (!initialized) { + initialized = true; + } else { + get_application_information()->on_event(1); + } +} + +} // namespace ui diff --git a/firmware/application/external/pacman/ui_pacman.hpp b/firmware/application/apps/ui_standalone_view.hpp similarity index 63% rename from firmware/application/external/pacman/ui_pacman.hpp rename to firmware/application/apps/ui_standalone_view.hpp index 8d56f075..012d7f07 100644 --- a/firmware/application/external/pacman/ui_pacman.hpp +++ b/firmware/application/apps/ui_standalone_view.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Bernd Herzog + * Copyright (C) 2024 Bernd Herzog * * This file is part of PortaPack. * @@ -19,22 +19,24 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __UI_PACMAN_H__ -#define __UI_PACMAN_H__ +#ifndef __UI_STANDALONE_VIEW_H__ +#define __UI_STANDALONE_VIEW_H__ #include "ui_navigation.hpp" #include "event_m0.hpp" #include "message.hpp" +#include "standalone_app.hpp" -namespace ui::external_app::pacman { +namespace ui { -class PacmanView : public View { +class StandaloneView : public View { public: - PacmanView(NavigationView& nav); + StandaloneView(NavigationView& nav, std::unique_ptr app_image); + virtual ~StandaloneView() override { get_application_information()->shutdown(); } void focus() override; - std::string title() const override { return "Pac-Man"; }; + std::string title() const override { return (const char*)get_application_information()->app_name; }; void paint(Painter& painter) override; void frame_sync(); @@ -42,6 +44,10 @@ class PacmanView : public View { private: bool initialized = false; NavigationView& nav_; + std::unique_ptr _app_image; + standalone_application_information_t* get_application_information() const { + return reinterpret_cast(_app_image.get()); + } Button dummy{ {240, 0, 0, 0}, @@ -54,6 +60,6 @@ class PacmanView : public View { }}; }; -} // namespace ui::external_app::pacman +} // namespace ui -#endif /*__UI_PACMAN_H__*/ +#endif /*__UI_STANDALONE_VIEW_H__*/ diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 67a14091..baba494f 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -1,9 +1,5 @@ set(EXTCPPSRC - #pacman - external/pacman/main.cpp - external/pacman/ui_pacman.cpp - #tetris external/tetris/main.cpp external/tetris/ui_tetris.cpp @@ -87,7 +83,6 @@ set(EXTCPPSRC ) set(EXTAPPLIST - pacman afsk_rx calculator font_viewer diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index d64c54a6..aab54bda 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -23,7 +23,6 @@ MEMORY * Also need to consider processor memory map - reading 0xADxxxxxx generates a fault which may be better than unexpected behavior. * External app address ranges below must match those in python file "external_app_info.py". */ - ram_external_app_pacman (rwx) : org = 0xADB00000, len = 32k ram_external_app_afsk_rx (rwx) : org = 0xADB10000, len = 32k ram_external_app_calculator (rwx) : org = 0xADB20000, len = 32k ram_external_app_font_viewer(rwx) : org = 0xADB30000, len = 32k @@ -47,12 +46,6 @@ MEMORY SECTIONS { - .external_app_pacman : ALIGN(4) SUBALIGN(4) - { - KEEP(*(.external_app.app_pacman.application_information)); - *(*ui*external_app*pacman*); - } > ram_external_app_pacman - .external_app_afsk_rx : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_afsk_rx.application_information)); diff --git a/firmware/application/external/pacman/ui_pacman.cpp b/firmware/application/external/pacman/ui_pacman.cpp deleted file mode 100644 index 8436ac21..00000000 --- a/firmware/application/external/pacman/ui_pacman.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "ui_pacman.hpp" -#include "irq_controls.hpp" - -namespace ui::external_app::pacman { - -#pragma GCC diagnostic push -// external code, so ignore warnings -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-but-set-variable" -#pragma GCC diagnostic ignored "-Wreturn-type" -#pragma GCC diagnostic ignored "-Weffc++" -#include "playfield.hpp" -#pragma GCC diagnostic pop - -PacmanView::PacmanView(NavigationView& nav) - : nav_(nav) { - add_children({&dummy}); -} - -void PacmanView::focus() { - dummy.focus(); -} - -void PacmanView::paint(Painter& painter) { - (void)painter; - static Playfield _game; - static bool wait_for_button_release{false}; - - if (!initialized) { - initialized = true; - _game.Init(); - } - - auto switches_raw = swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu)); - auto switches_debounced = get_switches_state().to_ulong(); - - // For the Select (Start/Pause) button, wait for release to avoid repeat - uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel); - if (wait_for_button_release) { - if ((switches_debounced & buttons_to_wait_for) == 0) - wait_for_button_release = false; - switches_debounced &= ~buttons_to_wait_for; - } else { - if (switches_debounced & buttons_to_wait_for) - wait_for_button_release = true; - } - - // For the directional buttons, use the raw inputs for fastest response time - but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0; - but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0; - but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0; - but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0; - - // For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen - but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0; - - _game.Step(); -} - -void PacmanView::frame_sync() { - set_dirty(); -} - -} // namespace ui::external_app::pacman diff --git a/firmware/application/ui_external_items_menu_loader.cpp b/firmware/application/ui_external_items_menu_loader.cpp index 48cb7ab7..f3c2cf10 100644 --- a/firmware/application/ui_external_items_menu_loader.cpp +++ b/firmware/application/ui_external_items_menu_loader.cpp @@ -2,6 +2,7 @@ #include "sd_card.hpp" #include "file_path.hpp" +#include "ui_standalone_view.hpp" namespace ui { @@ -10,8 +11,10 @@ namespace ui { // iterates over all ppma-s, and if it is runnable on the current system, it'll call the callback, and pass info. /* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function callback) { if (!callback) return; + if (sd_card::status() != sd_card::Status::Mounted) return; + for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppma")) { auto filePath = apps_dir / entry.path(); File app; @@ -43,6 +46,37 @@ namespace ui { .appLocation = application_information.menu_location}; callback(info); } + + for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppmp")) { + auto filePath = apps_dir / entry.path(); + File app; + + auto openError = app.open(filePath); + if (openError) + continue; + + standalone_application_information_t application_information = {}; + + auto readResult = app.read(&application_information, sizeof(standalone_application_information_t)); + if (!readResult) + continue; + + if (application_information.header_version < CURRENT_STANDALONE_APPLICATION_API_VERSION) + continue; + + // here the app is startable and good. + std::string appshortname = filePath.filename().string(); + if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppmp") { + // Remove the ".ppmp" suffix + appshortname = appshortname.substr(0, appshortname.size() - 5); + } + AppInfoConsole info{ + .appCallName = appshortname.c_str(), + .appFriendlyName = reinterpret_cast(&application_information.app_name[0]), + .appLocation = application_information.menu_location}; + + callback(info); + } } /* static */ std::vector ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) { @@ -103,6 +137,44 @@ namespace ui { external_apps.push_back(gridItem); } + for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppmp")) { + auto filePath = apps_dir / entry.path(); + File app; + + auto openError = app.open(filePath); + if (openError) + continue; + + standalone_application_information_t application_information = {}; + + auto readResult = app.read(&application_information, sizeof(standalone_application_information_t)); + if (!readResult) + continue; + + if (application_information.menu_location != app_location) + continue; + + if (application_information.header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION) + continue; + + GridItem gridItem = {}; + gridItem.text = reinterpret_cast(&application_information.app_name[0]); + + gridItem.color = Color((uint16_t)application_information.icon_color); + + auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data}; + gridItem.bitmap = dyn_bmp.bitmap(); + bitmaps.push_back(std::move(dyn_bmp)); + + gridItem.on_select = [&nav, app_location, filePath]() { + if (!run_standalone_app(nav, filePath)) { + nav.display_modal("Error", "The .ppmp file in your " + apps_dir.string() + "\nfolder can't be read. Please\nupdate your SD Card content."); + } + }; + + external_apps.push_back(gridItem); + } + return external_apps; } @@ -187,4 +259,39 @@ namespace ui { return true; } +/* static */ bool ExternalItemsMenuLoader::run_standalone_app(ui::NavigationView& nav, std::filesystem::path filePath) { + File app; + + auto openError = app.open(filePath); + if (openError) + return false; + + auto app_image = std::make_unique(app.size()); + + // read file in 512 byte chunks + for (size_t file_read_index = 0; file_read_index < app.size(); file_read_index += std::filesystem::max_file_block_size) { + auto bytes_to_read = std::filesystem::max_file_block_size; + if (file_read_index + std::filesystem::max_file_block_size > app.size()) + bytes_to_read = app.size() - file_read_index; + + auto readResult = app.read(&app_image[file_read_index], bytes_to_read); + if (!readResult) + return false; + + if (readResult.value() < std::filesystem::max_file_block_size) + break; + } + + for (size_t file_read_index = 0; file_read_index < app.size() / 4; file_read_index++) { + uint32_t* ptr = reinterpret_cast(&app_image[file_read_index * 4]); + + if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) { + *ptr = *ptr - 0xADB10000 + (uint32_t)app_image.get(); + } + } + + nav.push(std::move(app_image)); + return true; +} + } // namespace ui diff --git a/firmware/application/ui_external_items_menu_loader.hpp b/firmware/application/ui_external_items_menu_loader.hpp index 0c3b04c8..74745d17 100644 --- a/firmware/application/ui_external_items_menu_loader.hpp +++ b/firmware/application/ui_external_items_menu_loader.hpp @@ -26,6 +26,7 @@ #include "ui.hpp" #include "ui_navigation.hpp" #include "external_app.hpp" +#include "standalone_app.hpp" #include "file.hpp" @@ -57,6 +58,7 @@ class ExternalItemsMenuLoader { static std::vector load_external_items(app_location_t, NavigationView&); ExternalItemsMenuLoader() = delete; static bool run_external_app(ui::NavigationView&, std::filesystem::path); + static bool run_standalone_app(ui::NavigationView&, std::filesystem::path); static void load_all_external_items_callback(std::function callback); private: diff --git a/firmware/common/external_app.hpp b/firmware/common/external_app.hpp index d1d8e771..c257671b 100644 --- a/firmware/common/external_app.hpp +++ b/firmware/common/external_app.hpp @@ -25,20 +25,13 @@ #include "ch.h" #include "ui_navigation.hpp" #include "spi_image.hpp" +#include "standalone_app.hpp" #define CURRENT_HEADER_VERSION 0x00000002 #define MIN_HEADER_VERSION_FOR_CHECKSUM 0x00000002 typedef void (*externalAppEntry_t)(ui::NavigationView& nav); -enum app_location_t : uint32_t { - UTILITIES = 0, - RX, - TX, - DEBUG, - HOME -}; - struct application_information_t { uint8_t* memory_location; externalAppEntry_t externalAppEntry; diff --git a/firmware/common/standalone_app.hpp b/firmware/common/standalone_app.hpp new file mode 100644 index 00000000..5a34f8b7 --- /dev/null +++ b/firmware/common/standalone_app.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __UI_STANDALONE_APP_H__ +#define __UI_STANDALONE_APP_H__ + +#include +#include +#include + +#define CURRENT_STANDALONE_APPLICATION_API_VERSION 1 + +struct standalone_application_api_t { + void* (*malloc)(size_t size); + void* (*calloc)(size_t num, size_t size); + void* (*realloc)(void* p, size_t size); + void (*free)(void* p); + void (*create_thread)(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority); + void (*fill_rectangle)(int x, int y, int width, int height, uint16_t color); + uint8_t (*swizzled_switches)(); + uint64_t (*get_switches_state)(); + + // HOW TO extend this interface: + // to keep everything backward compatible: add new fields at the end + // and increment CURRENT_STANDALONE_APPLICATION_API_VERSION +}; + +enum app_location_t : uint32_t { + UTILITIES = 0, + RX, + TX, + DEBUG, + HOME +}; + +struct standalone_application_information_t { + uint32_t header_version; + + uint8_t app_name[16]; + uint8_t bitmap_data[32]; + uint32_t icon_color; + app_location_t menu_location; + + /// @brief gets called once at application start + void (*initialize)(const standalone_application_api_t& api); + + /// @brief gets called when an event occurs + /// @param events bitfield of events + /// @note events are defined in firmware/application/event_m0.hpp + void (*on_event)(const uint32_t& events); + + /// @brief gets called once at application shutdown + void (*shutdown)(); +}; + +#endif /*__UI_STANDALONE_APP_H__*/ diff --git a/firmware/standalone/CMakeLists.txt b/firmware/standalone/CMakeLists.txt new file mode 100644 index 00000000..fa3c53df --- /dev/null +++ b/firmware/standalone/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +project(standalone_apps) + +add_subdirectory(pacman) + +add_custom_target( + standalone_apps + DEPENDS pacman_app +) + diff --git a/firmware/standalone/pacman/CMakeLists.txt b/firmware/standalone/pacman/CMakeLists.txt new file mode 100644 index 00000000..92ffabf1 --- /dev/null +++ b/firmware/standalone/pacman/CMakeLists.txt @@ -0,0 +1,233 @@ +# +# Copyright (C) 2024 Bernd Herzog +# +# This file is part of PortaPack. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +############################################################################## +# Build global options +# NOTE: Can be overridden externally. +# + +enable_language(C CXX ASM) + +include(CheckCXXCompilerFlag) + +project(pacman_app) + +# Compiler options here. +set(USE_OPT "-Os -g --specs=nano.specs") + +# C specific options here (added to USE_OPT). +set(USE_COPT "-std=gnu99") + +# C++ specific options here (added to USE_OPT). +check_cxx_compiler_flag("-std=c++20" cpp20_supported) +if(cpp20_supported) + set(USE_CPPOPT "-std=c++20") +else() + set(USE_CPPOPT "-std=c++17") +endif() +set(USE_CPPOPT "${USE_CPPOPT} -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -fno-use-cxa-atexit") + +# Enable this if you want the linker to remove unused code and data +set(USE_LINK_GC yes) + +# Linker extra options here. +set(USE_LDOPT) + +# Enable this if you want link time optimizations (LTO) - this flag affects chibios only +set(USE_LTO no) + +# If enabled, this option allows to compile the application in THUMB mode. +set(USE_THUMB yes) + +# Enable this if you want to see the full log while compiling. +set(USE_VERBOSE_COMPILE no) + +# +# Build global options +############################################################################## + +############################################################################## +# Architecture or project specific options +# + +# Enables the use of FPU on Cortex-M4 (no, softfp, hard). +set(USE_FPU no) + +# +# Architecture or project specific options +############################################################################## + +############################################################################## +# Project, sources and paths +# + +# Define linker script file here +set(LDSCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/external.ld) + + +# C sources that can be compiled in ARM or THUMB mode depending on the global +# setting. +FILE(GLOB_RECURSE Sources_C ${CMAKE_CURRENT_LIST_DIR}/*.c) +set(CSRC + ${Sources_C} +) + +# C++ sources that can be compiled in ARM or THUMB mode depending on the global +# setting. +FILE(GLOB_RECURSE Sources_CPP ${CMAKE_CURRENT_LIST_DIR}/*.cpp) +set(CPPSRC + ${Sources_CPP} +) + +# C sources to be compiled in ARM mode regardless of the global setting. +# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler +# option that results in lower performance and larger code size. +set(ACSRC) + +# C++ sources to be compiled in ARM mode regardless of the global setting. +# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler +# option that results in lower performance and larger code size. +set(ACPPSRC) + +# C sources to be compiled in THUMB mode regardless of the global setting. +# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler +# option that results in lower performance and larger code size. +set(TCSRC) + +# C sources to be compiled in THUMB mode regardless of the global setting. +# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler +# option that results in lower performance and larger code size. +set(TCPPSRC) + +# List ASM source files here +set(ASMSRC) + +set(INCDIR + ${CMAKE_CURRENT_SOURCE_DIR} + ${COMMON} + ${COMMON}/../application + ${COMMON}/../application/hw +) + +# +# Project, sources and paths +############################################################################## + +############################################################################## +# Compiler settings +# + +# TODO: Entertain using MCU=cortex-m0.small-multiply for LPC43xx M0 core. +# However, on GCC-ARM-Embedded 4.9 2015q2, it seems to produce non-functional +# binaries. +set(MCU cortex-m0) + +# ARM-specific options here +set(AOPT) + +# THUMB-specific options here +set(TOPT "-mthumb -DTHUMB") + +# Define C warning options here +set(CWARN "-Wall -Wextra -Wstrict-prototypes") + +# Define C++ warning options here +set(CPPWARN "-Wall -Wextra -Wno-psabi") + +# +# Compiler settings +############################################################################## + +############################################################################## +# Start of default section +# + +# List all default C defines here, like -D_DEBUG=1 +# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI? +# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types +set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0") + +# List all default ASM defines here, like -D_DEBUG=1 +set(DADEFS) + +# List all default directories to look for include files here +set(DINCDIR) + +# List the default directory to look for the libraries here +set(DLIBDIR) + +# List all default libraries here +set(DLIBS) + +# +# End of default section +############################################################################## + +############################################################################## +# Start of user section +# + +# List all user C define here, like -D_DEBUG=1 +set(UDEFS) + +# Define ASM defines here +set(UADEFS) + +# List all user directories here +set(UINCDIR) + +# List the user directory to look for the libraries here +set(ULIBDIR) + +# List all user libraries here +set(ULIBS) + +# +# End of user defines +############################################################################## + +set(RULESPATH ${CHIBIOS}/os/ports/GCC/ARMCMx) +include(${RULESPATH}/rules.cmake) + +############################################################################## + + +add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC}) +set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT}) +add_definitions(${DEFS}) +include_directories(. ${INCDIR}) +link_directories(${LLIBDIR}) +target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map) + +# redirect std lib memory allocations +target_link_libraries(${PROJECT_NAME}.elf "-Wl,-wrap,_malloc_r") +target_link_libraries(${PROJECT_NAME}.elf "-Wl,-wrap,_free_r") + +add_custom_command( + OUTPUT ${PROJECT_NAME}.ppmp + COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.ppmp + DEPENDS ${PROJECT_NAME}.elf +) + +add_custom_target( + ${PROJECT_NAME} + DEPENDS ${PROJECT_NAME}.ppmp +) diff --git a/firmware/application/external/pacman/DrawIndexedMap.h b/firmware/standalone/pacman/DrawIndexedMap.h similarity index 86% rename from firmware/application/external/pacman/DrawIndexedMap.h rename to firmware/standalone/pacman/DrawIndexedMap.h index 09d35713..04ce83bc 100644 --- a/firmware/application/external/pacman/DrawIndexedMap.h +++ b/firmware/standalone/pacman/DrawIndexedMap.h @@ -17,6 +17,7 @@ /* MIT license, all text above must be included in any redistribution. */ // #include "ili9328.h" +#include "standalone_app.hpp" typedef uint16_t ushort; @@ -46,16 +47,15 @@ uint16_t _paletteW[] = }; void drawIndexedmap(uint8_t* indexmap, int16_t x, uint16_t y) { - ui::Painter painter; - byte i = 0; - word color = (word)_paletteW[indexmap[0]]; + uint16_t color = (uint16_t)_paletteW[indexmap[0]]; for (byte tmpY = 0; tmpY < 8; tmpY++) { byte width = 1; for (byte tmpX = 0; tmpX < 8; tmpX++) { - word next_color = (word)_paletteW[indexmap[++i]]; + uint16_t next_color = (uint16_t)_paletteW[indexmap[++i]]; if ((color != next_color && width >= 1) || tmpX == 7) { - painter.draw_hline({x + tmpX - width + 1, y + tmpY}, width, ui::Color(color)); + _api->fill_rectangle(x + tmpX - width + 1, y + tmpY, width, 1, color); + // painter.draw_hline({x + tmpX - width + 1, y + tmpY}, width, ui::Color(color)); color = next_color; width = 0; diff --git a/firmware/application/external/pacman/PacmanTiles.h b/firmware/standalone/pacman/PacmanTiles.h similarity index 100% rename from firmware/application/external/pacman/PacmanTiles.h rename to firmware/standalone/pacman/PacmanTiles.h diff --git a/firmware/application/external/pacman/crntsc.h b/firmware/standalone/pacman/crntsc.h similarity index 100% rename from firmware/application/external/pacman/crntsc.h rename to firmware/standalone/pacman/crntsc.h diff --git a/firmware/application/external/pacman/crpal.h b/firmware/standalone/pacman/crpal.h similarity index 100% rename from firmware/application/external/pacman/crpal.h rename to firmware/standalone/pacman/crpal.h diff --git a/firmware/standalone/pacman/external.ld b/firmware/standalone/pacman/external.ld new file mode 100644 index 00000000..4048f938 --- /dev/null +++ b/firmware/standalone/pacman/external.ld @@ -0,0 +1,118 @@ +/* + Copyright (C) 2024 Bernd Herzog + + Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + + 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. +*/ + +MEMORY +{ + ram : org = 0xADB10000, len = 64k /* DO NOT CHANGE the address. We make the image relocateable on load. It needs to be 0xADB10000 */ +} + +__ram_start__ = ORIGIN(ram); +__ram_size__ = LENGTH(ram); +__ram_end__ = __ram_start__ + __ram_size__; + +SECTIONS +{ + . = 0; + _text = .; + startup : ALIGN(16) SUBALIGN(16) + { + KEEP(*(.standalone_application_information)); + } > ram + + constructors : ALIGN(4) SUBALIGN(4) + { + PROVIDE(__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE(__init_array_end = .); + } > ram + + destructors : ALIGN(4) SUBALIGN(4) + { + PROVIDE(__fini_array_start = .); + KEEP(*(.fini_array)) + KEEP(*(SORT(.fini_array.*))) + PROVIDE(__fini_array_end = .); + } > ram + + .text : ALIGN(16) SUBALIGN(16) + { + *(.text.startup.*) + *(.text) + *(.text.*) + *(.rodata) + *(.rodata.*) + *(.glue_7t) + *(.glue_7) + *(.gcc*) + } > ram + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > ram + + .ARM.exidx : { + PROVIDE(__exidx_start = .); + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + PROVIDE(__exidx_end = .); + } > ram + + .eh_frame_hdr : + { + *(.eh_frame_hdr) + } > ram + + .eh_frame : ONLY_IF_RO + { + *(.eh_frame) + } > ram + + .textalign : ONLY_IF_RO + { + . = ALIGN(8); + } > ram + + .bss ALIGN(4) : ALIGN(4) + { + . = ALIGN(4); + PROVIDE(_bss_start = .); + *(.bss) + *(.bss.*) + *(COMMON) + . = ALIGN(4); + PROVIDE(_bss_end = .); + } > ram + + . = ALIGN(4); + _etext = .; + _textdata = _etext; + + .data ALIGN(4) : AT (_textdata) + { + . = ALIGN(4); + PROVIDE(_data = .); + *(.data) + *(.data.*) + *(.ramtext) + . = ALIGN(4); + PROVIDE(_edata = .); + } > ram +} + +PROVIDE(end = .); +_end = .; + diff --git a/firmware/application/external/pacman/main.cpp b/firmware/standalone/pacman/main.cpp similarity index 53% rename from firmware/application/external/pacman/main.cpp rename to firmware/standalone/pacman/main.cpp index 65717260..4ef787f7 100644 --- a/firmware/application/external/pacman/main.cpp +++ b/firmware/standalone/pacman/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Bernd Herzog + * Copyright (C) 2024 Bernd Herzog * * This file is part of PortaPack. * @@ -19,24 +19,15 @@ * Boston, MA 02110-1301, USA. */ -#include "ui.hpp" -#include "ui_pacman.hpp" -#include "ui_navigation.hpp" -#include "external_app.hpp" +#include "standalone_app.hpp" +#include "pacman.hpp" +#include -namespace ui::external_app::pacman { -void initialize_app(ui::NavigationView& nav) { - nav.push(); -} -} // namespace ui::external_app::pacman +const standalone_application_api_t* _api; extern "C" { - -__attribute__((section(".external_app.app_pacman.application_information"), used)) application_information_t _application_information_pacman = { - /*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time - /*.externalAppEntry = */ ui::external_app::pacman::initialize_app, - /*.header_version = */ CURRENT_HEADER_VERSION, - /*.app_version = */ VERSION_MD5, +__attribute__((section(".standalone_application_information"), used)) standalone_application_information_t _standalone_application_information = { + /*.header_version = */ CURRENT_STANDALONE_APPLICATION_API_VERSION, /*.app_name = */ "Pac-Man", /*.bitmap_data = */ { @@ -73,10 +64,38 @@ __attribute__((section(".external_app.app_pacman.application_information"), used 0x00, 0x00, }, - /*.icon_color = */ ui::Color::yellow().v, + /*.icon_color = 16 bit: 5R 6G 5B*/ 0x0000FFE0, /*.menu_location = */ app_location_t::UTILITIES, - /*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0}, - /*.m4_app_offset = */ 0x00000000, // will be filled at compile time + /*.initialize_app = */ initialize, + /*.on_event = */ on_event, + /*.shutdown = */ shutdown, }; } + +/* Implementing abort() eliminates requirement for _getpid(), _kill(), _exit(). */ +extern "C" void abort() { + while (true); +} + +// replace memory allocations to use heap from chibios +extern "C" void* malloc(size_t size) { + return _api->malloc(size); +} +extern "C" void* calloc(size_t num, size_t size) { + return _api->calloc(num, size); +} +extern "C" void* realloc(void* p, size_t size) { + return _api->realloc(p, size); +} +extern "C" void free(void* p) { + _api->free(p); +} + +// redirect std lib memory allocations (sprintf, etc.) +extern "C" void* __wrap__malloc_r(size_t size) { + return _api->malloc(size); +} +extern "C" void __wrap__free_r(void* p) { + _api->free(p); +} diff --git a/firmware/standalone/pacman/pacman.cpp b/firmware/standalone/pacman/pacman.cpp new file mode 100644 index 00000000..b0c23c27 --- /dev/null +++ b/firmware/standalone/pacman/pacman.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include "pacman.hpp" +#include "irq_controls.hpp" + +#pragma GCC diagnostic push +// external code, so ignore warnings +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#pragma GCC diagnostic ignored "-Wreturn-type" +#pragma GCC diagnostic ignored "-Weffc++" +#include "playfield.hpp" +#pragma GCC diagnostic pop + +std::unique_ptr _playfield; + +void initialize(const standalone_application_api_t& api) { + _api = &api; +} + +void on_event(const uint32_t& events) { + static bool wait_for_button_release{false}; + + if (!_playfield) { + _playfield = std::make_unique(); + _playfield->Init(); + } + + if (events & 1) { + auto switches_raw = _api->swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu)); + auto switches_debounced = _api->get_switches_state(); + + // For the Select (Start/Pause) button, wait for release to avoid repeat + uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel); + if (wait_for_button_release) { + if ((switches_debounced & buttons_to_wait_for) == 0) + wait_for_button_release = false; + switches_debounced &= ~buttons_to_wait_for; + } else { + if (switches_debounced & buttons_to_wait_for) + wait_for_button_release = true; + } + + // For the directional buttons, use the raw inputs for fastest response time + but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0; + but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0; + but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0; + but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0; + + // For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen + but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0; + + _playfield->Step(); + } +} + +void shutdown() { + _playfield.reset(); +} diff --git a/firmware/standalone/pacman/pacman.hpp b/firmware/standalone/pacman/pacman.hpp new file mode 100644 index 00000000..95ce09fa --- /dev/null +++ b/firmware/standalone/pacman/pacman.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PACMAN_H__ +#define __PACMAN_H__ + +#include "standalone_app.hpp" + +void initialize(const standalone_application_api_t& api); +void on_event(const uint32_t& events); +void shutdown(); + +extern const standalone_application_api_t* _api; + +#endif /*__PACMAN_H__*/ diff --git a/firmware/application/external/pacman/playfield.hpp b/firmware/standalone/pacman/playfield.hpp similarity index 99% rename from firmware/application/external/pacman/playfield.hpp rename to firmware/standalone/pacman/playfield.hpp index d0b4f171..a643ec5b 100644 --- a/firmware/application/external/pacman/playfield.hpp +++ b/firmware/standalone/pacman/playfield.hpp @@ -1,4 +1,6 @@ +#include + /******************************************************************************/ /* MAIN GAME VARIABLES */ /******************************************************************************/ diff --git a/firmware/tools/copy_external_apps.sh b/firmware/tools/copy_external_apps.sh index bd807c66..e8578021 100755 --- a/firmware/tools/copy_external_apps.sh +++ b/firmware/tools/copy_external_apps.sh @@ -44,6 +44,7 @@ do echo "Copying external applications to" $mountpoint mkdir -p $mountpoint/APPS cp application/*.ppma $mountpoint/APPS + cp standalone/*/*.ppmp $mountpoint/APPS echo "Unmounting" $mountpoint umount $mountpoint