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