Compare commits

...

12 Commits

Author SHA1 Message Date
gullradriel 264b1b38ba fix complicated timing logic (#1480) 2023-10-09 20:01:40 +02:00
Mark Thompson 65e71508ac Incrementing date workaround for dead RTC battery (#1479)
* Enable f_utime function
* Added file_update_date() function
* Added rtc_battery_workaround() function
* Update ui_navigation.cpp
2023-10-09 12:04:46 +02:00
Mark Thompson e5546159c5 Kludge fix for null parent() (#1477) 2023-10-09 12:03:11 +02:00
gullradriel d7c961ea4f Fixing empty output file/store button/text display (#1475) 2023-10-08 22:21:00 +02:00
Mark Thompson ad987f2b5e Added new icon for IQ Trim, and IQ Trim Button in FileMan (#1476)
* Add files via upload

* Added trim icon

* Added trim icon

* Added Trim button to FileMan

* Added Trim button to FileMan

* Added Trim button to FileMan

* Added Trim button to FileMan
2023-10-08 14:57:59 -05:00
Brumi-2021 defb934c3f Activating TX Low Pass Filter in Second IF IC (#1470)
* Activating TX Low Pass Filter in Second IF IC
* solving PR conversation revisions
* Final TX LPF decision for ADSB_TX
* Small comments correction.
* Added set TX LPF also  in APRS, BurgerPgr,Jammer
2023-10-08 17:50:31 +02:00
Bernd Herzog 5a850984b9 Calculator app (#1474)
* implemented calculator app

* improved output

* refactoring
2023-10-08 16:24:02 +02:00
Bernd Herzog 1185b67b28 changed alignment for text sections (#1473) 2023-10-07 15:30:43 +02:00
Bernd Herzog abb42e8df4 added executeable bit for new scripts (#1471) 2023-10-03 08:17:45 +02:00
Bernd Herzog 7fdb1af69d 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
2023-10-02 20:19:22 +02:00
Kyle Reed 78713cc2af Search cleanup, more binder support (#1467) 2023-10-01 09:04:37 -07:00
Kyle Reed 951890eaff Add Binder helper methods (#1465)
* Add binder, fix focus on remote btn delete
* Use binder for freqman edit
2023-09-29 18:55:27 +02:00
77 changed files with 13053 additions and 431 deletions
+2 -2
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 }}
+2 -2
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 }}
+6
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)
+10 -2
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.
+9 -5
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
)
+6 -1
View File
@@ -74,7 +74,10 @@ void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
auto duration = ms_duration(file_size, transmitter_model.sampling_rate(), 2);
text_duration.set(to_string_time_ms(duration));
button_play.focus();
// TODO: fix in UI framework with 'try_focus()'?
// Hack around focus getting called by ctor before parent is set.
if (parent())
button_play.focus();
}
void GpsSimAppView::on_tx_progress(const uint32_t progress) {
@@ -173,6 +176,8 @@ GpsSimAppView::GpsSimAppView(
&waterfall,
});
transmitter_model.set_baseband_bandwidth(15'000'000); // GPS L1 signal use to have wide band spectrum, still with lobule energy -30dB's at + - 15 Mhz
if (!settings_.radio_loaded()) {
field_frequency.set_value(initial_target_frequency);
transmitter_model.set_sampling_rate(2600000);
@@ -120,6 +120,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
);
baseband::set_sample_rate(sample_rate);
transmitter_model.set_baseband_bandwidth(1'750'000); // the Minimum TX LPF 1M75 is fine.
transmitter_model.enable();
tx_view.set_transmitting(true);
+4
View File
@@ -296,6 +296,10 @@ void ADSBTxView::generate_frames() {
void ADSBTxView::start_tx() {
generate_frames();
/* Already tested , with SDR Angel + another Hackrf RX and dump1090 + SDR RLT. Final conclusion is TX LPF 6 Mhz is
* the best settings to fulfill ADSB transponder spectrum mask requirements (<=-20 dB's at +-7Mhz , <=-40 dB's at +-23Mhz )
* and not showing any ADSB data decoding degradation.*/
transmitter_model.set_baseband_bandwidth(6'000'000); // best settings for ADSB TX.
transmitter_model.enable();
baseband::set_adsb();
+1
View File
@@ -57,6 +57,7 @@ void APRSTXView::start_tx() {
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
// text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
transmitter_model.set_baseband_bandwidth(1'750'000); // APRS Automatic Packet Reporting System (APRS).AFSK with NBFM , max BW= 12k5 , then TX LPF min 1M75
transmitter_model.enable();
baseband::set_afsk_data(
@@ -67,6 +67,7 @@ void CoasterPagerView::generate_frame() {
void CoasterPagerView::start_tx() {
generate_frame();
transmitter_model.set_baseband_bandwidth(1'750'000); // AFSK narrowband ,low baud, max FM dev 150khz , std 10khz. TX LPF=1M75 the min.
transmitter_model.enable();
baseband::set_fsk_data(19 * 8, 2280000 / 1000, 5000, 32);
+10
View File
@@ -30,6 +30,7 @@
#include "ui_remote.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp"
#include "ui_iq_trim.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
@@ -566,6 +567,7 @@ FileManagerView::FileManagerView(
&button_new_file,
&button_open_notepad,
&button_rename_timestamp,
&button_open_iq_trim,
});
menu_view.on_highlight = [this]() {
@@ -648,6 +650,14 @@ FileManagerView::FileManagerView(
} else
nav_.display_modal("Timestamp Rename", "Can't rename that.");
};
button_open_iq_trim.on_select = [this]() {
auto path = get_selected_full_path();
if (selected_is_valid() && !get_selected_entry().is_directory && is_cxx_capture_file(path)) {
nav_.push<IQTrimView>(path);
} else
nav_.display_modal("IQ Trim", "Not a capture file.");
};
}
} // namespace ui
+6
View File
@@ -272,6 +272,12 @@ class FileManagerView : public FileManBaseView {
&bitmap_icon_options_datetime,
Color::orange(),
/*vcenter*/ true};
NewButton button_open_iq_trim{
{9 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_trim,
Color::orange()};
};
} /* namespace ui */
+20 -72
View File
@@ -23,6 +23,7 @@
#include "ui_freqman.hpp"
#include "binder.hpp"
#include "event_m0.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
@@ -152,28 +153,14 @@ FrequencySaveView::FrequencySaveView(
add_children(
{&labels,
&big_display,
&button_clear,
&button_edit,
&button_save,
&text_description});
&field_description});
entry_.type = freqman_type::Single;
entry_.frequency_a = value;
entry_.description = to_string_timestamp(rtc_time::now());
refresh_ui();
button_clear.on_select = [this, &nav](Button&) {
entry_.description = "";
refresh_ui();
};
button_edit.on_select = [this, &nav](Button&) {
temp_buffer_ = entry_.description;
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
entry_.description = new_desc;
refresh_ui();
});
};
bind(field_description, entry_.description, nav);
button_save.on_select = [this, &nav](Button&) {
db_.insert_entry(db_.entry_count(), entry_);
@@ -181,9 +168,13 @@ FrequencySaveView::FrequencySaveView(
};
}
void FrequencySaveView::focus() {
refresh_ui();
FreqManBaseView::focus();
}
void FrequencySaveView::refresh_ui() {
big_display.set(entry_.frequency_a);
text_description.set(entry_.description);
}
/* FrequencyLoadView *************************************/
@@ -376,69 +367,26 @@ FrequencyEditView::FrequencyEditView(
field_step.options().insert(
field_step.options().begin(), {"None", -1});
field_type.set_by_value((int32_t)entry_.type);
field_type.on_change = [this](size_t, auto value) {
entry_.type = static_cast<freqman_type>(value);
bind(field_type, entry_.type, [this](auto) {
refresh_ui();
};
});
// TODO: this pattern should be able to be wrapped up.
field_freq_a.set_value(entry_.frequency_a);
field_freq_a.on_change = [this](rf::Frequency f) {
entry_.frequency_a = f;
bind(field_freq_a, entry_.frequency_a, nav, [this](auto) {
refresh_ui();
};
field_freq_a.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_a.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_a.set_value(f);
};
};
});
field_freq_b.set_value(entry_.frequency_b);
field_freq_b.on_change = [this](rf::Frequency f) {
entry_.frequency_b = f;
bind(field_freq_b, entry_.frequency_b, nav, [this](auto) {
refresh_ui();
};
field_freq_b.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_b.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_b.set_value(f);
};
};
});
field_modulation.set_by_value((int32_t)entry_.modulation);
field_modulation.on_change = [this](size_t, auto value) {
entry_.modulation = static_cast<freqman_index_t>(value);
bind(field_modulation, entry_.modulation, [this](auto) {
populate_bandwidth_options();
};
});
field_bandwidth.set_by_value((int32_t)entry_.bandwidth);
field_bandwidth.on_change = [this](size_t, auto value) {
entry_.bandwidth = static_cast<freqman_index_t>(value);
};
field_step.set_by_value((int32_t)entry_.step);
field_step.on_change = [this](size_t, auto value) {
entry_.step = static_cast<freqman_index_t>(value);
};
field_tone.set_by_value((int32_t)entry_.tone);
field_tone.on_change = [this](size_t, auto value) {
entry_.tone = static_cast<freqman_index_t>(value);
};
field_description.set_text(entry_.description);
field_description.on_change = [this](TextField& tf) {
entry_.description = tf.get_text();
};
field_description.on_select = [this](TextField& tf) {
temp_buffer_ = tf.get_text();
text_prompt(nav_, temp_buffer_, FreqManBaseView::desc_edit_max,
[this, &tf](std::string& new_desc) {
tf.set_text(new_desc);
});
};
bind(field_bandwidth, entry_.bandwidth);
bind(field_step, entry_.step);
bind(field_tone, entry_.tone);
bind(field_description, entry_.description, nav_);
button_save.on_select = [this](Button&) {
if (on_save)
+4 -10
View File
@@ -87,9 +87,9 @@ class FrequencySaveView : public FreqManBaseView {
public:
FrequencySaveView(NavigationView& nav, const rf::Frequency value);
std::string title() const override { return "Save freq"; }
void focus() override;
private:
std::string temp_buffer_{};
freqman_entry entry_{};
void refresh_ui();
@@ -101,15 +101,9 @@ class FrequencySaveView : public FreqManBaseView {
Labels labels{
{{0 * 8, 6 * 16}, "Description:", Color::white()}};
Text text_description{{0 * 8, 7 * 16, 30 * 8, 1 * 16}};
Button button_clear{
{4 * 8, 10 * 16, 10 * 8, 2 * 16},
"Clear"};
Button button_edit{
{16 * 8, 10 * 16, 10 * 8, 2 * 16},
"Edit"};
TextField field_description{
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
""};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
+13 -4
View File
@@ -47,10 +47,7 @@ IQTrimView::IQTrimView(NavigationView& nav)
auto open_view = nav_.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
path_ = std::move(path);
profile_capture();
compute_range();
refresh_ui();
open_file(path);
};
};
@@ -84,6 +81,18 @@ IQTrimView::IQTrimView(NavigationView& nav)
};
}
IQTrimView::IQTrimView(NavigationView& nav, const fs::path& path)
: IQTrimView(nav) {
open_file(path);
}
void IQTrimView::open_file(const std::filesystem::path& path) {
path_ = std::move(path);
profile_capture();
compute_range();
refresh_ui();
}
void IQTrimView::paint(Painter& painter) {
if (info_) {
uint32_t power_cutoff = field_cutoff.value() * static_cast<uint64_t>(info_->max_power) / 100;
+3
View File
@@ -68,12 +68,15 @@ class TrimProgressUI {
class IQTrimView : public View {
public:
IQTrimView(NavigationView& nav);
IQTrimView(NavigationView& nav, const std::filesystem::path& path);
std::string title() const override { return "IQ Trim"; }
void paint(Painter& painter) override;
void focus() override;
private:
void open_file(const std::filesystem::path& path);
/* Update controls with latest values. */
void refresh_ui();
+3 -1
View File
@@ -261,6 +261,7 @@ void JammerView::start_tx() {
transmitter_model.set_rf_amp(field_amp.value());
transmitter_model.set_tx_gain(field_gain.value());
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
transmitter_model.enable();
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
@@ -288,7 +289,8 @@ void JammerView::on_timer() {
mscounter = 0;
if (jamming) {
if (cooling) {
if (++seconds >= field_timepause.value()) { // Re-start TX
if (++seconds >= field_timepause.value()) { // Re-start TX
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
transmitter_model.enable();
button_transmit.set_text("STOP");
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
+4
View File
@@ -87,6 +87,10 @@ void MicTXView::set_tx(bool enable) {
transmitting = true;
configure_baseband();
transmitter_model.set_target_frequency(tx_frequency); // Now, no need: transmitter_model.set_tx_gain(tx_gain), nor (rf_amp);
/* The max. Power Spectrum Densitiy in WFM with High tone mod level (80%) and high 32kHZ subtone as fmod. with max fdeviation 150k ,
BW aprox = 2 *(150K + 32K) = 364khz, then we just select the minimum TX LPF 1M75. */
transmitter_model.set_baseband_bandwidth(1'750'000);
transmitter_model.enable();
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
} else {
+1 -1
View File
@@ -273,7 +273,7 @@ void PlaylistView::send_current_track() {
// ReplayThread starts immediately on construction; must be set before creating.
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
transmitter_model.set_sampling_rate(get_actual_sample_rate(current()->metadata.sample_rate));
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.set_baseband_bandwidth(current()->metadata.sample_rate <= 500'000 ? 1'750'000 : 2'500'000); // TX LPF min 1M75 for SR <=500K, and 2M5 (by experimental test) for SR >500K
transmitter_model.enable();
// Reset the transmit progress bar.
@@ -60,7 +60,6 @@ class PlaylistView : public View {
// More header == less spectrum view.
static constexpr ui::Dim header_height = 6 * 16;
static constexpr uint32_t baseband_bandwidth = 2'500'000;
struct playlist_entry {
std::filesystem::path path{};
+1
View File
@@ -187,6 +187,7 @@ void RDSView::start_tx() {
else
frame_datetime.clear();
transmitter_model.set_baseband_bandwidth(1'750'000); // Big Spectrum harmonics reduction, and now quicker decoding time.
transmitter_model.enable();
tx_thread = std::make_unique<RDSThread>(frames);
+31 -43
View File
@@ -103,7 +103,6 @@ void ReconView::reset_indexes() {
void ReconView::update_description() {
if (frequency_list.empty() || current_entry().description.empty()) {
description = "...no description...";
return;
} else {
switch (current_entry().type) {
case freqman_type::Range:
@@ -117,7 +116,6 @@ void ReconView::update_description() {
}
description += current_entry().description;
}
desc_cycle.set(description);
}
@@ -496,6 +494,7 @@ ReconView::ReconView(NavigationView& nav)
button_manual_recon.on_select = [this](Button&) {
button_remove.set_text("DELETE");
button_add.hidden(false);
scanner_mode = false;
manual_mode = true;
recon_pause();
@@ -539,7 +538,7 @@ ReconView::ReconView(NavigationView& nav)
button_scanner_mode.set_style(&Styles::white);
button_scanner_mode.set_text("MANUAL");
file_name.set_style(&Styles::white);
file_name.set("MANUAL RANGE RECON");
file_name.set("MANUAL => " + output_file);
desc_cycle.set_style(&Styles::white);
last_entry.modulation = freqman_invalid_index;
@@ -608,12 +607,12 @@ ReconView::ReconView(NavigationView& nav)
scanner_mode = false;
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
button_remove.set_text("DELETE");
button_remove.set_text("REMOVE");
} else {
scanner_mode = true;
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
button_scanner_mode.set_text("REMOVE");
button_remove.set_text("DELETE");
}
frequency_file_load(true);
if (autostart) {
@@ -621,12 +620,17 @@ ReconView::ReconView(NavigationView& nav)
} else {
recon_pause();
}
button_add.hidden(scanner_mode);
if (scanner_mode) // only needed when hiding, UI mayhem
set_dirty();
};
button_config.on_select = [this, &nav](Button&) {
if (is_recording) // disabling config while recording
return;
clear_freqlist_for_ui_action();
freq_lock = 0;
timer = 0;
auto open_view = nav.push<ReconSetupView>(input_file, output_file);
@@ -733,30 +737,22 @@ void ReconView::frequency_file_load(bool) {
button_scanner_mode.set_text("RECON");
}
file_name.set(file_input + "=>" + output_file);
freqman_load_options options{
.load_freqs = load_freqs,
.load_ranges = load_ranges,
.load_hamradios = load_hamradios};
if (!load_freqman_file(file_input, frequency_list, options)) {
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
file_name.set_style(&Styles::red);
desc_cycle.set(" NO " + file_input + ".TXT FILE ...");
file_name.set("=> NO DATA");
} else {
file_name.set(file_input + "=>" + output_file);
if (frequency_list.size() == 0) {
file_name.set_style(&Styles::red);
desc_cycle.set("/0 no entries in list");
file_name.set("BadOrEmpty " + file_input);
} else {
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
}
desc_cycle.set("...empty file...");
frequency_list.clear();
text_cycle.set_text(" ");
return;
}
if (frequency_list.empty()) {
text_cycle.set_text(" ");
return; // Can't really do much.
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
reset_indexes();
@@ -781,19 +777,8 @@ void ReconView::frequency_file_load(bool) {
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
systime_t time_interval = 100;
uint32_t local_recon_lock_duration = recon_lock_duration;
chrono_end = chTimeNow();
if (field_mode.selected_index_value() == SPEC_MODULATION) {
time_interval = chrono_end - chrono_start;
if (field_lock_wait.value() == 0) {
if (time_interval <= 1) // capping here to avoid freeze because too quick
local_recon_lock_duration = 2; // minimum working tested value
else
local_recon_lock_duration = time_interval;
}
}
systime_t time_interval = chrono_end - chrono_start;
chrono_start = chrono_end;
// hack to reload the list if it was cleared by going into CONFIG
@@ -813,7 +798,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (!timer) {
status = 0;
freq_lock = 0;
timer = local_recon_lock_duration;
timer = recon_lock_duration;
}
if (freq_lock < recon_lock_nb_match) // LOCKING
{
@@ -1214,14 +1199,6 @@ void ReconView::handle_remove_current_item() {
if (current_is_valid()) {
frequency_list.erase(frequency_list.begin() + current_index);
}
if (frequency_list.size() > 0) {
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
update_description();
} else {
current_index = 0;
}
}
// In Scanner or Manual mode, remove the entry from the output file.
@@ -1232,6 +1209,17 @@ void ReconView::handle_remove_current_item() {
freq_db.delete_entry(entry);
}
// Clip
if (frequency_list.size() > 0) {
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
} else {
current_index = 0;
text_cycle.set_text(" ");
}
update_description();
}
} /* namespace ui */
+1 -1
View File
@@ -303,7 +303,7 @@ class ReconView : public View {
NumberField field_lock_wait{
{25 * 8, 24 * 8 + 4},
4,
{0, RECON_MAX_LOCK_DURATION},
{STATS_UPDATE_INTERVAL, RECON_MAX_LOCK_DURATION},
STATS_UPDATE_INTERVAL,
' ',
};
+12 -35
View File
@@ -21,6 +21,7 @@
#include "ui_remote.hpp"
#include "binder.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "io_convert.hpp"
@@ -239,19 +240,9 @@ RemoteEntryEditView::RemoteEntryEditView(
&button_done,
});
// TODO: It's time to make field bindings and clean this mess up.
field_name.on_change = [this](TextField& tf) {
entry_.name = tf.get_text();
button_preview.set_text(entry_.name);
};
field_name.on_select = [this, &nav](TextField& tf) {
temp_buffer_ = tf.get_text();
text_prompt(nav, temp_buffer_, text_edit_max,
[this, &tf](std::string& str) {
tf.set_text(str);
});
};
field_name.set_text(entry_.name);
bind(field_name, entry_.name, nav, [this](auto& v) {
button_preview.set_text(v);
});
field_path.on_select = [this, &nav](TextField&) {
auto open_view = nav.push<FileLoadView>(".C*");
@@ -262,31 +253,16 @@ RemoteEntryEditView::RemoteEntryEditView(
};
};
field_freq.on_edit = [this, &nav]() {
auto freq_view = nav.push<FrequencyKeypadView>(entry_.metadata.center_frequency);
freq_view->on_changed = [this](rf::Frequency f) {
entry_.metadata.center_frequency = f;
field_freq.set_value(f);
};
};
field_icon_index.on_change = [this](int32_t v) {
entry_.icon = v;
bind(field_freq, entry_.metadata.center_frequency, nav);
bind(field_icon_index, entry_.icon, [this](auto v) {
button_preview.set_bitmap(RemoteIcons::get(v));
};
field_icon_index.set_value(entry.icon);
field_fg_color_index.on_change = [this](int32_t v) {
entry_.fg_color = v;
});
bind(field_fg_color_index, entry_.fg_color, [this](auto v) {
button_preview.set_color(RemoteColors::get(v));
};
field_fg_color_index.set_value(entry_.fg_color);
field_bg_color_index.on_change = [this](int32_t v) {
entry_.bg_color = v;
});
bind(field_bg_color_index, entry_.bg_color, [this](auto) {
button_preview.set_dirty();
};
field_bg_color_index.set_value(entry_.bg_color);
});
button_delete.on_select = [this, &nav]() {
nav.display_modal(
@@ -485,6 +461,7 @@ void RemoteView::edit_button(RemoteButton& btn) {
nav_.set_on_pop([this]() {
refresh_ui();
set_needs_save();
focus(); // Need to refocus after refreshing the buttons.
});
edit_view->on_delete = [this](RemoteEntryModel& to_delete) {
-1
View File
@@ -195,7 +195,6 @@ class RemoteEntryEditView : public View {
private:
RemoteEntryModel& entry_;
std::string temp_buffer_{};
void refresh_ui();
void load_path(std::filesystem::path&& path);
+124 -157
View File
@@ -23,7 +23,9 @@
#include "ui_search.hpp"
#include "baseband_api.hpp"
#include "binder.hpp"
#include "string_format.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
@@ -47,8 +49,61 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
painter.draw_string(target_rect.location(), style, to_string_short_freq(entry.frequency) + " " + entry.time + " " + str_duration);
}
void SearchView::focus() {
field_frequency_min.focus();
/* SearchView ********************************************/
SearchView::SearchView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({&labels,
&field_frequency_min,
&field_frequency_max,
&field_lna,
&field_vga,
&field_threshold,
&text_mean,
&text_slices,
&text_rate,
&text_infos,
&vu_max,
&progress_timers,
&check_snap,
&options_snap,
&big_display,
&recent_entries_view});
baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);
recent_entries_view.set_parent_rect({0, 28 * 8, screen_width, 12 * 8});
recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
nav.push<FrequencySaveView>(entry.frequency);
};
text_mean.set_style(&Styles::grey);
text_slices.set_style(&Styles::grey);
text_rate.set_style(&Styles::grey);
progress_timers.set_style(&Styles::grey);
big_display.set_style(&Styles::grey);
field_frequency_min.set_step(100'000);
bind(field_frequency_min, settings_.freq_min, nav, [this](auto) {
on_range_changed();
});
field_frequency_max.set_step(100'000);
bind(field_frequency_max, settings_.freq_max, nav, [this](auto) {
on_range_changed();
});
bind(field_threshold, settings_.power_threshold);
bind(check_snap, settings_.snap_search);
bind(options_snap, settings_.snap_step);
progress_timers.set_max(DETECT_DELAY);
on_range_changed();
receiver_model.enable();
}
SearchView::~SearchView() {
@@ -56,6 +111,18 @@ SearchView::~SearchView() {
baseband::shutdown();
}
void SearchView::on_show() {
baseband::spectrum_streaming_start();
}
void SearchView::on_hide() {
baseband::spectrum_streaming_stop();
}
void SearchView::focus() {
field_frequency_min.focus();
}
void SearchView::do_detection() {
uint8_t power_max = 0;
int32_t bin_max = -1;
@@ -83,7 +150,7 @@ void SearchView::do_detection() {
if (power > overall_power_max)
overall_power_max = power;
if ((power >= mean_power + power_threshold) && (power > power_max)) {
if ((power >= mean_power + settings_.power_threshold) && (power > power_max)) {
power_max = power;
bin_max = slices[slice].max_index;
slice_max = slice;
@@ -104,7 +171,7 @@ void SearchView::do_detection() {
}
// Check range
if ((resolved_frequency >= f_min) && (resolved_frequency <= f_max)) {
if ((resolved_frequency >= settings_.freq_min) && (resolved_frequency <= settings_.freq_max)) {
duration = 0;
auto& entry = ::on_packet(recent, resolved_frequency);
@@ -159,16 +226,47 @@ void SearchView::do_detection() {
}
}
void SearchView::add_spectrum_pixel(Color color) {
// Is avoiding floats really necessary ?
bin_skip_acc += bin_skip_frac;
if (bin_skip_acc < 0x10000)
return;
void SearchView::do_timers() {
if (timing_div >= 60) {
// ~1Hz
bin_skip_acc -= 0x10000;
timing_div = 0;
if (pixel_index < 240)
spectrum_row[pixel_index++] = color;
// Update scan rate
text_rate.set(to_string_dec_uint(search_counter, 3));
search_counter = 0;
}
if (timing_div % 12 == 0) {
// ~5Hz
// Update power levels
text_mean.set(to_string_dec_uint(mean_power, 3));
vu_max.set_value(overall_power_max);
vu_max.set_mark(mean_power + settings_.power_threshold);
}
if (timing_div % 6 == 0) {
// ~10Hz
// Update timing indicator
if (locked) {
progress_timers.set_max(RELEASE_DELAY);
progress_timers.set_value(RELEASE_DELAY - release_timer);
} else {
progress_timers.set_max(DETECT_DELAY);
progress_timers.set_value(detect_timer);
}
// Increment timers
if (detect_timer < DETECT_DELAY) detect_timer++;
if (release_timer < RELEASE_DELAY) release_timer++;
if (locked) duration++;
}
timing_div++;
}
void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
@@ -221,22 +319,14 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_start();
}
void SearchView::on_show() {
baseband::spectrum_streaming_start();
}
void SearchView::on_hide() {
baseband::spectrum_streaming_stop();
}
void SearchView::on_range_changed() {
rf::Frequency slices_span, center_frequency;
rf::Frequency slices_span;
rf::Frequency center_frequency;
int64_t offset;
size_t slice;
f_min = field_frequency_min.value();
f_max = field_frequency_max.value();
search_span = abs(f_max - f_min);
// TODO: enforce min < max?
search_span = abs(settings_.freq_max - settings_.freq_min);
if (search_span > SEARCH_SLICE_WIDTH) {
// ex: 100M~115M (15M span):
@@ -253,14 +343,14 @@ void SearchView::on_range_changed() {
// offset = 0 + 2.5/2 = 1.25M
offset = ((search_span - slices_span) / 2) + (SEARCH_SLICE_WIDTH / 2);
// slice_start = 100M + 1.25M = 101.25M
center_frequency = std::min(f_min, f_max) + offset;
center_frequency = std::min(settings_.freq_min, settings_.freq_max) + offset;
for (slice = 0; slice < slices_nb; slice++) {
slices[slice].center_frequency = center_frequency;
center_frequency += SEARCH_SLICE_WIDTH;
}
} else {
slices[0].center_frequency = (f_max + f_min) / 2;
slices[0].center_frequency = (settings_.freq_max + settings_.freq_min) / 2;
receiver_model.set_target_frequency(slices[0].center_frequency);
slices_nb = 1;
@@ -272,139 +362,16 @@ void SearchView::on_range_changed() {
slice_counter = 0;
}
void SearchView::on_lna_changed(int32_t v_db) {
receiver_model.set_lna(v_db);
}
void SearchView::add_spectrum_pixel(Color color) {
// Is avoiding floats really necessary?
bin_skip_acc += bin_skip_frac;
if (bin_skip_acc < 0x10000)
return;
void SearchView::on_vga_changed(int32_t v_db) {
receiver_model.set_vga(v_db);
}
bin_skip_acc -= 0x10000;
void SearchView::do_timers() {
if (timing_div >= 60) {
// ~1Hz
timing_div = 0;
// Update scan rate
text_rate.set(to_string_dec_uint(search_counter, 3));
search_counter = 0;
}
if (timing_div % 12 == 0) {
// ~5Hz
// Update power levels
text_mean.set(to_string_dec_uint(mean_power, 3));
vu_max.set_value(overall_power_max);
vu_max.set_mark(mean_power + power_threshold);
}
if (timing_div % 6 == 0) {
// ~10Hz
// Update timing indicator
if (locked) {
progress_timers.set_max(RELEASE_DELAY);
progress_timers.set_value(RELEASE_DELAY - release_timer);
} else {
progress_timers.set_max(DETECT_DELAY);
progress_timers.set_value(detect_timer);
}
// Increment timers
if (detect_timer < DETECT_DELAY) detect_timer++;
if (release_timer < RELEASE_DELAY) release_timer++;
if (locked) duration++;
}
timing_div++;
}
SearchView::SearchView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({&labels,
&field_frequency_min,
&field_frequency_max,
&field_lna,
&field_vga,
&field_threshold,
&text_mean,
&text_slices,
&text_rate,
&text_infos,
&vu_max,
&progress_timers,
&check_snap,
&options_snap,
&big_display,
&recent_entries_view});
baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);
recent_entries_view.set_parent_rect({0, 28 * 8, 240, 12 * 8});
recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
nav.push<FrequencyKeypadView>(entry.frequency);
};
text_mean.set_style(&Styles::grey);
text_slices.set_style(&Styles::grey);
text_rate.set_style(&Styles::grey);
progress_timers.set_style(&Styles::grey);
big_display.set_style(&Styles::grey);
check_snap.set_value(true);
options_snap.set_selected_index(1); // 12.5kHz
field_threshold.set_value(80);
field_threshold.on_change = [this](int32_t value) {
power_threshold = value;
};
field_frequency_min.set_value(receiver_model.target_frequency() - 1000000);
field_frequency_min.set_step(100000);
field_frequency_min.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_min.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() - 1000000);
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_min.set_value(f);
};
};
field_frequency_max.set_value(receiver_model.target_frequency() + 1000000);
field_frequency_max.set_step(100000);
field_frequency_max.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_max.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() + 1000000);
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_max.set_value(f);
};
};
field_lna.set_value(receiver_model.lna());
field_lna.on_change = [this](int32_t v) {
this->on_lna_changed(v);
};
field_vga.set_value(receiver_model.vga());
field_vga.on_change = [this](int32_t v_db) {
this->on_vga_changed(v_db);
};
progress_timers.set_max(DETECT_DELAY);
on_range_changed();
receiver_model.enable();
if (pixel_index < screen_width)
spectrum_row[pixel_index++] = color;
}
} /* namespace ui */
+55 -31
View File
@@ -20,14 +20,13 @@
* Boston, MA 02110-1301, USA.
*/
#include "app_settings.hpp"
#include "receiver_model.hpp"
#include "recent_entries.hpp"
#include "radio_state.hpp"
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "recent_entries.hpp"
namespace ui {
@@ -95,6 +94,26 @@ class SearchView : public View {
SEARCH_SLICE_WIDTH /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
// Settings
struct SearchSettings {
uint32_t power_threshold = 80;
rf::Frequency freq_min = 100'000'000;
rf::Frequency freq_max = 400'000'000;
bool snap_search = true;
uint32_t snap_step = 12'500;
};
SearchSettings settings_{};
app_settings::SettingsManager app_settings_{
"rx_search"sv,
app_settings::Mode::RX,
{
{"power_threshold"sv, &settings_.power_threshold},
{"freq_min"sv, &settings_.freq_min},
{"freq_max"sv, &settings_.freq_max},
{"snap_search"sv, &settings_.snap_search},
{"snap_step"sv, &settings_.snap_step},
}};
struct slice_t {
rf::Frequency center_frequency;
uint8_t max_power;
@@ -103,33 +122,36 @@ class SearchView : public View {
int16_t index;
} slices[32];
uint32_t bin_skip_acc{0}, bin_skip_frac{};
uint32_t pixel_index{0};
std::array<Color, 240> spectrum_row = {0};
ChannelSpectrumFIFO* fifo{nullptr};
rf::Frequency f_min{0}, f_max{0};
uint8_t detect_timer{0}, release_timer{0}, timing_div{0};
uint8_t overall_power_max{0};
uint32_t mean_power{0}, mean_acc{0};
uint32_t duration{0};
uint32_t power_threshold{80}; // Todo: Put this in persistent / settings
rf::Frequency slice_start{0};
uint8_t slices_nb{0};
uint8_t slice_counter{0};
int16_t last_bin{0};
uint32_t last_slice{0};
Coord last_tick_pos{0};
rf::Frequency search_span{0}, resolved_frequency{0};
uint16_t locked_bin{0};
uint8_t search_counter{0};
bool locked{false};
uint32_t bin_skip_acc = 0;
uint32_t bin_skip_frac = 0;
uint32_t pixel_index = 0;
std::array<Color, 240> spectrum_row{};
ChannelSpectrumFIFO* fifo = nullptr;
uint8_t detect_timer = 0;
uint8_t release_timer = 0;
uint8_t timing_div = 0;
uint8_t overall_power_max{0};
uint32_t mean_power = 0;
uint32_t mean_acc = 0;
uint32_t duration = 0;
rf::Frequency slice_start = 0;
uint8_t slices_nb = 0;
uint8_t slice_counter = 0;
int16_t last_bin = 0;
uint32_t last_slice = 0;
Coord last_tick_pos = 0;
rf::Frequency search_span = 0;
rf::Frequency resolved_frequency = 0;
uint16_t locked_bin = 0;
uint8_t search_counter = 0;
bool locked = false;
void do_detection();
void do_timers();
void on_channel_spectrum(const ChannelSpectrum& spectrum);
void on_range_changed();
void do_detection();
void on_lna_changed(int32_t v_db);
void on_vga_changed(int32_t v_db);
void do_timers();
void add_spectrum_pixel(Color color);
const RecentEntriesColumns columns{{{"Frequency", 9},
@@ -150,6 +172,7 @@ class SearchView : public View {
{1 * 8, 1 * 16}};
FrequencyField field_frequency_max{
{11 * 8, 1 * 16}};
LNAGainField field_lna{
{22 * 8, 1 * 16}};
VGAGainField field_vga{
@@ -191,10 +214,10 @@ class SearchView : public View {
{17 * 8, 15 * 8}, // Position
7, // Length
{ // Options
{"25kHz ", 25000},
{"12.5kHz", 12500},
{"8.33kHz", 8333},
{"2.5kHz", 2500},
{"25kHz ", 25'000},
{"12.5kHz", 12'500},
{"8.33kHz", 8'333},
{"2.5kHz", 2'500},
{"500Hz", 500}}};
BigFrequency big_display{
@@ -207,6 +230,7 @@ class SearchView : public View {
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
this->fifo = message.fifo;
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
+24
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;
+1
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();
+117
View File
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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 __BINDER_H__
#define __BINDER_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_widget.hpp"
namespace ui {
/* Default no-op binding callback. */
struct NoOp {
NoOp() {}
template <typename T>
void operator()(T) const {}
};
/* The gist of the bind functions is to bind a control
* to a value.
* bind(control, value)
*
* An optional callback can be specified which will be called
* when the control's value is changed.
* bind(control, value, [](auto new_val) { ... });
*
* Some controls need a reference to the NavigationView so
* edit dialogs (like text_prompt) can be launched.
* bind(control, value, nav, [](auto new_val) { ... });
*
* Be careful with lifetime of captured objects. Most things
* are captured by reference so the caller will need to ensure
* adequate lifetime of the referenced instances.
*/
template <typename T, typename Fn = NoOp>
void bind(Checkbox& check, T& value, Fn fn = Fn{}) {
check.set_value(value);
check.on_select = [&value, fn](Checkbox&, bool b) {
value = b;
fn(value);
};
}
template <typename T, typename Fn = NoOp>
void bind(NumberField& field, T& value, Fn fn = Fn{}) {
field.set_value(value);
field.on_change = [&value, fn](int32_t v) {
value = v;
fn(value);
};
}
template <typename T, typename Fn = NoOp>
void bind(OptionsField& field, T& value, Fn fn = Fn{}) {
field.set_by_value(static_cast<int32_t>(value));
field.on_change = [&value, fn](size_t, auto v) {
value = static_cast<T>(v);
fn(value);
};
}
template <typename T, typename Fn = NoOp>
void bind(FrequencyField& field, T& value, NavigationView& nav, Fn fn = Fn{}) {
field.set_value(value);
field.on_change = [&value, fn](rf::Frequency f) {
value = f;
fn(value);
};
field.on_edit = [&field, &value, &nav]() {
auto freq_view = nav.push<FrequencyKeypadView>(value);
freq_view->on_changed = [&field](rf::Frequency f) {
field.set_value(f);
};
};
}
template <typename T, typename Fn = NoOp>
void bind(TextField& field, T& value, NavigationView& nav, Fn fn = Fn{}) {
field.set_text(value);
field.on_change = [&value, fn](TextField& tf) {
value = tf.get_text();
fn(value);
};
// text_prompt needs a mutable working buffer.
// Capture a new string and make the lambda mutable so it can be modified.
field.on_select = [&nav, buf = std::string{}](TextField& tf) mutable {
buf = tf.get_text();
text_prompt(nav, buf, /*max_length*/ 255,
[&tf](std::string& str) {
tf.set_text(str);
});
};
}
} // namespace ui
#endif /*__BINDER_H__*/
+38
View File
@@ -5645,6 +5645,44 @@ static constexpr Bitmap bitmap_icon_shift{
{16, 16},
bitmap_icon_shift_data};
static constexpr uint8_t bitmap_icon_trim_data[] = {
0x10,
0x10,
0x30,
0x12,
0x50,
0x15,
0x50,
0x15,
0x50,
0x15,
0x52,
0x95,
0x56,
0xD5,
0x5F,
0xF5,
0x56,
0xD5,
0x52,
0x95,
0x50,
0x15,
0x50,
0x15,
0x50,
0x15,
0x90,
0x18,
0x10,
0x10,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_trim{
{16, 16},
bitmap_icon_trim_data};
} /* namespace ui */
#endif /*__BITMAP_HPP__*/
+13
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();
}
+1
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();
+82
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
};
}
@@ -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
@@ -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__*/
File diff suppressed because it is too large Load Diff
+82
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_calculator.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::calculator {
void initialize_app(ui::NavigationView& nav) {
nav.push<CalculatorView>();
}
} // namespace ui::external_app::calculator
extern "C" {
__attribute__((section(".external_app.app_calculator.application_information"), used)) application_information_t _application_information_calculator = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::calculator::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Calculator",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0xF8,
0x1F,
0xF8,
0x1F,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
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
};
}
@@ -0,0 +1,269 @@
/*
* 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_calculator.hpp"
using namespace ui;
namespace ui::external_app::calculator {
uint8_t current_key = 255;
char display_string[10];
uint8_t fgm = 0;
// interface to external code
unsigned int mp;
char CHARMAP[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'H',
'I',
'L',
'M',
'N',
'O',
'P',
'R',
'S',
'T',
'U',
'V',
'c',
's',
't',
' ',
'.',
'*',
'+',
'-',
'/',
'!',
'<',
'=',
'>',
'^',
'f',
'n',
'p',
's',
'm',
'w',
};
void printcat(uint8_t c, uint8_t x) { // Print char c at position x
display_string[x] = CHARMAP[c];
}
uint8_t getkey(void) { // Read keypad
return current_key;
}
#include "ivt.hpp"
void step() {
loop();
}
void CalculatorView::focus() {
button_F.focus();
}
CalculatorView::CalculatorView(NavigationView& nav)
: nav_{nav} {
add_children({&button_F,
&button_7,
&button_8,
&button_9,
&button_E,
&button_4,
&button_5,
&button_6,
&button_N,
&button_1,
&button_2,
&button_3,
&button_C,
&button_0,
&button_P,
&button_D,
&console});
button_F.on_select = [&nav, this](Button&) { on_button_press(0); };
button_7.on_select = [&nav, this](Button&) { on_button_press(1); };
button_8.on_select = [&nav, this](Button&) { on_button_press(2); };
button_9.on_select = [&nav, this](Button&) { on_button_press(3); };
button_E.on_select = [&nav, this](Button&) { on_button_press(4); };
button_4.on_select = [&nav, this](Button&) { on_button_press(5); };
button_5.on_select = [&nav, this](Button&) { on_button_press(6); };
button_6.on_select = [&nav, this](Button&) { on_button_press(7); };
button_N.on_select = [&nav, this](Button&) { on_button_press(8); };
button_1.on_select = [&nav, this](Button&) { on_button_press(9); };
button_2.on_select = [&nav, this](Button&) { on_button_press(10); };
button_3.on_select = [&nav, this](Button&) { on_button_press(11); };
button_C.on_select = [&nav, this](Button&) { on_button_press(12); };
button_0.on_select = [&nav, this](Button&) { on_button_press(13); };
button_P.on_select = [&nav, this](Button&) { on_button_press(14); };
button_D.on_select = [&nav, this](Button&) { on_button_press(15); };
}
void CalculatorView::on_button_press(uint8_t button) {
auto pre_fgm = fgm;
for (int i = 0; i < 10; i++)
display_string[i] = ' ';
current_key = button;
step();
do {
current_key = 255;
step();
} while (mp);
std::string d(&display_string[0], 10);
if (pre_fgm && button != 0) {
console.write(" ");
switch (button) {
case 1:
console.write("SUM+");
break;
case 2:
console.write("PRG");
break;
case 3:
console.write("/");
break;
case 4:
console.write("SWAP");
break;
case 5:
console.write("DICT");
break;
case 6:
console.write("USR");
break;
case 7:
console.write("*");
break;
case 8:
console.write("ROT");
break;
case 9:
console.write("RCL");
break;
case 10:
console.write("STO");
break;
case 11:
console.write("-");
break;
case 12:
console.write("CA");
break;
case 13:
console.write("PI");
break;
case 14:
console.write("INT");
break;
case 15:
console.write("+");
break;
}
console.writeln(d);
} else if (button == 15) {
console.write("\r");
console.writeln(d);
} else {
console.write("\r");
console.write(d);
}
update_button_labels();
}
void CalculatorView::update_button_labels() {
if (fgm) {
button_F.set_text("MENU");
button_7.set_text("SUM+");
button_8.set_text("PRG");
button_9.set_text("/");
button_E.set_text("SWAP");
button_4.set_text("DICT");
button_5.set_text("USR");
button_6.set_text("*");
button_N.set_text("ROT");
button_1.set_text("RCL");
button_2.set_text("STO");
button_3.set_text("-");
button_C.set_text("CA");
button_0.set_text("PI");
button_P.set_text("INT");
button_D.set_text("+");
} else {
button_F.set_text("F");
button_7.set_text("7");
button_8.set_text("8");
button_9.set_text("9");
button_E.set_text("E");
button_4.set_text("4");
button_5.set_text("5");
button_6.set_text("6");
button_N.set_text("N");
button_1.set_text("1");
button_2.set_text("2");
button_3.set_text("3");
button_C.set_text("C");
button_0.set_text("0");
button_P.set_text(".");
button_D.set_text("D");
}
}
} // namespace ui::external_app::calculator
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* 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_calculator_H__
#define __UI_calculator_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
using namespace ui;
namespace ui::external_app::calculator {
class CalculatorView : public View {
public:
CalculatorView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Calculator"; };
private:
NavigationView& nav_;
Button button_F{{0 * 8, 11 * 16, 6 * 8, 16 + 8}, "F"};
Button button_7{{8 * 8, 11 * 16, 6 * 8, 16 + 8}, "7"};
Button button_8{{16 * 8, 11 * 16, 6 * 8, 16 + 8}, "8"};
Button button_9{{24 * 8, 11 * 16, 6 * 8, 16 + 8}, "9"};
Button button_E{{0 * 8, 13 * 16, 6 * 8, 16 + 8}, "E"};
Button button_4{{8 * 8, 13 * 16, 6 * 8, 16 + 8}, "4"};
Button button_5{{16 * 8, 13 * 16, 6 * 8, 16 + 8}, "5"};
Button button_6{{24 * 8, 13 * 16, 6 * 8, 16 + 8}, "6"};
Button button_N{{0 * 8, 15 * 16, 6 * 8, 16 + 8}, "N"};
Button button_1{{8 * 8, 15 * 16, 6 * 8, 16 + 8}, "1"};
Button button_2{{16 * 8, 15 * 16, 6 * 8, 16 + 8}, "2"};
Button button_3{{24 * 8, 15 * 16, 6 * 8, 16 + 8}, "3"};
Button button_C{{0 * 8, 17 * 16, 6 * 8, 16 + 8}, "C"};
Button button_0{{8 * 8, 17 * 16, 6 * 8, 16 + 8}, "0"};
Button button_P{{16 * 8, 17 * 16, 6 * 8, 16 + 8}, "."};
Button button_D{{24 * 8, 17 * 16, 6 * 8, 16 + 8}, "D"};
Console console{
{0 * 8, 0 * 16, screen_width, 10 * 16}};
void on_button_press(uint8_t button);
void update_button_labels();
};
} // namespace ui::external_app::calculator
#endif /*__UI_calculator_H__*/
+20
View File
@@ -0,0 +1,20 @@
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
#calculator
external/calculator/main.cpp
external/calculator/ui_calculator.cpp
)
set(EXTAPPLIST
pacman
afsk_rx
calculator
)
+44
View File
@@ -0,0 +1,44 @@
/*
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 = 32k
ram_external_app_afsk_rx (rwx) : org = 0xEEEA0000, len = 32k
ram_external_app_calculator (rwx) : org = 0xEEEB0000, len = 32k
}
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));
*(*ui*external_app*afsk_rx*);
} > ram_external_app_afsk_rx
.external_app_calculator : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_calculator.application_information));
*(*ui*external_app*calculator*);
} > ram_external_app_calculator
}
+66
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
+82
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
+50
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
+59
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__*/
+8
View File
@@ -325,6 +325,14 @@ FATTimestamp file_created_date(const std::filesystem::path& file_path) {
return {filinfo.fdate, filinfo.ftime};
}
std::filesystem::filesystem_error file_update_date(const std::filesystem::path& file_path, FATTimestamp timestamp) {
FILINFO filinfo{};
filinfo.fdate = timestamp.FAT_date;
filinfo.ftime = timestamp.FAT_time;
return f_utime(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
}
std::filesystem::filesystem_error make_new_file(
const std::filesystem::path& file_path) {
File f;
+1
View File
@@ -270,6 +270,7 @@ std::filesystem::filesystem_error rename_file(const std::filesystem::path& file_
std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_path, const std::filesystem::path& dest_path);
FATTimestamp file_created_date(const std::filesystem::path& file_path);
std::filesystem::filesystem_error file_update_date(const std::filesystem::path& file_path, FATTimestamp timestamp);
std::filesystem::filesystem_error make_new_file(const std::filesystem::path& file_path);
std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path);
std::filesystem::filesystem_error ensure_directory(const std::filesystem::path& dir_path);
+22 -4
View File
@@ -122,7 +122,7 @@ void MAX2837::init() {
_map.r.lpf_1.LPF_EN = 1; /* Enable low-pass filter */
_map.r.lpf_1.ModeCtrl = 0b01; /* Rx LPF */
_map.r.lpf_1.FT = 0b0000; /* 5MHz LPF */
_map.r.lpf_1.FT = 0b0000; /* 1,75MHz LPF */
_map.r.spi_en.EN_SPI = 1; /* enable chip functions when ENABLE pin set */
@@ -233,10 +233,28 @@ void MAX2837::set_vga_gain(const int_fast8_t db) {
flush();
}
void MAX2837::set_lpf_rf_bandwidth(const uint32_t bandwidth_minimum) {
void MAX2837::set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) {
_map.r.lpf_1.ModeCtrl = 0b01; /* Address reg 2, D3-D2, Set mode lowpass filter block to Rx LPF . Active when Address 6 D<9> = 1 */
_map.r.lpf_1.FT = filter::bandwidth_ordinal(bandwidth_minimum);
_dirty[Register::LPF_1] = 1;
flush();
flush_one(Register::LPF_1);
_map.r.vga_3_rx_top.LPF_MODE_SEL = 1; /* Address 6 reg, D9 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 2 D3:D2*/
flush_one(Register::VGA_3_RX_TOP);
_map.r.vga_3_rx_top.LPF_MODE_SEL = 0; /* Leave LPF_MODE_SEL 0 = Normal operation */
flush_one(Register::VGA_3_RX_TOP);
}
void MAX2837::set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum) {
_map.r.lpf_1.ModeCtrl = 0b10; /* Address 2 reg, D3-D2, Set mode lowpass filter block to Tx LPF . Active when Address 6 D<9> = 1 */
_map.r.lpf_1.FT = filter::bandwidth_ordinal(bandwidth_minimum);
flush_one(Register::LPF_1);
_map.r.vga_3_rx_top.LPF_MODE_SEL = 1; /* Address 6 reg, D9 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 2 D3:D2*/
flush_one(Register::VGA_3_RX_TOP);
_map.r.vga_3_rx_top.LPF_MODE_SEL = 0; /* Leave LPF_MODE_SEL 0 = Normal operation */
flush_one(Register::VGA_3_RX_TOP);
}
bool MAX2837::set_frequency(const rf::Frequency lo_frequency) {
+2 -1
View File
@@ -783,7 +783,8 @@ class MAX2837 : public MAX283x {
void set_tx_vga_gain(const int_fast8_t db) override;
void set_lna_gain(const int_fast8_t db) override;
void set_vga_gain(const int_fast8_t db) override;
void set_lpf_rf_bandwidth(const uint32_t bandwidth_minimum) override;
void set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) override;
void set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum) override;
#if 0
void rx_cal() {
_map.r.spi_en.EN_SPI = 1;
+25 -3
View File
@@ -278,10 +278,32 @@ void MAX2839::set_vga_gain(const int_fast8_t db) {
configure_rx_gain();
}
void MAX2839::set_lpf_rf_bandwidth(const uint32_t bandwidth_minimum) {
void MAX2839::set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) {
_map.r.lpf_vga_1.ModeCtrl = 0b01; /* Address reg 5, D9-D8, Set mode lowpass filter block to Rx LPF . Active when Address 8 D<2> = 1 */
flush_one(Register::LPF_VGA_1);
_map.r.rx_top_1.LPF_MODE_SEL = 1; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one(Register::RX_TOP_1);
_map.r.lpf.FT = filter::bandwidth_ordinal(bandwidth_minimum);
_dirty[Register::LPF] = 1;
flush();
flush_one(Register::LPF);
_map.r.rx_top_1.LPF_MODE_SEL = 0; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one(Register::RX_TOP_1); /* Leave LPF_MODE_SEL 0 = Normal operation */
}
void MAX2839::set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum) {
_map.r.lpf_vga_1.ModeCtrl = 0b10; /* Address reg 5, D9-D8, Set mode lowpass filter block to Tx LPF . Active when Address 8 D<2> = 1 */
flush_one(Register::LPF_VGA_1);
_map.r.rx_top_1.LPF_MODE_SEL = 1; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one(Register::RX_TOP_1);
_map.r.lpf.FT = filter::bandwidth_ordinal(bandwidth_minimum);
flush_one(Register::LPF);
_map.r.rx_top_1.LPF_MODE_SEL = 0; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one(Register::RX_TOP_1); /* Leave LPF_MODE_SEL 0 = Normal operation */
}
bool MAX2839::set_frequency(const rf::Frequency lo_frequency) {
+2 -1
View File
@@ -686,7 +686,8 @@ class MAX2839 : public MAX283x {
void set_tx_vga_gain(const int_fast8_t db) override;
void set_lna_gain(const int_fast8_t db) override;
void set_vga_gain(const int_fast8_t db) override;
void set_lpf_rf_bandwidth(const uint32_t bandwidth_minimum) override;
void set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum) override;
void set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum) override;
bool set_frequency(const rf::Frequency lo_frequency) override;
void set_rx_lo_iq_calibration(const size_t v) override;
void set_rx_buff_vcm(const size_t v) override;
+2 -1
View File
@@ -123,7 +123,8 @@ class MAX283x {
virtual void set_tx_vga_gain(const int_fast8_t db);
virtual void set_lna_gain(const int_fast8_t db);
virtual void set_vga_gain(const int_fast8_t db);
virtual void set_lpf_rf_bandwidth(const uint32_t bandwidth_minimum);
virtual void set_lpf_rf_bandwidth_rx(const uint32_t bandwidth_minimum);
virtual void set_lpf_rf_bandwidth_tx(const uint32_t bandwidth_minimum);
virtual bool set_frequency(const rf::Frequency lo_frequency);
+6 -2
View File
@@ -232,8 +232,12 @@ void set_tx_gain(const int_fast8_t db) {
second_if->set_tx_vga_gain(db);
}
void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum) {
second_if->set_lpf_rf_bandwidth(bandwidth_minimum);
void set_baseband_filter_bandwidth_rx(const uint32_t bandwidth_minimum) {
second_if->set_lpf_rf_bandwidth_rx(bandwidth_minimum);
}
void set_baseband_filter_bandwidth_tx(const uint32_t bandwidth_minimum) {
second_if->set_lpf_rf_bandwidth_tx(bandwidth_minimum);
}
void set_baseband_rate(const uint32_t rate) {
+2 -1
View File
@@ -50,7 +50,8 @@ void set_rf_amp(const bool rf_amp);
void set_lna_gain(const int_fast8_t db);
void set_vga_gain(const int_fast8_t db);
void set_tx_gain(const int_fast8_t db);
void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum);
void set_baseband_filter_bandwidth_rx(const uint32_t bandwidth_minimum);
void set_baseband_filter_bandwidth_tx(const uint32_t bandwidth_minimum);
void set_baseband_rate(const uint32_t rate);
void set_antenna_bias(const bool on);
+1 -1
View File
@@ -270,7 +270,7 @@ void ReceiverModel::update_tuning_frequency() {
}
void ReceiverModel::update_baseband_bandwidth() {
radio::set_baseband_filter_bandwidth(baseband_bandwidth());
radio::set_baseband_filter_bandwidth_rx(baseband_bandwidth());
}
void ReceiverModel::update_sampling_rate() {
+1 -1
View File
@@ -139,7 +139,7 @@ void TransmitterModel::update_tuning_frequency() {
}
void TransmitterModel::update_baseband_bandwidth() {
radio::set_baseband_filter_bandwidth(baseband_bandwidth());
radio::set_baseband_filter_bandwidth_tx(baseband_bandwidth());
}
void TransmitterModel::update_sampling_rate() {
+6
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;
+1
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();
@@ -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
@@ -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__*/
+66 -7
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"
@@ -159,6 +159,8 @@ SystemStatusView::SystemStatusView(
&status_icons,
});
rtc_battery_workaround();
if (pmem::should_use_sdcard_for_pmem()) {
pmem::load_persistent_settings_from_file();
}
@@ -360,6 +362,55 @@ void SystemStatusView::on_title() {
nav_.pop();
}
void SystemStatusView::rtc_battery_workaround() {
if (sd_card::status() != sd_card::Status::Mounted)
return;
uint16_t year;
uint8_t month;
uint8_t day;
FATTimestamp timestamp;
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
// if year is 0000, assume RTC battery is dead
if (datetime.year() == 0) {
// if timestamp file is present, use it's date and add 1 day
if (std::filesystem::file_exists(DATE_FILEFLAG)) {
timestamp = file_created_date(DATE_FILEFLAG);
year = (timestamp.FAT_date >> 9) + 1980;
month = (timestamp.FAT_date >> 5) & 0xF;
day = timestamp.FAT_date & 0x1F;
// bump to next month at 28 days for simplicity
if (++day > 28) {
day = 1;
if (++month > 12) {
month = 1;
++year;
}
}
} else {
make_new_file(DATE_FILEFLAG);
year = 1980;
month = 1;
day = 1;
}
// update RTC (keeps ticking while powered on regardless of RTC battery condition)
rtc::RTC new_datetime{year, month, day, datetime.hour(), datetime.minute(), datetime.second()};
rtcSetTime(&RTCD1, &new_datetime);
// update file date
timestamp.FAT_date = ((year - 1980) << 9) | ((uint16_t)month << 5) | day;
timestamp.FAT_time = 0;
file_update_date(DATE_FILEFLAG, timestamp);
}
}
/* Information View *****************************************************/
InformationView::InformationView(
@@ -376,11 +427,8 @@ InformationView::InformationView(
&ltime});
version.set_style(&style_infobar);
ltime.set_hide_clock(pmem::hide_clock());
ltime.set_style(&style_infobar);
ltime.set_seconds_enabled(true);
ltime.set_date_enabled(pmem::clock_with_date());
refresh();
set_dirty();
}
@@ -492,7 +540,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 +559,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 +591,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 *****************************************************/
@@ -553,7 +608,7 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
{"File Manager", Color::green(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
{"Freq. Manager", Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
{"Notepad", Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
{"IQ Trim", Color::orange(), &bitmap_icon_cut, [&nav]() { nav.push<IQTrimView>(); }},
{"IQ Trim", Color::orange(), &bitmap_icon_trim, [&nav]() { nav.push<IQTrimView>(); }},
{"SD Over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
{"Signal Gen", Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
// {"Test App", Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); }},
@@ -565,6 +620,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
}
+5 -1
View File
@@ -44,6 +44,9 @@
#include <vector>
#include <utility>
// for incrementing fake date when RTC battery is dead
#define DATE_FILEFLAG u"/SETTINGS/DATE_FILEFLAG"
using namespace sd_card;
namespace ui {
@@ -79,6 +82,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 +111,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.
@@ -235,6 +238,7 @@ class SystemStatusView : public View {
void on_title();
void refresh();
void on_clk();
void rtc_battery_workaround();
MessageHandlerRegistration message_handler_refresh{
Message::ID::StatusRefresh,
+50 -25
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
)
@@ -33,6 +33,9 @@ __ram_end__ = __ram_start__ + __ram_size__;
ENTRY(ResetHandler)
/* external apps */
INCLUDE external.ld
SECTIONS
{
. = 0;
@@ -63,7 +66,7 @@ SECTIONS
PROVIDE(__fini_array_end = .);
} > flash
.text : ALIGN(16) SUBALIGN(16)
.text : ALIGN(4) SUBALIGN(4)
{
*(.text.startup.*)
*(.text)
@@ -142,7 +145,7 @@ SECTIONS
*(COMMON)
. = ALIGN(4);
PROVIDE(_bss_end = .);
} > ram
} > ram
}
PROVIDE(end = .);
@@ -61,7 +61,7 @@ SECTIONS
PROVIDE(__fini_array_end = .);
} > flash
.text : ALIGN(16) SUBALIGN(16)
.text : ALIGN(4) SUBALIGN(4)
{
*(.text.startup.*)
*(.text)
+54
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__*/
+1 -1
View File
@@ -47,7 +47,7 @@
#define _USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define _USE_CHMOD 0
#define _USE_CHMOD 1
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */
+25 -3
View File
@@ -28,6 +28,7 @@
#include <cstddef>
#include <algorithm>
#include "irq_controls.hpp"
#include "string_format.hpp"
using namespace portapack;
@@ -625,6 +626,8 @@ void Console::write(std::string message) {
} else {
if (c == '\n') {
crlf();
} else if (c == '\r') {
pos = {0, pos.y()};
} else if (c == '\x1B') {
escape = true;
} else {
@@ -1730,9 +1733,15 @@ bool TextEdit::on_key(const KeyEvent key) {
cursor_pos_--;
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
cursor_pos_++;
else if (key == KeyEvent::Select)
insert_mode_ = !insert_mode_;
else
else if (key == KeyEvent::Select) {
if (key_is_long_pressed(key)) {
// Delete text to the cursor.
text_ = text_.substr(cursor_pos_);
set_cursor(0);
} else {
insert_mode_ = !insert_mode_;
}
} else
return false;
set_dirty();
@@ -1760,6 +1769,19 @@ bool TextEdit::on_touch(const TouchEvent event) {
return true;
}
void TextEdit::on_focus() {
// Enable long press on "Select".
SwitchesState config;
config[toUType(Switch::Sel)] = true;
set_switches_long_press_config(config);
}
void TextEdit::on_blur() {
// Reset long press.
SwitchesState config{};
set_switches_long_press_config(config);
}
/* TextField *************************************************************/
TextField::TextField(Rect parent_rect, std::string text)
+3
View File
@@ -689,6 +689,9 @@ class TextEdit : public Widget {
bool on_encoder(const EncoderEvent delta) override;
bool on_touch(const TouchEvent event) override;
void on_focus() override;
void on_blur() override;
protected:
std::string& text_;
size_t max_length_;
Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

+53
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
+131
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 = 32*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 32kb: {} 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))