External apps (#1469)

* implemented external app build

* added some ui stuff for testing

* added pacman game

* wired key to pacman game

* fixed pacman drawing issue

* changed afsk rx app to be external

* fixed ui::NavigationView initialization for external apps

* refactoring

* refactoring

* moved m4 image to external app

* added script for external app deployment

* refactoring

* implemented dynamic app listing

* added color to app icon

* improved app loading

* added external apps to sd card content

* refactoring

* review findings

* typo

* review findings

* improved memory management of bitmaps
This commit is contained in:
Bernd Herzog 2023-10-02 20:19:22 +02:00 committed by GitHub
parent 78713cc2af
commit 7fdb1af69d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 9925 additions and 47 deletions

View File

@ -49,7 +49,7 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@ -61,7 +61,7 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@ -33,7 +33,7 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@ -45,7 +45,7 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}

View File

@ -42,6 +42,12 @@ if ("$ENV{VERSION_STRING}" STREQUAL "")
endif (GIT_VERSION_FOUND)
endif()
execute_process(
COMMAND bash "-c" "echo ${VERSION} | md5sum | head -c 8"
OUTPUT_VARIABLE VERSION_MD5
)
set(VERSION_MD5 "0x${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)

View File

@ -26,6 +26,7 @@ set(CHIBIOS ${PROJECT_SOURCE_DIR}/chibios)
set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack)
set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py)
set(EXPORT_EXTERNAL_APP_IMAGES ${PROJECT_SOURCE_DIR}/tools/export_external_apps.py)
set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
set(LZ4 lz4)
@ -55,8 +56,15 @@ add_custom_target(
program
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE} || (exit 0) # We need to add it for dfu-utils v.011 , (in v.09 it is not necessary)
COMMAND sleep 3s
COMMAND hackrf_spiflash -w ${FIRMWARE_FILENAME}
DEPENDS ${FIRMWARE_FILENAME}
COMMAND hackrf_spiflash -i -R -w ${FIRMWARE_FILENAME}
DEPENDS firmware ${FIRMWARE_FILENAME}
)
add_custom_target(
program-external-apps
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${PROJECT_SOURCE_DIR}/tools/copy_external_apps.sh
DEPENDS program
)
# TODO: Bad hack to fix location of LICENSE file for tar.

View File

@ -32,7 +32,7 @@ include(CheckCXXCompilerFlag)
project(application)
# Compiler options here.
set(USE_OPT "-Os -g --specs=nano.specs")
set(USE_OPT "-Os -g --specs=nano.specs -specs=nosys.specs")
# C specific options here (added to USE_OPT).
set(USE_COPT "-std=gnu99")
@ -97,6 +97,7 @@ include(${CHIBIOS_PORTAPACK}/os/ports/GCC/ARMCMx/LPC43xx_M0/port.cmake)
include(${CHIBIOS}/os/kernel/kernel.cmake)
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
include(${CHIBIOS}/test/test.cmake)
include(external/external.cmake)
# Define linker script file here
set(LDSCRIPT ${PORTLD}/LPC43xx_M0.ld)
@ -256,7 +257,6 @@ set(CPPSRC
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
apps/ui_adsb_tx.cpp
apps/ui_afsk_rx.cpp
apps/ui_aprs_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_bht_tx.cpp
@ -322,6 +322,9 @@ set(CPPSRC
${CPLD_20150901_DATA_CPP}
${CPLD_20170522_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
${EXTCPPSRC}
)
# C sources to be compiled in ARM mode regardless of the global setting.
@ -397,7 +400,7 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
# 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 -D'VERSION_STRING=\"${VERSION}\"'")
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5}")
# List all default ASM defines here, like -D_DEBUG=1
set(DADEFS)
@ -466,12 +469,13 @@ 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 ${LIBS})
target_link_libraries(${PROJECT_NAME}.elf ${LIBS} "-L${CMAKE_CURRENT_LIST_DIR}/external")
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
add_custom_command(
OUTPUT ${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin --remove-section=.external_app_*
COMMAND ${EXPORT_EXTERNAL_APP_IMAGES} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_OBJCOPY} ${EXTAPPLIST}
DEPENDS ${PROJECT_NAME}.elf
)

View File

@ -329,6 +329,30 @@ void run_image(const spi_flash::image_tag_t image_tag) {
}
}
void run_prepared_image(const uint32_t m4_code) {
if (baseband_image_running) {
chDbgPanic("BBRunning");
}
creg::m4txevent::clear();
shared_memory.clear_baseband_ready();
m4_init_prepared(m4_code, false);
baseband_image_running = true;
creg::m4txevent::enable();
if constexpr (enforce_core_sync) {
// Wait up to 3 seconds for baseband to start handling events.
auto count = 3'000u;
while (!shared_memory.baseband_ready && --count)
chThdSleepMilliseconds(1);
if (count == 0)
chDbgPanic("Baseband Sync Fail");
}
}
void shutdown() {
if (!baseband_image_running) {
return;

View File

@ -89,6 +89,7 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
void request_beep();
void run_image(const portapack::spi_flash::image_tag_t image_tag);
void run_prepared_image(const uint32_t m4_code);
void shutdown();
void spectrum_streaming_start();

View File

@ -59,6 +59,19 @@ void m4_init(const spi_flash::image_tag_t image_tag, const memory::region_t to,
chDbgPanic("NoImg");
}
void m4_init_prepared(const uint32_t m4_code, const bool full_reset) {
/* M4 core is assumed to be sleeping with interrupts off, so we can mess
* with its address space and RAM without concern.
*/
LPC_CREG->M4MEMMAP = m4_code;
/* Reset M4 core and optionally all peripherals */
LPC_RGU->RESET_CTRL[0] = (full_reset) ? (1 << 1) // PERIPH_RST
: (1 << 13); // M4_RST
return;
}
void m4_request_shutdown() {
baseband::shutdown();
}

View File

@ -28,6 +28,7 @@
#include "spi_image.hpp"
void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to, const bool full_reset);
void m4_init_prepared(const uint32_t m4_code, const bool full_reset);
void m4_request_shutdown();
void m0_halt();

View File

@ -0,0 +1,82 @@
/*
* 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.
*/
#include "ui.hpp"
#include "ui_afsk_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::afsk_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<AFSKRxView>();
}
} // namespace ui::external_app::afsk_rx
extern "C" {
__attribute__((section(".external_app.app_afsk_rx.application_information"), used)) application_information_t _application_information_afsk_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::afsk_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "AFSK",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xF8,
0x1F,
0x04,
0x20,
0x02,
0x40,
0xFF,
0xFF,
0xFF,
0xFF,
0xAB,
0xDF,
0xAB,
0xDF,
0xFF,
0xFF,
0xFF,
0xFF,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}

View File

@ -32,20 +32,21 @@
using namespace portapack;
using namespace modems;
using namespace ui;
namespace ui::external_app::afsk_rx {
void AFSKLogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
namespace ui {
void AFSKRxView::focus() {
field_frequency.focus();
}
AFSKRxView::AFSKRxView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_afsk_rx);
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&rssi,
&channel,
@ -147,4 +148,4 @@ AFSKRxView::~AFSKRxView() {
baseband::shutdown();
}
} /* namespace ui */
} // namespace ui::external_app::afsk_rx

View File

@ -33,6 +33,10 @@
#include "log_file.hpp"
#include "utility.hpp"
using namespace ui;
namespace ui::external_app::afsk_rx {
class AFSKLogger {
public:
Optional<File::Error> append(const std::string& filename) {
@ -45,8 +49,6 @@ class AFSKLogger {
LogFile log_file{};
};
namespace ui {
class AFSKRxView : public View {
public:
AFSKRxView(NavigationView& nav);
@ -116,6 +118,6 @@ class AFSKRxView : public View {
}};
};
} /* namespace ui */
} // namespace ui::external_app::afsk_rx
#endif /*__UI_AFSK_RX_H__*/

View File

@ -0,0 +1,15 @@
set(EXTCPPSRC
#pacman
external/pacman/main.cpp
external/pacman/ui_pacman.cpp
#afsk_rx
external/afsk_rx/main.cpp
external/afsk_rx/ui_afsk_rx.cpp
)
set(EXTAPPLIST
pacman
afsk_rx
)

View File

@ -0,0 +1,37 @@
/*
Copyright (C) 2023 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
{
/* external apps: regions can't overlap so addresses are corrected after build */
ram_external_app_pacman (rwx) : org = 0xEEE90000, len = 40k
ram_external_app_afsk_rx (rwx) : org = 0xEEEA0000, len = 40k
}
SECTIONS
{
.external_app_pacman : ALIGN(16) SUBALIGN(16)
{
KEEP(*(.external_app.app_pacman.application_information));
*(*ui*external_app*pacman*);
} > ram_external_app_pacman
.external_app_afsk_rx : ALIGN(16) SUBALIGN(16)
{
KEEP(*(.external_app.app_afsk_rx.application_information));
*(*ui*external_app*afsk_rx*);
} > ram_external_app_afsk_rx
}

View File

@ -0,0 +1,66 @@
/******************************************************************************/
/* */
/* PACMAN GAME FOR ARDUINO DUE */
/* */
/******************************************************************************/
/* Copyright (c) 2014 Dr. NCX (mirracle.mxx@gmail.com) */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL */
/* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED */
/* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR */
/* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES */
/* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, */
/* WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, */
/* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS */
/* SOFTWARE. */
/* */
/* MIT license, all text above must be included in any redistribution. */
// #include "ili9328.h"
typedef uint16_t ushort;
#define C16(_rr, _gg, _bb) ((ushort)(((_rr & 0xF8) << 8) | ((_gg & 0xFC) << 3) | ((_bb & 0xF8) >> 3)))
uint16_t _paletteW[] =
{
C16(0, 0, 0),
C16(255, 0, 0), // 1 red
C16(222, 151, 81), // 2 brown
C16(255, 0, 255), // 3 pink
C16(0, 0, 0),
C16(0, 255, 255), // 5 cyan
C16(71, 84, 255), // 6 mid blue
C16(255, 184, 81), // 7 lt brown
C16(0, 0, 0),
C16(255, 255, 0), // 9 yellow
C16(0, 0, 0),
C16(33, 33, 255), // 11 blue
C16(0, 255, 0), // 12 green
C16(71, 84, 174), // 13 aqua
C16(255, 184, 174), // 14 lt pink
C16(222, 222, 255), // 15 whiteish
};
void drawIndexedmap(uint8_t* indexmap, int16_t x, uint16_t y) {
ui::Painter painter;
byte i = 0;
word color = (word)_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]];
if ((color != next_color && width >= 1) || tmpX == 7) {
painter.draw_hline({x + tmpX - width + 1, y + tmpY}, width, ui::Color(color));
color = next_color;
width = 0;
}
width++;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
/*
* 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.
*/
#include "ui.hpp"
#include "ui_pacman.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::pacman {
void initialize_app(ui::NavigationView& nav) {
nav.push<PacmanView>();
}
} // namespace ui::external_app::pacman
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,
/*.app_name = */ "Pac-Man",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0xC0,
0x07,
0xE0,
0x0F,
0xF0,
0x1F,
0xF8,
0x07,
0xF8,
0x01,
0x78,
0x00,
0xF8,
0x01,
0xF8,
0x07,
0xF0,
0x1F,
0xE0,
0x0F,
0xC0,
0x07,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.m4_app_tag = portapack::spi_flash::image_tag_noop */ {'\0', '\0', '\0', '\0'}, // optional
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
#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
Playfield _game;
PacmanView::PacmanView(NavigationView& nav)
: nav_(nav) {
add_children({&dummy});
}
void PacmanView::focus() {
dummy.focus();
}
void PacmanView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
_game.Init();
}
auto switches_debounced = get_switches_state().to_ulong();
but_RIGHT = (switches_debounced & 0x01) == 0x01;
but_LEFT = (switches_debounced & 0x02) == 0x02;
but_DOWN = (switches_debounced & 0x04) == 0x04;
but_UP = (switches_debounced & 0x08) == 0x08;
but_A = (switches_debounced & 0x10) == 0x10;
_game.Step();
}
void PacmanView::frame_sync() {
set_dirty();
}
} // namespace ui::external_app::pacman

View File

@ -0,0 +1,59 @@
/*
* 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 __UI_PACMAN_H__
#define __UI_PACMAN_H__
#include "ui_navigation.hpp"
#include "event_m0.hpp"
#include "message.hpp"
namespace ui::external_app::pacman {
class PacmanView : public View {
public:
PacmanView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Pac-Man"; };
void paint(Painter& painter) override;
void frame_sync();
private:
bool initialized = false;
NavigationView& nav_;
Button dummy{
{240, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_sample{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::pacman
#endif /*__UI_PACMAN_H__*/

View File

@ -116,6 +116,12 @@ void BtnGridView::add_items(std::initializer_list<GridItem> new_items) {
update_items();
}
void BtnGridView::add_item(GridItem new_item) {
menu_items.push_back(new_item);
update_items();
}
void BtnGridView::update_items() {
size_t i = 0;

View File

@ -54,6 +54,7 @@ class BtnGridView : public View {
~BtnGridView();
void add_items(std::initializer_list<GridItem> new_items);
void add_item(GridItem new_item);
void set_max_rows(int rows);
int rows();
void clear();

View File

@ -0,0 +1,139 @@
#include "ui_external_items_menu_loader.hpp"
#include "sd_card.hpp"
namespace ui {
/* static */ std::vector<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
/* static */ std::vector<GridItem> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
bitmaps.clear();
std::vector<GridItem> external_apps;
if (sd_card::status() != sd_card::Status::Mounted)
return external_apps;
for (const auto& entry : std::filesystem::directory_iterator(u"APPS", u"*.ppma")) {
auto filePath = u"/APPS/" + entry.path();
File app;
auto openError = app.open(filePath);
if (openError)
continue;
application_information_t application_information = {};
auto readResult = app.read(&application_information, sizeof(application_information_t));
if (!readResult)
continue;
if (application_information.menu_location != app_location)
continue;
if (application_information.header_version != CURRENT_HEADER_VERSION)
continue;
bool versionMatches = VERSION_MD5 == application_information.app_version;
GridItem gridItem = {};
gridItem.text = reinterpret_cast<char*>(&application_information.app_name[0]);
if (versionMatches) {
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]() {
run_external_app(nav, filePath);
};
} else {
gridItem.color = Color::light_grey();
gridItem.bitmap = &bitmap_sd_card_error;
gridItem.on_select = [&nav]() {
nav.display_modal("Error", "The .ppma file in your APPS\nfolder is outdated. Please\nupdate your SD Card content.");
};
}
external_apps.push_back(gridItem);
}
return external_apps;
}
/* static */ void ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) {
File app;
auto openError = app.open(filePath);
if (openError)
chDbgPanic("file gone");
application_information_t application_information = {};
auto readResult = app.read(&application_information, sizeof(application_information_t));
if (!readResult)
chDbgPanic("no data");
app.seek(0);
if (application_information.m4_app_offset != 0) {
// copy application image
for (size_t file_read_index = 0; file_read_index < application_information.m4_app_offset; 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 > application_information.m4_app_offset)
bytes_to_read = application_information.m4_app_offset - file_read_index;
if (bytes_to_read == 0)
break;
readResult = app.read(&application_information.memory_location[file_read_index], bytes_to_read);
if (!readResult)
chDbgPanic("read error");
if (readResult.value() < std::filesystem::max_file_block_size)
break;
}
// copy baseband image
for (size_t file_read_index = application_information.m4_app_offset;; file_read_index += readResult.value()) {
size_t bytes_to_read = std::filesystem::max_file_block_size;
// not aligned
if ((file_read_index % std::filesystem::max_file_block_size) != 0)
bytes_to_read = std::filesystem::max_file_block_size - (file_read_index % std::filesystem::max_file_block_size);
if (bytes_to_read == 0)
break;
auto target_memory = reinterpret_cast<void*>(portapack::memory::map::m4_code.base() + file_read_index - application_information.m4_app_offset);
readResult = app.read(target_memory, bytes_to_read);
if (!readResult)
chDbgPanic("read error #2");
if (readResult.value() != bytes_to_read)
break;
}
} else {
// copy application image
for (size_t file_read_index = 0; file_read_index < 80 * std::filesystem::max_file_block_size; file_read_index += std::filesystem::max_file_block_size) {
auto bytes_to_read = std::filesystem::max_file_block_size;
readResult = app.read(&application_information.memory_location[file_read_index], bytes_to_read);
if (!readResult)
chDbgPanic("read error #3");
if (readResult.value() < std::filesystem::max_file_block_size)
break;
}
}
application_information.externalAppEntry(nav);
}
} // namespace ui

View File

@ -0,0 +1,66 @@
/*
* 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 __EXTERNAL_ITEMS_MENU_LOADER_H__
#define __EXTERNAL_ITEMS_MENU_LOADER_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
#include "file.hpp"
namespace ui {
template <size_t Width, size_t Height>
class DynamicBitmap {
public:
static constexpr size_t buffer_size = Width * Height / (sizeof(uint8_t) * 8); // one bit per pixel
DynamicBitmap(const uint8_t data[buffer_size])
: _buffer(buffer_size, 0),
_bitmap{new Bitmap{{Width, Height}, &_buffer[0]}} {
memcpy(&_buffer[0], data, buffer_size);
}
const Bitmap* bitmap() { return _bitmap.get(); }
private:
// Allocating both members so the class is movable without invalidation.
std::vector<uint8_t> _buffer;
std::unique_ptr<Bitmap> _bitmap;
};
class ExternalItemsMenuLoader {
public:
static std::vector<GridItem> load_external_items(app_location_t, NavigationView&);
ExternalItemsMenuLoader() = delete;
private:
static std::vector<DynamicBitmap<16, 16>> bitmaps;
static void run_external_app(ui::NavigationView&, std::filesystem::path);
};
} // namespace ui
#endif /*__EXTERNAL_ITEMS_MENU_LOADER_H__*/

View File

@ -34,7 +34,6 @@
#include "ui_about_simple.hpp"
#include "ui_adsb_rx.hpp"
#include "ui_adsb_tx.hpp"
#include "ui_afsk_rx.hpp"
#include "ui_aprs_rx.hpp"
#include "ui_aprs_tx.hpp"
#include "ui_bht_tx.hpp"
@ -80,6 +79,7 @@
#include "ui_touchtunes.hpp"
#include "ui_view_wav.hpp"
#include "ui_whipcalc.hpp"
#include "ui_external_items_menu_loader.hpp"
// #include "acars_app.hpp"
#include "ais_app.hpp"
@ -492,7 +492,6 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
add_items({
// {"ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }},
{"ADS-B", Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBRxView>(); }},
{"AFSK", Color::yellow(), &bitmap_icon_modem, [&nav]() { nav.push<AFSKRxView>(); }},
{"AIS Boats", Color::green(), &bitmap_icon_ais, [&nav]() { nav.push<AISAppView>(); }},
{"Analog TV", Color::yellow(), &bitmap_icon_sstv, [&nav]() { nav.push<AnalogTvView>(); }},
{"APRS", Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSRXView>(); }},
@ -512,6 +511,10 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
// {"SSTV", Color::dark_grey(), &bitmap_icon_sstv, [&nav](){ nav.push<NotImplementedView>(); }},
// {"TETRA", Color::dark_grey(), &bitmap_icon_tetra, [&nav](){ nav.push<NotImplementedView>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::RX, nav)) {
add_item(gridItem);
};
}
/* TransmittersMenuView **************************************************/
@ -540,6 +543,10 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
{"TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav]() { nav.push<LCRView>(); }},
{"TouchTune", ui::Color::green(), &bitmap_icon_touchtunes, [&nav]() { nav.push<TouchTunesView>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::TX, nav)) {
add_item(gridItem);
};
}
/* UtilitiesMenuView *****************************************************/
@ -565,6 +572,10 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
{"Wipe SD card", Color::red(), &bitmap_icon_tools_wipesd, [&nav]() { nav.push<WipeSDView>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::UTILITIES, nav)) {
add_item(gridItem);
};
set_max_rows(2); // allow wider buttons
}

View File

@ -79,6 +79,7 @@ class NavigationView : public View {
}
void push(View* v);
View* push_view(std::unique_ptr<View> new_view);
void replace(View* v);
void pop(bool trigger_update = true);
@ -107,7 +108,6 @@ class NavigationView : public View {
void free_view();
void update_view();
View* push_view(std::unique_ptr<View> new_view);
};
/* Holds widgets and grows dynamically toward the left.

View File

@ -265,6 +265,7 @@ include_directories(. ${INCDIR})
#######################################################################
set(BASEBAND_IMAGES)
set(BASEBAND_EXTERNAL_IMAGES)
macro(DeclareTargets chunk_tag name)
project("baseband_${name}")
@ -279,18 +280,38 @@ macro(DeclareTargets chunk_tag name)
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
add_custom_command(
OUTPUT ${PROJECT_NAME}.bin ${PROJECT_NAME}.img
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMAND ${LZ4} -f -9 ${PROJECT_NAME}.bin ${PROJECT_NAME}.lz4
COMMAND ${MAKE_IMAGE_CHUNK} ${PROJECT_NAME}.lz4 ${chunk_tag} ${PROJECT_NAME}.img
DEPENDS ${PROJECT_NAME}.elf ${MAKE_IMAGE_CHUNK}
VERBATIM
)
if(add_to_firmware)
add_custom_command(
OUTPUT ${chunk_tag}.bin ${PROJECT_NAME}.img
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${chunk_tag}.bin
COMMAND ${LZ4} -f -9 ${chunk_tag}.bin ${PROJECT_NAME}.lz4
COMMAND ${MAKE_IMAGE_CHUNK} ${PROJECT_NAME}.lz4 ${chunk_tag} ${PROJECT_NAME}.img
DEPENDS ${PROJECT_NAME}.elf ${MAKE_IMAGE_CHUNK}
VERBATIM
)
set(BASEBAND_IMAGES ${BASEBAND_IMAGES} ${PROJECT_NAME}.img)
else()
add_custom_command(
OUTPUT ${chunk_tag}.bin
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${chunk_tag}.bin
DEPENDS ${PROJECT_NAME}.elf
VERBATIM
)
set(BASEBAND_EXTERNAL_IMAGES ${BASEBAND_EXTERNAL_IMAGES} ${chunk_tag}.bin)
endif()
set(BASEBAND_IMAGES ${BASEBAND_IMAGES} ${PROJECT_NAME}.img)
endmacro()
set(add_to_firmware TRUE)
### ACARS RX
set(MODE_CPPSRC
@ -312,20 +333,13 @@ set(MODE_CPPSRC
)
DeclareTargets(PADT adsbtx)
### AFSK
### AFSK TX
set(MODE_CPPSRC
proc_afsk.cpp
)
DeclareTargets(PAFT afsktx)
### AFSK RX
set(MODE_CPPSRC
proc_afskrx.cpp
)
DeclareTargets(PAFR afskrx)
### APRS RX
set(MODE_CPPSRC
@ -493,13 +507,6 @@ set(MODE_CPPSRC
)
DeclareTargets(PSTX sstvtx)
### Test
set(MODE_CPPSRC
proc_test.cpp
)
DeclareTargets(PTST test)
### Tones
set(MODE_CPPSRC
@ -586,6 +593,24 @@ set(MODE_CPPSRC
)
DeclareTargets(PUSB sd_over_usb)
### Place external app and disabled images below so they don't get added to the firmware
set(add_to_firmware FALSE)
### AFSK RX
set(MODE_CPPSRC
proc_afskrx.cpp
)
DeclareTargets(PAFR afskrx)
### Test
set(MODE_CPPSRC
proc_test.cpp
)
DeclareTargets(PTST test)
### HackRF "factory" firmware
add_custom_command(
@ -616,7 +641,7 @@ project(baseband)
add_custom_command(
OUTPUT ${PROJECT_NAME}.img
COMMAND cat ${BASEBAND_IMAGES} > ${PROJECT_NAME}.img
DEPENDS ${BASEBAND_IMAGES}
DEPENDS ${BASEBAND_IMAGES} ${BASEBAND_EXTERNAL_IMAGES}
DEPENDS hackrf.img terminator.img
VERBATIM
)

View File

@ -33,6 +33,9 @@ __ram_end__ = __ram_start__ + __ram_size__;
ENTRY(ResetHandler)
/* external apps */
INCLUDE external.ld
SECTIONS
{
. = 0;
@ -142,7 +145,7 @@ SECTIONS
*(COMMON)
. = ALIGN(4);
PROVIDE(_bss_end = .);
} > ram
} > ram
}
PROVIDE(end = .);

View File

@ -0,0 +1,54 @@
/*
* 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 __EXTERNAL_APPS_H__
#define __EXTERNAL_APPS_H__
#include "ch.h"
#include "ui_navigation.hpp"
#include "spi_image.hpp"
#define CURRENT_HEADER_VERSION 0x00000001
typedef void (*externalAppEntry_t)(ui::NavigationView& nav);
enum app_location_t : uint32_t {
UTILITIES = 0,
RX,
TX
};
struct application_information_t {
uint8_t* memory_location;
externalAppEntry_t externalAppEntry;
uint32_t header_version;
uint32_t app_version;
uint8_t app_name[16];
uint8_t bitmap_data[32];
uint32_t icon_color;
app_location_t menu_location;
portapack::spi_flash::image_tag_t m4_app_tag;
uint32_t m4_app_offset;
};
#endif /*__EXTERNAL_APPS_H__*/

View File

@ -0,0 +1,53 @@
#!/bin/bash
#
# 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.
#
# This script will copy compiled external apps to the Portapack.
try=1
while [ $try -le 180 ]
do
if ls /dev/disk/by-id/*Portapack*part1 1> /dev/null 2>&1; then
disk=$(ls /dev/disk/by-id/*Portapack*part1)
part=$(readlink -f $disk)
mountpoint=$(findmnt -f -o TARGET --noheadings $part)
if [[ ! -z "$mountpoint" ]]; then
echo "Copying external applications to" $mountpoint
mkdir -p $mountpoint/APPS
cp application/*.ppma $mountpoint/APPS
echo "Unmounting" $mountpoint
umount $mountpoint
exit
fi
fi
if [ "$(( $try %5 ))" -eq "1" ]; then
echo "Please start SD over USB app ..."
fi
sleep 1
try=$(( $try + 1 ))
done

View File

@ -0,0 +1,131 @@
#!/usr/bin/env python3
#
# 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.
#
import os
import sys
import struct
import subprocess
usage_message = """
PortaPack external app image creator
This script is used in the build process and should never be run manually.
See firmware/application/CMakeLists.txt > COMMAND ${EXPORT_EXTERNAL_APP_IMAGES}
Usage: <command> <project source dir> <binary dir> <cmake objcopy path> <list of external image prefixes>
"""
maximum_application_size = 40*1024
if len(sys.argv) < 4:
print(usage_message)
sys.exit(-1)
def read_image(path):
f = open(path, 'rb')
data = f.read()
f.close()
return data
def write_image(data, path):
f = open(path, 'wb')
f.write(data)
f.close()
def patch_image(image_data, search_address, replace_address):
if (len(image_data) % 4) != 0:
print("file size not divideable by 4")
sys.exit(-1)
external_application_image = bytearray()
for x in range(int(len(image_data)/4)):
snippet = image_data[x*4:4*(x+1)]
val = int.from_bytes(snippet, byteorder='little')
# in firmware/application/external/external.ld the origin is set to something like search_address=0xEEE90000
# if the value is above the search_address and inside a 40kb window (maximum size of an app) we replace it
# with replace_address=(0x1008000 + m4 size) where the app will actually be located. The reason we do this instead just
# using the right address in external.ld is gcc does not permit to use the same memory range multiple times.
if val > search_address and (val - search_address) < maximum_application_size:
relative_address = val - search_address
new_address = replace_address + relative_address
new_snippet = new_address.to_bytes(4, byteorder='little')
external_application_image += new_snippet
else:
external_application_image += snippet
return external_application_image
project_source_dir = sys.argv[1] #/portapack-mayhem/firmware/application
binary_dir = sys.argv[2] #/portapack-mayhem/build/firmware/application
cmake_objcopy = sys.argv[3]
memory_location_header_position = 0
externalAppEntry_header_position = 4
m4_app_tag_header_position = 72
m4_app_offset_header_position = 76
for external_image_prefix in sys.argv[4:]:
# COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}_ext_pacman.bin --only-section=.external_app_pacman
himg = "{}/external_app_{}.himg".format(binary_dir, external_image_prefix)
subprocess.run([cmake_objcopy, "-v", "-O", "binary", "{}/application.elf".format(binary_dir), himg, "--only-section=.external_app_{}".format(external_image_prefix)])
external_application_image = read_image(himg)
#m4 image @ 0x44
chunk_data = external_application_image[m4_app_tag_header_position:m4_app_tag_header_position+4]
# skip m4 if not set
if (chunk_data[0] == 0 and chunk_data[1] == 0 and chunk_data[2] == 0 and chunk_data[3] == 0):
replace_address = 0x10080000
search_address = int.from_bytes(external_application_image[externalAppEntry_header_position:externalAppEntry_header_position+4], byteorder='little') & 0xFFFF0000
external_application_image = patch_image(external_application_image, search_address, replace_address)
external_application_image[memory_location_header_position:memory_location_header_position+4] = replace_address.to_bytes(4, byteorder='little')
write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix))
continue
chunk_tag = chunk_data.decode("utf-8")
m4_image = read_image("{}/../baseband/{}.bin".format(binary_dir, chunk_tag))
app_image_len = len(external_application_image)
external_application_image += m4_image
if (len(m4_image) % 4) != 0:
print("m4 file size not divideable by 4")
sys.exit(-1)
replace_address = 0x10080000 + len(m4_image)
search_address = int.from_bytes(external_application_image[externalAppEntry_header_position:externalAppEntry_header_position+4], byteorder='little') & 0xFFFF0000
external_application_image = patch_image(external_application_image, search_address, replace_address)
external_application_image[memory_location_header_position:memory_location_header_position+4] = replace_address.to_bytes(4, byteorder='little')
external_application_image[m4_app_offset_header_position:m4_app_offset_header_position+4] = app_image_len.to_bytes(4, byteorder='little')
if (len(external_application_image) > maximum_application_size) != 0:
print("application {} can not exceed 40kb: {} bytes used".format(external_image_prefix, len(external_application_image)))
sys.exit(-1)
# write .ppma (portapack mayhem application)
write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix))