mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-21 06:59:02 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed834e3553 | |||
| ce08d93ae2 | |||
| 13fd1b1f3b | |||
| d04c781ada | |||
| 24605777a6 | |||
| b5ac792853 | |||
| 44c319dcc6 | |||
| a442971b81 | |||
| 8b5adb6bc1 | |||
| c3add0ce84 | |||
| 8725b01995 | |||
| 936e8279f9 | |||
| 6193023c8f | |||
| 1800939833 | |||
| 2f48fc2ef9 | |||
| 918ec0574f | |||
| 1139b22141 | |||
| 7598c25fc8 | |||
| 0f6a9277a8 | |||
| 6a8650d484 | |||
| 46d9e02684 | |||
| 367479d163 | |||
| bbbf5a37c8 | |||
| b2ad1fa979 | |||
| 998be5ba09 | |||
| 9e8d279298 | |||
| fd03bb800c | |||
| f80c28cde7 | |||
| b7e5bedf4b | |||
| 994e426d1e | |||
| c5bf0d94f9 | |||
| 8ad9ada015 | |||
| e854261124 | |||
| 0370b4eb55 | |||
| 5eef5b4e31 | |||
| 61d9c051de | |||
| 5ea1bff1e6 | |||
| bc035cff6a | |||
| f2c008602d | |||
| b8073bca0f | |||
| c30a61441b | |||
| f0f279eec5 | |||
| 464cc8449e | |||
| ee2e57d702 | |||
| a2a5fb166e | |||
| f59f5dfaa3 | |||
| 13f12227bd | |||
| c9db21063b | |||
| 125184f300 | |||
| 8068517808 | |||
| a4f6bbda5e | |||
| 8b2598fdac | |||
| c36fe78bd5 | |||
| bd8385464e | |||
| 8c996b5bc6 | |||
| d1901939e1 | |||
| 41ec11fb66 | |||
| df47490d85 | |||
| a2f6d14b15 | |||
| 6a6c6d6502 | |||
| 2d98c5d311 | |||
| 03b13f8ab0 | |||
| 6e74ad942f | |||
| 0f85f247b6 |
@@ -71,11 +71,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py)
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "content=$CHANGELOG" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
|
||||
@@ -55,11 +55,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "content=$CHANGELOG" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.9.0
|
||||
v1.9.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.9.1
|
||||
v2.0.0
|
||||
|
||||
+3
-2
@@ -55,7 +55,7 @@ CMakeFiles/
|
||||
# Debugging
|
||||
.gdbinit*
|
||||
|
||||
# Editor/IDE files
|
||||
# Editor/ IDE files
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode
|
||||
@@ -68,8 +68,9 @@ CMakeFiles/
|
||||
.DS_Store
|
||||
/firmware/CMakeCache.txt
|
||||
|
||||
# Python env
|
||||
# Python env/ venv
|
||||
env/
|
||||
venv/
|
||||
|
||||
# Other
|
||||
*.bak
|
||||
|
||||
@@ -72,5 +72,12 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
|
||||
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
|
||||
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
|
||||
|
||||
find_program(CCACHE "ccache")
|
||||
if(CCACHE)
|
||||
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
|
||||
set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
|
||||
endif(CCACHE)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(firmware)
|
||||
|
||||
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
|
||||
@@ -19,9 +19,5 @@ RUN apk add --no-cache g++ gcc clang clang-static clang-dev llvm-dev llvm-static
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin && cd ~/bin && \
|
||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
||||
|
||||
CMD cd .. && cd build && \
|
||||
cmake .. && make ppfw
|
||||
|
||||
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ if(cpp20_supported)
|
||||
else()
|
||||
set(USE_CPPOPT "-std=c++17")
|
||||
endif()
|
||||
set(USE_CPPOPT "${USE_CPPOPT} -flto -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
|
||||
set(USE_CPPOPT "${USE_CPPOPT} -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
|
||||
|
||||
# Enable this if you want the linker to remove unused code and data
|
||||
set(USE_LINK_GC yes)
|
||||
@@ -52,7 +52,7 @@ set(USE_LINK_GC yes)
|
||||
# Linker extra options here.
|
||||
set(USE_LDOPT)
|
||||
|
||||
# Enable this if you want link time optimizations (LTO)
|
||||
# Enable this if you want link time optimizations (LTO) - this flag affects chibios only
|
||||
set(USE_LTO no)
|
||||
|
||||
# If enabled, this option allows to compile the application in THUMB mode.
|
||||
@@ -293,7 +293,7 @@ set(CPPSRC
|
||||
# apps/ui_jammer.cpp
|
||||
# apps/ui_keyfob.cpp
|
||||
# apps/ui_lcr.cpp
|
||||
apps/ui_level.cpp
|
||||
apps/ui_level.cpp
|
||||
apps/ui_looking_glass_app.cpp
|
||||
apps/ui_mictx.cpp
|
||||
apps/ui_modemsetup.cpp
|
||||
@@ -304,7 +304,7 @@ set(CPPSRC
|
||||
apps/ui_pocsag_tx.cpp
|
||||
apps/ui_rds.cpp
|
||||
apps/ui_recon_settings.cpp
|
||||
apps/ui_recon.cpp
|
||||
apps/ui_recon.cpp
|
||||
apps/ui_remote.cpp
|
||||
apps/ui_scanner.cpp
|
||||
apps/ui_sd_over_usb.cpp
|
||||
@@ -347,10 +347,12 @@ set(CPPSRC
|
||||
${HACKRF_CPLD_DATA_CPP}
|
||||
ui_external_items_menu_loader.cpp
|
||||
view_factory_base.cpp
|
||||
|
||||
${EXTCPPSRC}
|
||||
)
|
||||
|
||||
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
|
||||
|
||||
list (APPEND CPPSRC ${EXTCPPSRC}) # Append external sources after setting lto flag to internal ones
|
||||
|
||||
# C sources to be compiled in ARM mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
|
||||
@@ -113,6 +113,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
||||
namespace app_settings {
|
||||
|
||||
enum class Mode : uint8_t {
|
||||
NO_RF = 0x00,
|
||||
RX = 0x01,
|
||||
TX = 0x02,
|
||||
RX_TX = 0x03, // Both TX/RX
|
||||
|
||||
@@ -64,9 +64,9 @@ static std::string mmsi(
|
||||
|
||||
static std::string mid(
|
||||
const ais::MMSI& mmsi) {
|
||||
std::database db;
|
||||
database db;
|
||||
std::string mid_code = "";
|
||||
std::database::MidDBRecord mid_record = {};
|
||||
database::MidDBRecord mid_record = {};
|
||||
int return_code = 0;
|
||||
|
||||
// Try getting the country name from mids.db using MID code for given MMSI
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -51,7 +52,7 @@ void SoundBoardView::stop() {
|
||||
|
||||
void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
stop();
|
||||
// progressbar.set_value(0);
|
||||
progressbar.set_value(0);
|
||||
|
||||
if (return_code == ReplayThread::END_OF_FILE) {
|
||||
if (check_random.value()) {
|
||||
@@ -80,9 +81,16 @@ void SoundBoardView::file_error() {
|
||||
}
|
||||
|
||||
void SoundBoardView::start_tx(const uint32_t id) {
|
||||
if (file_list.empty()) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits_per_sample;
|
||||
|
||||
stop();
|
||||
|
||||
@@ -93,11 +101,12 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
playing_id = id;
|
||||
|
||||
// progressbar.set_max(reader->sample_count());
|
||||
progressbar.set_max(reader->sample_count());
|
||||
|
||||
// button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
sample_rate = reader->sample_rate();
|
||||
bits_per_sample = reader->bits_per_sample();
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
@@ -113,12 +122,13 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
1536000 / 20, // Update vu-meter at 20Hz
|
||||
transmitter_model.channel_bandwidth(),
|
||||
0, // Gain is unused
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
|
||||
0, // AM
|
||||
0, // DSB
|
||||
0, // USB
|
||||
0 // LSB
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
bits_per_sample,
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
|
||||
@@ -126,7 +136,8 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
tx_view.set_transmitting(true);
|
||||
|
||||
audio::output::start();
|
||||
if (tone_key_index == 0)
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
/*void SoundBoardView::show_infos() {
|
||||
@@ -138,8 +149,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
}*/
|
||||
|
||||
void SoundBoardView::on_tx_progress(const uint32_t progress) {
|
||||
(void)progress; // avoid warning
|
||||
// progressbar.set_value(progress);
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
void SoundBoardView::on_select_entry() {
|
||||
@@ -164,7 +174,7 @@ void SoundBoardView::refresh_list() {
|
||||
|
||||
if (entry_extension == ".WAV") {
|
||||
if (reader->open(u"/WAV/" + entry.path().native())) {
|
||||
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) {
|
||||
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
|
||||
// sounds[c].ms_duration = reader->ms_duration();
|
||||
// sounds[c].path = u"WAV/" + entry.path().native();
|
||||
if (count >= (page - 1) * 100 && count < page * 100) {
|
||||
@@ -203,7 +213,7 @@ void SoundBoardView::refresh_list() {
|
||||
|
||||
for (size_t n = 0; n < file_list.size(); n++) {
|
||||
menu_view.add_item({file_list[n].string().substr(0, 30),
|
||||
ui::Color::white(),
|
||||
ui::Color::dark_magenta(),
|
||||
nullptr,
|
||||
[this](KeyEvent) {
|
||||
on_select_entry();
|
||||
@@ -230,8 +240,9 @@ SoundBoardView::SoundBoardView(
|
||||
&options_tone_key,
|
||||
//&text_title,
|
||||
//&text_duration,
|
||||
//&progressbar,
|
||||
&progressbar,
|
||||
&field_volume,
|
||||
&text_volume_disabled,
|
||||
&page_info,
|
||||
&check_loop,
|
||||
&check_random,
|
||||
@@ -257,6 +268,13 @@ SoundBoardView::SoundBoardView(
|
||||
tone_keys_populate(options_tone_key);
|
||||
options_tone_key.set_selected_index(0);
|
||||
|
||||
text_volume_disabled.hidden(true);
|
||||
options_tone_key.on_change = [this](size_t index, OptionsField::value_t) {
|
||||
bool tone_key_enabled = (index != 0);
|
||||
text_volume_disabled.hidden(!tone_key_enabled);
|
||||
field_volume.hidden(tone_key_enabled);
|
||||
};
|
||||
|
||||
check_loop.set_value(false);
|
||||
check_random.set_value(false);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -103,7 +104,7 @@ class SoundBoardView : public View {
|
||||
"<="};
|
||||
|
||||
Text page_info{
|
||||
{0, 30 * 8 - 4, 30 * 8, 16}};
|
||||
{0, 29 * 8, 30 * 8, 16}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 0, 240, 175},
|
||||
@@ -128,6 +129,9 @@ class SoundBoardView : public View {
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 180}};
|
||||
Text text_volume_disabled{
|
||||
{28 * 8, 180, 3 * 8, 16},
|
||||
"--"};
|
||||
|
||||
Checkbox check_loop{
|
||||
{0, 25 * 8 + 4},
|
||||
@@ -139,9 +143,8 @@ class SoundBoardView : public View {
|
||||
6,
|
||||
"Random"};
|
||||
|
||||
// ProgressBar progressbar {
|
||||
// { 0 * 8, 30 * 8 - 4, 30 * 8, 16 }
|
||||
// };
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -127,8 +128,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
||||
text_icao_address.set(entry.icao_str);
|
||||
|
||||
// Try getting the aircraft information from icao24.db
|
||||
std::database db{};
|
||||
std::database::AircraftDBRecord aircraft_record;
|
||||
database db{};
|
||||
database::AircraftDBRecord aircraft_record;
|
||||
auto return_code = db.retrieve_aircraft_record(&aircraft_record, entry.icao_str);
|
||||
switch (return_code) {
|
||||
case DATABASE_RECORD_FOUND:
|
||||
@@ -199,6 +200,10 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
||||
}
|
||||
break;
|
||||
|
||||
case DATABASE_RECORD_NOT_FOUND:
|
||||
// Defaults should be filled by the constructor
|
||||
break;
|
||||
|
||||
case DATABASE_NOT_FOUND:
|
||||
text_manufacturer.set("No icao24.db file");
|
||||
break;
|
||||
@@ -233,24 +238,6 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
&button_aircraft_details,
|
||||
&button_see_map});
|
||||
|
||||
// The following won't change for a given airborne aircraft.
|
||||
// Try getting the airline's name from airlines.db.
|
||||
// NB: Only works once callsign has been read and won't be updated.
|
||||
std::database db;
|
||||
std::database::AirlinesDBRecord airline_record;
|
||||
std::string airline_code = entry_.callsign.substr(0, 3);
|
||||
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
|
||||
|
||||
switch (return_code) {
|
||||
case DATABASE_RECORD_FOUND:
|
||||
text_airline.set(airline_record.airline);
|
||||
text_country.set(airline_record.country);
|
||||
break;
|
||||
case DATABASE_NOT_FOUND:
|
||||
text_airline.set("No airlines.db file");
|
||||
break;
|
||||
}
|
||||
|
||||
text_icao_address.set(entry_.icao_str);
|
||||
|
||||
button_aircraft_details.on_select = [this, &nav](Button&) {
|
||||
@@ -330,6 +317,31 @@ void ADSBRxDetailsView::on_orientation(const OrientationDataMessage* msg) {
|
||||
}
|
||||
|
||||
void ADSBRxDetailsView::refresh_ui() {
|
||||
// The following won't change for a given airborne aircraft.
|
||||
// Try getting the airline's name from airlines.db.
|
||||
if (!airline_checked && !entry_.callsign.empty()) {
|
||||
airline_checked = true;
|
||||
|
||||
database db;
|
||||
database::AirlinesDBRecord airline_record;
|
||||
std::string airline_code = entry_.callsign.substr(0, 3);
|
||||
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
|
||||
|
||||
switch (return_code) {
|
||||
case DATABASE_RECORD_FOUND:
|
||||
text_airline.set(airline_record.airline);
|
||||
text_country.set(airline_record.country);
|
||||
break;
|
||||
case DATABASE_RECORD_NOT_FOUND:
|
||||
// text_airline.set("-"); // It's what it is constructed with
|
||||
// text_country.set("-"); // It's what it is constructed with
|
||||
break;
|
||||
case DATABASE_NOT_FOUND:
|
||||
text_airline.set("No airlines.db file");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto age = entry_.age;
|
||||
if (age < 60)
|
||||
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -280,6 +281,7 @@ class ADSBRxDetailsView : public View {
|
||||
// NB: Keeping a copy so that it doesn't end up dangling
|
||||
// if removed from the recent entries list.
|
||||
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
|
||||
bool airline_checked{false};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
|
||||
|
||||
@@ -101,6 +101,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 3) {
|
||||
field_frequency.set_value(144575000);
|
||||
} else if (i == 4) {
|
||||
field_frequency.set_value(145825000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -221,7 +221,8 @@ class APRSRxView : public View {
|
||||
{{"NA ", 0},
|
||||
{"EUR", 1},
|
||||
{"AUS", 2},
|
||||
{"NZ ", 3}}};
|
||||
{"NZ ", 3},
|
||||
{"ISS", 4}}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -322,6 +323,11 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ControlsSwitchesWidget::on_encoder(const EncoderEvent delta) {
|
||||
last_delta = delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
const auto pos = screen_pos();
|
||||
|
||||
@@ -404,6 +410,8 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
|
||||
|
||||
switches_event >>= 1;
|
||||
}
|
||||
|
||||
painter.draw_string({5 * 8, 12 * 16}, Styles::light_grey, to_string_dec_int(last_delta, 3));
|
||||
}
|
||||
|
||||
void ControlsSwitchesWidget::on_frame_sync() {
|
||||
@@ -449,6 +457,17 @@ DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
/* DebugReboot **********************************************/
|
||||
|
||||
DebugReboot::DebugReboot(NavigationView& nav) {
|
||||
(void)nav;
|
||||
|
||||
LPC_RGU->RESET_CTRL[0] = (1 << 0);
|
||||
|
||||
while (1)
|
||||
__WFE();
|
||||
}
|
||||
|
||||
/* DebugMenuView *********************************************************/
|
||||
|
||||
DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
@@ -464,6 +483,7 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
|
||||
{"Pers. Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
|
||||
//{ "Radio State", ui::Color::white(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{"Reboot", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<DebugReboot>(); }},
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
|
||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
|
||||
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
|
||||
@@ -552,6 +572,7 @@ void DebugPmemView::update() {
|
||||
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
}
|
||||
|
||||
bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
@@ -561,10 +582,10 @@ bool DebugScreenTest::on_key(const KeyEvent key) {
|
||||
nav_.pop();
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
|
||||
painter.fill_rectangle({0, 0, screen_width, screen_height}, std::rand());
|
||||
break;
|
||||
case KeyEvent::Left:
|
||||
pen_color = semirand();
|
||||
pen_color = std::rand();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -584,17 +605,10 @@ bool DebugScreenTest::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t DebugScreenTest::semirand() {
|
||||
static uint64_t seed{0x0102030405060708};
|
||||
seed = seed * 137;
|
||||
seed = (seed >> 1) | ((seed & 0x01) << 63);
|
||||
return (uint16_t)seed;
|
||||
}
|
||||
|
||||
void DebugScreenTest::paint(Painter& painter) {
|
||||
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Color::white());
|
||||
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
|
||||
pen_color = semirand();
|
||||
pen_color = std::rand();
|
||||
}
|
||||
|
||||
/* DebugLCRView *******************************************************/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -251,18 +252,21 @@ class ControlsSwitchesWidget : public Widget {
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect},
|
||||
key_event_mask(0),
|
||||
long_press_key_event_mask{0} {
|
||||
long_press_key_event_mask{0},
|
||||
last_delta{0} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
void on_show() override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
uint8_t key_event_mask;
|
||||
uint8_t long_press_key_event_mask;
|
||||
EncoderEvent last_delta;
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
@@ -284,6 +288,7 @@ class DebugControlsView : public View {
|
||||
private:
|
||||
Labels labels{
|
||||
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
|
||||
{{0 * 8, 11 * 16}, "Dial:", Color::grey()},
|
||||
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
|
||||
|
||||
ControlsSwitchesWidget switches_widget{
|
||||
@@ -385,7 +390,6 @@ class DebugScreenTest : public View {
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
uint16_t semirand();
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
@@ -420,6 +424,11 @@ class DebugPeripheralsMenuView : public BtnGridView {
|
||||
std::string title() const override { return "Peripherals"; };
|
||||
};
|
||||
|
||||
class DebugReboot : public BtnGridView {
|
||||
public:
|
||||
DebugReboot(NavigationView& nav);
|
||||
};
|
||||
|
||||
class DebugMenuView : public BtnGridView {
|
||||
public:
|
||||
DebugMenuView(NavigationView& nav);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -30,6 +31,30 @@ static const char16_t* firmware_folder = u"/FIRMWARE";
|
||||
Thread* FlashUtilityView::thread{nullptr};
|
||||
static constexpr size_t max_filename_length = 26;
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path) {
|
||||
File firmware_file;
|
||||
uint32_t read_buffer[128];
|
||||
uint32_t checksum{(uint32_t)~FLASH_EXPECTED_CHECKSUM}; // initializing to invalid checksum in case file can't be read
|
||||
|
||||
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
|
||||
auto result = firmware_file.open(path.c_str());
|
||||
if (!result.is_valid()) {
|
||||
checksum = 0;
|
||||
for (uint32_t i = 0; i < FLASH_ROM_SIZE / sizeof(read_buffer); i++) {
|
||||
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
|
||||
|
||||
// if file is smaller than 1MB, assume it's a downgrade to an old FW version and ignore the checksum
|
||||
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
|
||||
checksum = FLASH_EXPECTED_CHECKSUM;
|
||||
break;
|
||||
}
|
||||
|
||||
checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer));
|
||||
}
|
||||
}
|
||||
return (checksum == FLASH_EXPECTED_CHECKSUM);
|
||||
}
|
||||
|
||||
FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&labels,
|
||||
@@ -85,9 +110,8 @@ bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string&
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path) {
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
|
||||
//
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
@@ -98,22 +122,22 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), fileName);
|
||||
});
|
||||
if (res.string().empty()) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), "BAD TAR FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
ui::Painter painter;
|
||||
if (endsWith(path, u".tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path).native();
|
||||
if (path.empty()) return;
|
||||
path = extract_tar(u'/' + path, painter).native();
|
||||
}
|
||||
|
||||
if (path.empty() || !valid_firmware_file(path.c_str())) {
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, Styles::red, "BAD FIRMWARE FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
return false;
|
||||
}
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
@@ -126,6 +150,7 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
m0_halt();
|
||||
return true; // fixes compiler warning (line should not be reached due to halt)
|
||||
}
|
||||
|
||||
void FlashUtilityView::focus() {
|
||||
|
||||
@@ -32,8 +32,14 @@
|
||||
#include "untar.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#define FLASH_ROM_SIZE 1048576
|
||||
#define FLASH_STARTING_ADDRESS 0x00000000
|
||||
#define FLASH_EXPECTED_CHECKSUM 0x00000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path);
|
||||
|
||||
class FlashUtilityView : public View {
|
||||
public:
|
||||
FlashUtilityView(NavigationView& nav);
|
||||
@@ -41,6 +47,7 @@ class FlashUtilityView : public View {
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
bool flash_firmware(std::filesystem::path::string_type path);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
@@ -55,9 +62,9 @@ class FlashUtilityView : public View {
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
void firmware_selected(std::filesystem::path::string_type path);
|
||||
void flash_firmware(std::filesystem::path::string_type path);
|
||||
|
||||
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "tonesets.hpp"
|
||||
#include "ui_tone_key.hpp"
|
||||
#include "wm8731.hpp"
|
||||
#include "radio.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -105,6 +107,7 @@ void MicTXView::configure_baseband() {
|
||||
transmitting ? transmitter_model.channel_bandwidth() : 0,
|
||||
mic_gain_x10 / 10.0,
|
||||
shift_bits(), // to be used in dsp_modulate
|
||||
8, // bits per sample
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
|
||||
(mic_mod_index == MIC_MOD_AM),
|
||||
(mic_mod_index == MIC_MOD_DSB),
|
||||
@@ -335,6 +338,7 @@ MicTXView::MicTXView(
|
||||
&field_rxlna,
|
||||
&field_rxvga,
|
||||
&field_rxamp,
|
||||
hackrf_r9 ? &field_tx_iq_phase_cal_2839 : &field_tx_iq_phase_cal_2837,
|
||||
&tx_button,
|
||||
&tx_icon});
|
||||
|
||||
@@ -367,6 +371,21 @@ MicTXView::MicTXView(
|
||||
receiver_model.set_rf_amp(v);
|
||||
};
|
||||
|
||||
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
if (hackrf_r9) { // MAX2839 has 6 bits IQ CAL phasse adjustment.
|
||||
field_tx_iq_phase_cal_2839.set_value(iq_phase_calibration_value);
|
||||
field_tx_iq_phase_cal_2839.on_change = [this](int32_t v) {
|
||||
iq_phase_calibration_value = v;
|
||||
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
};
|
||||
} else { // MAX2837 has 5 bits IQ CAL phase adjustment.
|
||||
field_tx_iq_phase_cal_2837.set_value(iq_phase_calibration_value);
|
||||
field_tx_iq_phase_cal_2837.on_change = [this](int32_t v) {
|
||||
iq_phase_calibration_value = v;
|
||||
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
|
||||
};
|
||||
}
|
||||
|
||||
options_gain.on_change = [this](size_t, int32_t v) {
|
||||
mic_gain_x10 = v;
|
||||
configure_baseband();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -113,6 +114,7 @@ class MicTXView : public View {
|
||||
uint32_t va_level{40};
|
||||
uint32_t attack_ms{500};
|
||||
uint32_t decay_ms{1000};
|
||||
uint8_t iq_phase_calibration_value{15};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_mic",
|
||||
app_settings::Mode::RX_TX,
|
||||
@@ -132,6 +134,7 @@ class MicTXView : public View {
|
||||
{"vox"sv, &va_enabled},
|
||||
{"rogerbeep"sv, &rogerbeep_enabled},
|
||||
{"tone_key_index"sv, &tone_key_index},
|
||||
{"iq_phase_calibration"sv, &iq_phase_calibration_value},
|
||||
}};
|
||||
|
||||
rf::Frequency tx_frequency{0};
|
||||
@@ -160,7 +163,8 @@ class MicTXView : public View {
|
||||
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
|
||||
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
|
||||
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
|
||||
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
|
||||
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()},
|
||||
{{21 * 8, (31 * 8)}, "TX-IQ-CAL:", Color::light_grey()}};
|
||||
Labels labels_WM8731{
|
||||
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()}};
|
||||
Labels labels_AK4951{
|
||||
@@ -338,6 +342,22 @@ class MicTXView : public View {
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_tx_iq_phase_cal_2837{
|
||||
{24 * 8, (33 * 8)},
|
||||
2,
|
||||
{0, 31}, // 5 bits IQ CAL phase adjustment.
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_tx_iq_phase_cal_2839{
|
||||
{24 * 8, (33 * 8)},
|
||||
2,
|
||||
{0, 63}, // 6 bits IQ CAL phasse adjustment.
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button tx_button{
|
||||
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
|
||||
"PTT TX",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyleft (ↄ) 2022 NotPike
|
||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -389,7 +390,7 @@ PlaylistView::PlaylistView(
|
||||
ensure_directory(u"PLAYLIST");
|
||||
waterfall.show_audio_spectrum_view(false);
|
||||
|
||||
field_frequency.set_value(100'000'000);
|
||||
field_frequency.set_value(transmitter_model.target_frequency());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
if (current())
|
||||
current()->metadata.center_frequency = f;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Kyle Reed, zxkmm
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -56,7 +57,7 @@ class PlaylistView : public View {
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_playlist", app_settings::Mode::TX};
|
||||
"tx_replay", app_settings::Mode::TX};
|
||||
|
||||
// More header == less spectrum view.
|
||||
static constexpr ui::Dim header_height = 6 * 16;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -42,6 +44,7 @@ namespace fs = std::filesystem;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "config_mode.hpp"
|
||||
|
||||
@@ -81,6 +84,7 @@ SetDateTimeView::SetDateTimeView(
|
||||
&field_second,
|
||||
&text_weekday,
|
||||
&text_day_of_year,
|
||||
&text_in_dst_range,
|
||||
&checkbox_dst_enable,
|
||||
&options_dst_start_which,
|
||||
&options_dst_start_weekday,
|
||||
@@ -100,18 +104,25 @@ SetDateTimeView::SetDateTimeView(
|
||||
options_dst_start_month.set_options(month_options);
|
||||
options_dst_end_month.set_options(month_options);
|
||||
|
||||
const auto dst_changed_fn = [this](size_t, uint32_t) {
|
||||
handle_date_field_update();
|
||||
};
|
||||
|
||||
const auto date_changed_fn = [this](int32_t) {
|
||||
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
|
||||
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
|
||||
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
|
||||
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
|
||||
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
|
||||
handle_date_field_update();
|
||||
};
|
||||
|
||||
field_year.on_change = date_changed_fn;
|
||||
field_month.on_change = date_changed_fn;
|
||||
field_day.on_change = date_changed_fn;
|
||||
|
||||
options_dst_start_which.on_change = dst_changed_fn;
|
||||
options_dst_start_weekday.on_change = dst_changed_fn;
|
||||
options_dst_start_month.on_change = dst_changed_fn;
|
||||
options_dst_end_which.on_change = dst_changed_fn;
|
||||
options_dst_end_weekday.on_change = dst_changed_fn;
|
||||
options_dst_end_month.on_change = dst_changed_fn;
|
||||
|
||||
rtc::RTC datetime;
|
||||
rtc_time::now(datetime);
|
||||
SetDateTimeModel model{
|
||||
@@ -146,6 +157,17 @@ void SetDateTimeView::form_init(const SetDateTimeModel& model) {
|
||||
}
|
||||
|
||||
SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
return {
|
||||
.year = static_cast<uint16_t>(field_year.value()),
|
||||
.month = static_cast<uint8_t>(field_month.value()),
|
||||
.day = static_cast<uint8_t>(field_day.value()),
|
||||
.hour = static_cast<uint8_t>(field_hour.value()),
|
||||
.minute = static_cast<uint8_t>(field_minute.value()),
|
||||
.second = static_cast<uint8_t>(field_second.value()),
|
||||
.dst = dst_collect()};
|
||||
}
|
||||
|
||||
pmem::dst_config_t SetDateTimeView::dst_collect() {
|
||||
pmem::dst_config_t dst;
|
||||
dst.b.dst_enabled = static_cast<uint8_t>(checkbox_dst_enable.value());
|
||||
dst.b.start_which = static_cast<uint8_t>(options_dst_start_which.selected_index_value());
|
||||
@@ -154,14 +176,16 @@ SetDateTimeModel SetDateTimeView::form_collect() {
|
||||
dst.b.end_which = static_cast<uint8_t>(options_dst_end_which.selected_index_value());
|
||||
dst.b.end_weekday = static_cast<uint8_t>(options_dst_end_weekday.selected_index_value());
|
||||
dst.b.end_month = static_cast<uint8_t>(options_dst_end_month.selected_index_value());
|
||||
return {
|
||||
.year = static_cast<uint16_t>(field_year.value()),
|
||||
.month = static_cast<uint8_t>(field_month.value()),
|
||||
.day = static_cast<uint8_t>(field_day.value()),
|
||||
.hour = static_cast<uint8_t>(field_hour.value()),
|
||||
.minute = static_cast<uint8_t>(field_minute.value()),
|
||||
.second = static_cast<uint8_t>(field_second.value()),
|
||||
.dst = dst};
|
||||
return dst;
|
||||
}
|
||||
|
||||
void SetDateTimeView::handle_date_field_update() {
|
||||
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
|
||||
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
|
||||
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
|
||||
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
|
||||
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
|
||||
text_in_dst_range.set(checkbox_dst_enable.value() && rtc_time::dst_test_date_range(field_year.value(), doy, dst_collect()) ? "DST" : "");
|
||||
}
|
||||
|
||||
/* SetRadioView ******************************************/
|
||||
@@ -293,6 +317,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
&toggle_bias_tee,
|
||||
&toggle_clock,
|
||||
&toggle_mute,
|
||||
&toggle_fake_brightness,
|
||||
&toggle_sd_card,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
@@ -326,6 +351,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
toggle_clock.set_value(!pmem::ui_hide_clock());
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
@@ -352,6 +378,7 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
pmem::set_ui_hide_clock(!toggle_clock.value());
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
send_system_refresh();
|
||||
|
||||
@@ -586,6 +613,8 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
pmem::cache::defaults();
|
||||
// Refresh status bar
|
||||
send_system_refresh();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -655,13 +684,16 @@ void SetQRCodeView::focus() {
|
||||
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_encoder_dial_sensitivity,
|
||||
&field_encoder_rate_multiplier,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::config_encoder_dial_sensitivity());
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
|
||||
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
@@ -726,6 +758,81 @@ void SetConfigModeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetFakeBrightnessView ************************************/
|
||||
|
||||
SetFakeBrightnessView::SetFakeBrightnessView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_fake_brightness,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&checkbox_brightness_switch});
|
||||
|
||||
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
|
||||
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
|
||||
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetFakeBrightnessView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetMenuColorView ************************************/
|
||||
|
||||
void SetMenuColorView::paint_sample() {
|
||||
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
|
||||
button_sample.set_bg_color(c);
|
||||
}
|
||||
|
||||
SetMenuColorView::SetMenuColorView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_sample,
|
||||
&field_red_level,
|
||||
&field_green_level,
|
||||
&field_blue_level,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
button_sample.set_focusable(false);
|
||||
|
||||
Color c = pmem::menu_color();
|
||||
field_red_level.set_value(c.r());
|
||||
field_green_level.set_value(c.g());
|
||||
field_blue_level.set_value(c.b());
|
||||
paint_sample();
|
||||
|
||||
const auto color_changed_fn = [this](int32_t) {
|
||||
paint_sample();
|
||||
};
|
||||
field_red_level.on_change = color_changed_fn;
|
||||
field_green_level.on_change = color_changed_fn;
|
||||
field_blue_level.on_change = color_changed_fn;
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
|
||||
pmem::set_menu_color(c);
|
||||
send_system_refresh();
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetMenuColorView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
@@ -746,6 +853,8 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SetSDCardView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
|
||||
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
|
||||
{"Brightness", ui::Color::dark_cyan(), &bitmap_icon_brightness, [&nav]() { nav.push<SetFakeBrightnessView>(); }},
|
||||
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [&nav]() { nav.push<SetMenuColorView>(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
* Copyleft (ɔ) 2024 zxkmm under GPL license
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -126,6 +128,9 @@ class SetDateTimeView : public View {
|
||||
Text text_day_of_year{
|
||||
{26 * 8, 6 * 16, 3 * 8, 16},
|
||||
""};
|
||||
Text text_in_dst_range{
|
||||
{17 * 8, 7 * 16, 3 * 8, 16},
|
||||
""};
|
||||
|
||||
Checkbox checkbox_dst_enable{
|
||||
{2 * 8, 9 * 16},
|
||||
@@ -171,6 +176,8 @@ class SetDateTimeView : public View {
|
||||
|
||||
void form_init(const SetDateTimeModel& model);
|
||||
SetDateTimeModel form_collect();
|
||||
portapack::persistent_memory::dst_config_t dst_collect();
|
||||
void handle_date_field_update();
|
||||
};
|
||||
|
||||
struct SetFrequencyCorrectionModel {
|
||||
@@ -315,39 +322,43 @@ class SetUIView : public View {
|
||||
};
|
||||
|
||||
ImageToggle toggle_camera{
|
||||
{7 * 8, 14 * 16 + 2, 16, 16},
|
||||
{6 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_camera};
|
||||
|
||||
ImageToggle toggle_sleep{
|
||||
{9 * 8, 14 * 16 + 2, 16, 16},
|
||||
{8 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_sleep};
|
||||
|
||||
ImageToggle toggle_stealth{
|
||||
{11 * 8, 14 * 16 + 2, 16, 16},
|
||||
{10 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_stealth};
|
||||
|
||||
ImageToggle toggle_converter{
|
||||
{13 * 8, 14 * 16 + 2, 16, 16},
|
||||
{12 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_upconvert};
|
||||
|
||||
ImageToggle toggle_bias_tee{
|
||||
{15 * 8, 14 * 16 + 2, 16, 16},
|
||||
{14 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_biast_off};
|
||||
|
||||
ImageToggle toggle_clock{
|
||||
{17 * 8, 14 * 16 + 2, 8, 16},
|
||||
{16 * 8, 14 * 16 + 2, 8, 16},
|
||||
&bitmap_icon_clk_ext};
|
||||
|
||||
ImageToggle toggle_mute{
|
||||
{18 * 8, 14 * 16 + 2, 16, 16},
|
||||
{17 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_and_headphones_mute};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
{20 * 8, 14 * 16 + 2, 16, 16},
|
||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_mute};
|
||||
|
||||
ImageToggle toggle_fake_brightness{
|
||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_brightness};
|
||||
|
||||
ImageToggle toggle_sd_card{
|
||||
{22 * 8, 14 * 16 + 2, 16, 16},
|
||||
{23 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_sd_card_ok};
|
||||
|
||||
Button button_save{
|
||||
@@ -545,6 +556,7 @@ class SetQRCodeView : public View {
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::encoder_dial_sensitivity;
|
||||
using portapack::persistent_memory::encoder_rate_multiplier;
|
||||
|
||||
class SetEncoderDialView : public View {
|
||||
public:
|
||||
@@ -556,18 +568,30 @@ class SetEncoderDialView : public View {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Adjusts how many steps to", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "change the encoder value.", Color::light_grey()},
|
||||
{{2 * 8, 4 * 16}, "Dial sensitivity:", Color::light_grey()},
|
||||
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "rotation position (number of", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "steps per full rotation):", Color::light_grey()},
|
||||
{{2 * 8, 5 * 16}, "Dial sensitivity:", Color::light_grey()},
|
||||
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
|
||||
{{1 * 8, 9 * 16}, "rotation rate (default 1", Color::light_grey()},
|
||||
{{1 * 8, 10 * 16}, "means no rate dependency):", Color::light_grey()},
|
||||
{{3 * 8, 12 * 16}, "Rate multiplier:", Color::light_grey()},
|
||||
};
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 4 * 16},
|
||||
{20 * 8, 5 * 16},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
|
||||
NumberField field_encoder_rate_multiplier{
|
||||
{20 * 8, 12 * 16},
|
||||
2,
|
||||
{1, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
@@ -667,6 +691,104 @@ class SetConfigModeView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::fake_brightness_level_options;
|
||||
|
||||
class SetFakeBrightnessView : public View {
|
||||
public:
|
||||
SetFakeBrightnessView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Brightness"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Limits screen brightness", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "(has a small performance", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "impact when enabled).", Color::light_grey()},
|
||||
{{2 * 8, 8 * 16}, "Brightness:", Color::light_grey()},
|
||||
};
|
||||
|
||||
OptionsField field_fake_brightness{
|
||||
{20 * 8, 8 * 16},
|
||||
6,
|
||||
{{"12.5%", fake_brightness_level_options::BRIGHTNESS_12p5},
|
||||
{"25%", fake_brightness_level_options::BRIGHTNESS_25},
|
||||
{"50%", fake_brightness_level_options::BRIGHTNESS_50}}};
|
||||
|
||||
Checkbox checkbox_brightness_switch{
|
||||
{1 * 8, 5 * 16},
|
||||
16,
|
||||
"Enable brightness adjust"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SetMenuColorView : public View {
|
||||
public:
|
||||
SetMenuColorView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Menu Color"; };
|
||||
|
||||
private:
|
||||
void paint_sample();
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 1 * 16}, "Menu Button Color Scheme", Color::light_grey()},
|
||||
{{2 * 8, 8 * 16}, "Red Level:", Color::light_grey()},
|
||||
{{2 * 8, 9 * 16}, "Green Level:", Color::light_grey()},
|
||||
{{2 * 8, 10 * 16}, "Blue Level:", Color::light_grey()},
|
||||
};
|
||||
|
||||
NewButton button_sample{
|
||||
{8 * 8, 4 * 16, 14 * 8, 3 * 16},
|
||||
"New Color",
|
||||
&bitmap_icon_brightness,
|
||||
};
|
||||
|
||||
NumberField field_red_level{
|
||||
{15 * 8, 8 * 16},
|
||||
3,
|
||||
{8, 248},
|
||||
8,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_green_level{
|
||||
{15 * 8, 9 * 16},
|
||||
3,
|
||||
{8, 248},
|
||||
8,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_blue_level{
|
||||
{15 * 8, 10 * 16},
|
||||
3,
|
||||
{8, 248},
|
||||
8,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SettingsMenuView : public BtnGridView {
|
||||
public:
|
||||
SettingsMenuView(NavigationView& nav);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -26,6 +27,8 @@
|
||||
#include "log_file.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -424,6 +427,9 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
&text_size,
|
||||
});
|
||||
|
||||
text_position.set_style(&Styles::bg_dark_blue);
|
||||
text_size.set_style(&Styles::bg_dark_blue);
|
||||
|
||||
viewer.set_font_zoom(enable_zoom);
|
||||
|
||||
viewer.on_select = [this]() {
|
||||
@@ -550,6 +556,8 @@ void TextEditorView::open_file(const fs::path& path) {
|
||||
viewer.set_file(*file_);
|
||||
}
|
||||
|
||||
portapack::persistent_memory::set_apply_fake_brightness(false); // work around to resolve the display issue in notepad app. not elegant i know, so TODO.
|
||||
|
||||
refresh_ui();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -22,11 +23,12 @@
|
||||
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ViewWavView::update_scale(int32_t new_scale) {
|
||||
@@ -37,9 +39,24 @@ void ViewWavView::update_scale(int32_t new_scale) {
|
||||
}
|
||||
|
||||
void ViewWavView::refresh_waveform() {
|
||||
// NB: We can't read from the file to update the waveform when playback is in progress, so defer til playback done.
|
||||
// (This only happens if the user messes with position or scale fields while playback is occurring)
|
||||
if (playback_in_progress) {
|
||||
waveform_update_needed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t bits_per_sample = wav_reader->bits_per_sample();
|
||||
|
||||
for (size_t i = 0; i < 240; i++) {
|
||||
wav_reader->data_seek(position + (i * scale));
|
||||
wav_reader->read(&waveform_buffer[i], sizeof(int16_t));
|
||||
if (bits_per_sample == 8) {
|
||||
uint8_t sample;
|
||||
wav_reader->read(&sample, 1);
|
||||
waveform_buffer[i] = (sample - 0x80) * 256;
|
||||
} else {
|
||||
wav_reader->read(&waveform_buffer[i], 2);
|
||||
}
|
||||
}
|
||||
|
||||
waveform.set_dirty();
|
||||
@@ -72,15 +89,33 @@ void ViewWavView::paint(Painter& painter) {
|
||||
painter.draw_vline({(Coord)i, 11 * 16}, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]);
|
||||
}
|
||||
|
||||
void ViewWavView::on_pos_changed() {
|
||||
position = (field_pos_seconds.value() * wav_reader->sample_rate()) + field_pos_samples.value();
|
||||
void ViewWavView::on_pos_time_changed() {
|
||||
position = (uint64_t)((field_pos_seconds.value() * 1000) + field_pos_milliseconds.value()) * wav_reader->sample_rate() / 1000;
|
||||
field_pos_milliseconds.set_range(0, ((uint32_t)field_pos_seconds.value() == wav_reader->ms_duration() / 1000) ? wav_reader->ms_duration() % 1000 : 999);
|
||||
if (!updating_position) {
|
||||
updating_position = true; // prevent recursion
|
||||
field_pos_samples.set_value(position);
|
||||
updating_position = false;
|
||||
}
|
||||
refresh_waveform();
|
||||
}
|
||||
|
||||
void ViewWavView::on_pos_sample_changed() {
|
||||
position = field_pos_samples.value();
|
||||
if (!updating_position) {
|
||||
updating_position = true; // prevent recursion
|
||||
field_pos_seconds.set_value(field_pos_samples.value() / wav_reader->sample_rate());
|
||||
field_pos_milliseconds.set_value((field_pos_samples.value() * 1000ull / wav_reader->sample_rate()) % 1000);
|
||||
updating_position = false;
|
||||
}
|
||||
refresh_waveform();
|
||||
}
|
||||
|
||||
void ViewWavView::load_wav(std::filesystem::path file_path) {
|
||||
int16_t sample;
|
||||
uint32_t average;
|
||||
|
||||
wav_file_path = file_path;
|
||||
|
||||
text_filename.set(file_path.filename().string());
|
||||
auto ms_duration = wav_reader->ms_duration();
|
||||
text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s");
|
||||
@@ -88,18 +123,27 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
|
||||
wav_reader->rewind();
|
||||
|
||||
text_samplerate.set(to_string_dec_uint(wav_reader->sample_rate()) + "Hz");
|
||||
text_bits_per_sample.set(to_string_dec_uint(wav_reader->bits_per_sample(), 2));
|
||||
text_title.set(wav_reader->title());
|
||||
|
||||
// Fill amplitude buffer, world's worst downsampling
|
||||
uint64_t skip = wav_reader->sample_count() / (240 * subsampling_factor);
|
||||
uint8_t bits_per_sample = wav_reader->bits_per_sample();
|
||||
|
||||
for (size_t i = 0; i < 240; i++) {
|
||||
average = 0;
|
||||
|
||||
for (size_t s = 0; s < subsampling_factor; s++) {
|
||||
wav_reader->data_seek(((i * subsampling_factor) + s) * skip);
|
||||
wav_reader->read(&sample, 2);
|
||||
average += (abs(sample) >> 8);
|
||||
if (bits_per_sample == 8) {
|
||||
uint8_t sample;
|
||||
wav_reader->read(&sample, 1);
|
||||
average += sample / 2;
|
||||
} else {
|
||||
int16_t sample;
|
||||
wav_reader->read(&sample, 2);
|
||||
average += (abs(sample) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
amplitude_buffer[i] = average / subsampling_factor;
|
||||
@@ -115,11 +159,109 @@ void ViewWavView::reset_controls() {
|
||||
field_pos_samples.set_value(0);
|
||||
field_cursor_a.set_value(0);
|
||||
field_cursor_b.set_value(0);
|
||||
field_pos_seconds.set_range(0, wav_reader->ms_duration() / 1000);
|
||||
field_pos_milliseconds.set_range(0, (wav_reader->ms_duration() < 1000) ? wav_reader->ms_duration() % 1000 : 999);
|
||||
field_pos_samples.set_range(0, wav_reader->sample_count() - 1);
|
||||
field_scale.set_range(1, std::min(99999ul, wav_reader->sample_count() / 240));
|
||||
}
|
||||
|
||||
bool ViewWavView::is_active() {
|
||||
return (bool)replay_thread;
|
||||
}
|
||||
|
||||
void ViewWavView::stop() {
|
||||
if (is_active())
|
||||
replay_thread.reset();
|
||||
|
||||
audio::output::stop();
|
||||
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
ready_signal = false;
|
||||
}
|
||||
|
||||
void ViewWavView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
(void)return_code;
|
||||
|
||||
stop();
|
||||
progressbar.set_value(0);
|
||||
|
||||
if (return_code == ReplayThread::READ_ERROR)
|
||||
file_error();
|
||||
|
||||
// Playback complete - now it's safe to update waveform view
|
||||
playback_in_progress = false;
|
||||
if (waveform_update_needed) {
|
||||
waveform_update_needed = false;
|
||||
refresh_waveform();
|
||||
}
|
||||
}
|
||||
|
||||
void ViewWavView::set_ready() {
|
||||
ready_signal = true;
|
||||
}
|
||||
|
||||
void ViewWavView::file_error() {
|
||||
nav_.display_modal("Error", "File read error.");
|
||||
}
|
||||
|
||||
void ViewWavView::start_playback() {
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits_per_sample;
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
stop();
|
||||
|
||||
if (!reader->open(wav_file_path)) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
playback_in_progress = true;
|
||||
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
sample_rate = reader->sample_rate();
|
||||
bits_per_sample = reader->bits_per_sample();
|
||||
|
||||
progressbar.set_max(reader->sample_count());
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
read_size, buffer_count,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
|
||||
baseband::set_audiotx_config(
|
||||
1536000 / 20, // Rate of sending progress updates
|
||||
0, // Transmit BW = 0 = not transmitting
|
||||
0, // Gain - unused
|
||||
8, // shift_bits_s16, default 8 bits - unused
|
||||
bits_per_sample, // bits_per_sample
|
||||
0, // tone key disabled
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
transmitter_model.set_sampling_rate(1536000);
|
||||
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
void ViewWavView::on_playback_progress(const uint32_t progress) {
|
||||
progressbar.set_value(progress);
|
||||
field_pos_samples.set_value(progress);
|
||||
}
|
||||
|
||||
ViewWavView::ViewWavView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||||
wav_reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
add_children({&labels,
|
||||
@@ -127,26 +269,33 @@ ViewWavView::ViewWavView(
|
||||
&text_samplerate,
|
||||
&text_title,
|
||||
&text_duration,
|
||||
&text_bits_per_sample,
|
||||
&button_open,
|
||||
&button_play,
|
||||
&waveform,
|
||||
&progressbar,
|
||||
&field_pos_seconds,
|
||||
&field_pos_milliseconds,
|
||||
&field_pos_samples,
|
||||
&field_scale,
|
||||
&field_cursor_a,
|
||||
&field_cursor_b,
|
||||
&text_delta});
|
||||
&text_delta,
|
||||
&field_volume});
|
||||
|
||||
reset_controls();
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".WAV");
|
||||
open_view->on_changed = [this, &nav](std::filesystem::path file_path) {
|
||||
// Can't show new dialogs in an on_changed handler, so use continuation.
|
||||
nav.set_on_pop([this, &nav, file_path]() {
|
||||
if (!wav_reader->open(file_path)) {
|
||||
nav_.display_modal("Error", "Couldn't open file.");
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
|
||||
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.");
|
||||
if ((wav_reader->channels() != 1) || ((wav_reader->bits_per_sample() != 8) && (wav_reader->bits_per_sample() != 16))) {
|
||||
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n8 or 16-bit mono files.");
|
||||
return;
|
||||
}
|
||||
load_wav(file_path);
|
||||
@@ -155,14 +304,26 @@ ViewWavView::ViewWavView(
|
||||
};
|
||||
};
|
||||
|
||||
field_volume.set_value(field_volume.value());
|
||||
|
||||
button_play.on_select = [this, &nav](ImageButton&) {
|
||||
if (this->is_active())
|
||||
stop();
|
||||
else
|
||||
start_playback();
|
||||
};
|
||||
|
||||
field_scale.on_change = [this](int32_t value) {
|
||||
update_scale(value);
|
||||
};
|
||||
field_pos_seconds.on_change = [this](int32_t) {
|
||||
on_pos_changed();
|
||||
on_pos_time_changed();
|
||||
};
|
||||
field_pos_milliseconds.on_change = [this](int32_t) {
|
||||
on_pos_time_changed();
|
||||
};
|
||||
field_pos_samples.on_change = [this](int32_t) {
|
||||
on_pos_changed();
|
||||
on_pos_sample_changed();
|
||||
};
|
||||
|
||||
field_cursor_a.on_change = [this](int32_t v) {
|
||||
@@ -179,4 +340,9 @@ void ViewWavView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
|
||||
ViewWavView::~ViewWavView() {
|
||||
stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -24,12 +25,15 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ViewWavView : public View {
|
||||
public:
|
||||
ViewWavView(NavigationView& nav);
|
||||
~ViewWavView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
@@ -37,15 +41,33 @@ class ViewWavView : public View {
|
||||
std::string title() const override { return "WAV viewer"; };
|
||||
|
||||
private:
|
||||
app_settings::SettingsManager settings_{
|
||||
"wav_viewer", app_settings::Mode::NO_RF};
|
||||
|
||||
NavigationView& nav_;
|
||||
static constexpr uint32_t subsampling_factor = 8;
|
||||
|
||||
void update_scale(int32_t new_scale);
|
||||
void refresh_waveform();
|
||||
void refresh_measurements();
|
||||
void on_pos_changed();
|
||||
void on_pos_time_changed();
|
||||
void on_pos_sample_changed();
|
||||
void load_wav(std::filesystem::path file_path);
|
||||
void reset_controls();
|
||||
bool is_active();
|
||||
void stop();
|
||||
void handle_replay_thread_done(const uint32_t return_code);
|
||||
void set_ready();
|
||||
void file_error();
|
||||
void start_playback();
|
||||
void on_playback_progress(const uint32_t progress);
|
||||
|
||||
std::filesystem::path wav_file_path{};
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool ready_signal{false};
|
||||
const size_t read_size{2048};
|
||||
const size_t buffer_count{3};
|
||||
const uint32_t progress_interval_samples{1536000 / 20};
|
||||
|
||||
std::unique_ptr<WAVFileReader> wav_reader{};
|
||||
|
||||
@@ -54,32 +76,47 @@ class ViewWavView : public View {
|
||||
int32_t scale{1};
|
||||
uint64_t ns_per_pixel{};
|
||||
uint64_t position{};
|
||||
bool updating_position{false};
|
||||
bool playback_in_progress{false};
|
||||
bool waveform_update_needed{false};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "File:", Color::light_grey()},
|
||||
{{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()},
|
||||
{{2 * 8, 1 * 16}, "-bit mono", Color::light_grey()},
|
||||
{{0 * 8, 2 * 16}, "Title:", Color::light_grey()},
|
||||
{{0 * 8, 3 * 16}, "Duration:", Color::light_grey()},
|
||||
{{0 * 8, 11 * 16}, "Position: s Scale:", Color::light_grey()},
|
||||
{{0 * 8, 12 * 16}, "Cursor A:", Color::dark_cyan()},
|
||||
{{0 * 8, 13 * 16}, "Cursor B:", Color::dark_magenta()},
|
||||
{{0 * 8, 14 * 16}, "Delta:", Color::light_grey()}};
|
||||
{{0 * 8, 12 * 16}, "Position: . s Scale:", Color::light_grey()},
|
||||
{{0 * 8, 13 * 16}, " Sample:", Color::light_grey()},
|
||||
{{0 * 8, 14 * 16}, "Cursor A:", Color::dark_cyan()},
|
||||
{{0 * 8, 15 * 16}, "Cursor B:", Color::dark_magenta()},
|
||||
{{0 * 8, 16 * 16}, "Delta:", Color::light_grey()},
|
||||
{{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}};
|
||||
|
||||
Text text_filename{
|
||||
{5 * 8, 0 * 16, 12 * 8, 16},
|
||||
{5 * 8, 0 * 16, 18 * 8, 16},
|
||||
""};
|
||||
Text text_samplerate{
|
||||
{11 * 8, 1 * 16, 8 * 8, 16},
|
||||
{12 * 8, 1 * 16, 10 * 8, 16},
|
||||
""};
|
||||
Text text_title{
|
||||
{6 * 8, 2 * 16, 18 * 8, 16},
|
||||
{6 * 8, 2 * 16, 17 * 8, 16},
|
||||
""};
|
||||
Text text_duration{
|
||||
{9 * 8, 3 * 16, 18 * 8, 16},
|
||||
{9 * 8, 3 * 16, 20 * 8, 16},
|
||||
""};
|
||||
Text text_bits_per_sample{
|
||||
{0 * 8, 1 * 16, 2 * 8, 16},
|
||||
"16"};
|
||||
Button button_open{
|
||||
{24 * 8, 8, 6 * 8, 2 * 16},
|
||||
"Open"};
|
||||
ImageButton button_play{
|
||||
{24 * 8, 17 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 18 * 16}};
|
||||
|
||||
Waveform waveform{
|
||||
{0, 5 * 16, 240, 64},
|
||||
@@ -89,27 +126,40 @@ class ViewWavView : public View {
|
||||
false,
|
||||
Color::white()};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 11 * 16, 30 * 8, 4}};
|
||||
|
||||
NumberField field_pos_seconds{
|
||||
{9 * 8, 11 * 16},
|
||||
{9 * 8, 12 * 16},
|
||||
4,
|
||||
{0, 0},
|
||||
1,
|
||||
' ',
|
||||
true};
|
||||
NumberField field_pos_milliseconds{
|
||||
{14 * 8, 12 * 16},
|
||||
3,
|
||||
{0, 999},
|
||||
1,
|
||||
' '};
|
||||
'0',
|
||||
true};
|
||||
NumberField field_pos_samples{
|
||||
{14 * 8, 11 * 16},
|
||||
6,
|
||||
{0, 999999},
|
||||
{9 * 8, 13 * 16},
|
||||
9,
|
||||
{0, 0},
|
||||
1,
|
||||
'0'};
|
||||
'0',
|
||||
true};
|
||||
NumberField field_scale{
|
||||
{28 * 8, 11 * 16},
|
||||
2,
|
||||
{1, 40},
|
||||
{25 * 8, 12 * 16},
|
||||
5,
|
||||
{1, 1},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
|
||||
NumberField field_cursor_a{
|
||||
{9 * 8, 12 * 16},
|
||||
{9 * 8, 14 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
@@ -117,7 +167,7 @@ class ViewWavView : public View {
|
||||
true};
|
||||
|
||||
NumberField field_cursor_b{
|
||||
{9 * 8, 13 * 16},
|
||||
{9 * 8, 15 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
@@ -125,8 +175,31 @@ class ViewWavView : public View {
|
||||
true};
|
||||
|
||||
Text text_delta{
|
||||
{6 * 8, 14 * 16, 30 * 8, 16},
|
||||
{7 * 8, 16 * 16, 30 * 8, 16},
|
||||
"-"};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
this->handle_replay_thread_done(message.return_code);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
this->set_ready();
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_playback_progress(message.progress);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -211,6 +211,7 @@ void set_audiotx_config(
|
||||
const float deviation_hz,
|
||||
const float audio_gain,
|
||||
uint8_t audio_shift_bits_s16,
|
||||
uint8_t bits_per_sample,
|
||||
const uint32_t tone_key_delta,
|
||||
const bool am_enabled,
|
||||
const bool dsb_enabled,
|
||||
@@ -221,6 +222,7 @@ void set_audiotx_config(
|
||||
deviation_hz,
|
||||
audio_gain,
|
||||
audio_shift_bits_s16,
|
||||
bits_per_sample,
|
||||
tone_key_delta,
|
||||
(float)persistent_memory::tone_mix() / 100.0f,
|
||||
am_enabled,
|
||||
|
||||
@@ -63,7 +63,7 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
|
||||
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
|
||||
void kill_tone();
|
||||
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
|
||||
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
|
||||
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
|
||||
void set_fifo_data(const int8_t* data);
|
||||
void set_pitch_rssi(int32_t avg, bool enabled);
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
|
||||
@@ -5759,6 +5759,44 @@ static constexpr Bitmap bitmap_icon_thermometer{
|
||||
{16, 16},
|
||||
bitmap_icon_thermometer_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_brightness_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x08,
|
||||
0x10,
|
||||
0xC0,
|
||||
0x03,
|
||||
0xE0,
|
||||
0x07,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF6,
|
||||
0x6F,
|
||||
0xF6,
|
||||
0x6F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xE0,
|
||||
0x07,
|
||||
0xC0,
|
||||
0x03,
|
||||
0x08,
|
||||
0x10,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_brightness{
|
||||
{16, 16},
|
||||
bitmap_icon_brightness_data};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__BITMAP_HPP__*/
|
||||
|
||||
@@ -27,14 +27,19 @@
|
||||
void config_mode_blink_until_dfu();
|
||||
|
||||
void config_mode_set() {
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_GUARD_VALUE);
|
||||
uint32_t cms = portapack::persistent_memory::config_mode_storage_direct();
|
||||
if ((cms >= CONFIG_MODE_GUARD_VALUE) && (cms < CONFIG_MODE_LIMIT_VALUE))
|
||||
cms += 1;
|
||||
else
|
||||
cms = CONFIG_MODE_GUARD_VALUE;
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(cms);
|
||||
}
|
||||
|
||||
bool config_mode_should_enter() {
|
||||
if (portapack::persistent_memory::config_disable_config_mode_direct())
|
||||
return false;
|
||||
else
|
||||
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_GUARD_VALUE;
|
||||
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_LIMIT_VALUE;
|
||||
}
|
||||
|
||||
void config_mode_clear() {
|
||||
|
||||
@@ -28,6 +28,13 @@
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
// number of boot failures before entering config menu mode
|
||||
#define BOOT_FAILURES_BEFORE_CONFIG_MODE 3
|
||||
|
||||
#define CONFIG_MODE_GUARD_VALUE 0xbadb0000
|
||||
#define CONFIG_MODE_LIMIT_VALUE (CONFIG_MODE_GUARD_VALUE + BOOT_FAILURES_BEFORE_CONFIG_MODE - 1)
|
||||
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
|
||||
|
||||
void config_mode_set();
|
||||
bool config_mode_should_enter();
|
||||
void config_mode_clear();
|
||||
|
||||
@@ -25,14 +25,12 @@
|
||||
#include "file.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace std {
|
||||
|
||||
int database::retrieve_mid_record(MidDBRecord* record, std::string search_term) {
|
||||
file_path = "AIS/mids.db";
|
||||
index_item_length = 4;
|
||||
record_length = 32;
|
||||
|
||||
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
@@ -42,7 +40,7 @@ int database::retrieve_airline_record(AirlinesDBRecord* record, std::string sear
|
||||
index_item_length = 4;
|
||||
record_length = 64;
|
||||
|
||||
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
@@ -52,12 +50,15 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
|
||||
index_item_length = 7;
|
||||
record_length = 146;
|
||||
|
||||
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term) {
|
||||
if (search_term.empty())
|
||||
return DATABASE_RECORD_NOT_FOUND;
|
||||
|
||||
auto result = db_file.open(file_path);
|
||||
if (!result.is_valid()) {
|
||||
number_of_records = (db_file.size() / (index_item_length + record_length)); // determine number of records in file
|
||||
@@ -91,5 +92,3 @@ int database::retrieve_record(std::string file_path, int index_item_length, int
|
||||
} else
|
||||
return (DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
} /* namespace std */
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
namespace std {
|
||||
class database {
|
||||
public:
|
||||
#define DATABASE_RECORD_FOUND 0 // record found in database
|
||||
@@ -62,9 +61,9 @@ class database {
|
||||
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
|
||||
|
||||
private:
|
||||
string file_path = ""; // path inclusing filename
|
||||
int index_item_length = 0; // length of index item
|
||||
int record_length = 0; // length of record
|
||||
std::string file_path = ""; // path inclusing filename
|
||||
int index_item_length = 0; // length of index item
|
||||
int record_length = 0; // length of record
|
||||
|
||||
File db_file{};
|
||||
int number_of_records = 0;
|
||||
@@ -77,6 +76,5 @@ class database {
|
||||
|
||||
int retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term);
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#endif /*__DATABASE_H__*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -44,6 +44,8 @@ using namespace lpc43xx;
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
static int delayed_error = 0;
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(M4Core_IRQHandler) {
|
||||
@@ -161,6 +163,10 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
}
|
||||
|
||||
if (events & EVT_MASK_RTC_TICK) {
|
||||
// delay error message by 2 seconds to wait for LCD being ready
|
||||
if (portapack::init_error != nullptr && ++delayed_error > 1)
|
||||
draw_guru_meditation(CORTEX_M4, portapack::init_error);
|
||||
|
||||
handle_rtc_tick();
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -3,7 +3,11 @@ set(EXTCPPSRC
|
||||
#pacman
|
||||
external/pacman/main.cpp
|
||||
external/pacman/ui_pacman.cpp
|
||||
|
||||
|
||||
#tetris
|
||||
external/tetris/main.cpp
|
||||
external/tetris/ui_tetris.cpp
|
||||
|
||||
#afsk_rx
|
||||
external/afsk_rx/main.cpp
|
||||
external/afsk_rx/ui_afsk_rx.cpp
|
||||
@@ -19,7 +23,7 @@ set(EXTCPPSRC
|
||||
#blespam
|
||||
external/blespam/main.cpp
|
||||
external/blespam/ui_blespam.cpp
|
||||
|
||||
|
||||
#analogtv
|
||||
external/analogtv/main.cpp
|
||||
external/analogtv/analog_tv_app.cpp
|
||||
@@ -35,13 +39,12 @@ set(EXTCPPSRC
|
||||
#lge
|
||||
external/lge/main.cpp
|
||||
external/lge/lge_app.cpp
|
||||
|
||||
|
||||
#lcr
|
||||
external/lcr/main.cpp
|
||||
external/lcr/ui_lcr.cpp
|
||||
|
||||
|
||||
#lcr
|
||||
|
||||
#jammer
|
||||
external/jammer/main.cpp
|
||||
external/jammer/ui_jammer.cpp
|
||||
|
||||
@@ -76,4 +79,5 @@ set(EXTAPPLIST
|
||||
gpssim
|
||||
spainter
|
||||
keyfob
|
||||
tetris
|
||||
)
|
||||
|
||||
+31
-22
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright (C) 2023 Bernd Herzog
|
||||
Copyright (C) 2024 Mark Thompson
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,21 +17,27 @@
|
||||
|
||||
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
|
||||
ram_external_app_font_viewer(rwx) : org = 0xEEEC0000, len = 32k
|
||||
ram_external_app_blespam(rwx) : org = 0xEEED0000, len = 32k
|
||||
ram_external_app_analogtv(rwx) : org = 0xEEEE0000, len = 32k
|
||||
ram_external_app_nrf_rx(rwx) : org = 0xEEEF0000, len = 32k
|
||||
ram_external_app_coasterp(rwx) : org = 0xEEF00000, len = 32k
|
||||
ram_external_app_lge(rwx) : org = 0xEEF10000, len = 32k
|
||||
ram_external_app_lcr(rwx) : org = 0xEEF20000, len = 32k
|
||||
ram_external_app_jammer(rwx) : org = 0xEEF30000, len = 32k
|
||||
ram_external_app_gpssim(rwx) : org = 0xEEF40000, len = 32k
|
||||
ram_external_app_spainter(rwx) : org = 0xEEF50000, len = 32k
|
||||
ram_external_app_keyfob(rwx) : org = 0xEEF60000, len = 32k
|
||||
/*
|
||||
* External apps: regions can't overlap so addresses are corrected after build.
|
||||
* Picking uncommon address values for search & replace in binaries (no false positives) - 0xADB00000-0xADEF0000 seems to be good.
|
||||
* Also need to consider processor memory map - reading 0xADxxxxxx generates a fault which may be better than unexpected behavior.
|
||||
* External app address ranges below must match those in python file "external_app_info.py".
|
||||
*/
|
||||
ram_external_app_pacman (rwx) : org = 0xADB00000, len = 32k
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xADB10000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xADB20000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xADB30000, len = 32k
|
||||
ram_external_app_blespam(rwx) : org = 0xADB40000, len = 32k
|
||||
ram_external_app_analogtv(rwx) : org = 0xADB50000, len = 32k
|
||||
ram_external_app_nrf_rx(rwx) : org = 0xADB60000, len = 32k
|
||||
ram_external_app_coasterp(rwx) : org = 0xADB70000, len = 32k
|
||||
ram_external_app_lge(rwx) : org = 0xADB80000, len = 32k
|
||||
ram_external_app_lcr(rwx) : org = 0xADB90000, len = 32k
|
||||
ram_external_app_jammer(rwx) : org = 0xADBA0000, len = 32k
|
||||
ram_external_app_gpssim(rwx) : org = 0xADBB0000, len = 32k
|
||||
ram_external_app_spainter(rwx) : org = 0xADBC0000, len = 32k
|
||||
ram_external_app_keyfob(rwx) : org = 0xADBD0000, len = 32k
|
||||
ram_external_app_tetris(rwx) : org = 0xADBE0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -59,7 +66,6 @@ SECTIONS
|
||||
*(*ui*external_app*font_viewer*);
|
||||
} > ram_external_app_font_viewer
|
||||
|
||||
|
||||
.external_app_blespam : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_blespam.application_information));
|
||||
@@ -77,40 +83,37 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_nrf_rx.application_information));
|
||||
*(*ui*external_app*nrf_rx*);
|
||||
} > ram_external_app_nrf_rx
|
||||
|
||||
|
||||
.external_app_coasterp : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_coasterp.application_information));
|
||||
*(*ui*external_app*coasterp*);
|
||||
} > ram_external_app_coasterp
|
||||
|
||||
|
||||
.external_app_lge : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_lge.application_information));
|
||||
*(*ui*external_app*lge*);
|
||||
} > ram_external_app_lge
|
||||
|
||||
|
||||
.external_app_lcr : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_lcr.application_information));
|
||||
*(*ui*external_app*lcr*);
|
||||
} > ram_external_app_lcr
|
||||
|
||||
|
||||
.external_app_jammer : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_jammer.application_information));
|
||||
*(*ui*external_app*jammer*);
|
||||
} > ram_external_app_jammer
|
||||
|
||||
|
||||
.external_app_gpssim : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_gpssim.application_information));
|
||||
*(*ui*external_app*gpssim*);
|
||||
} > ram_external_app_gpssim
|
||||
|
||||
|
||||
.external_app_spainter : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_spainter.application_information));
|
||||
@@ -123,4 +126,10 @@ SECTIONS
|
||||
*(*ui*external_app*keyfob*);
|
||||
} > ram_external_app_keyfob
|
||||
|
||||
.external_app_tetris : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_tetris.application_information));
|
||||
*(*ui*external_app*tetris*);
|
||||
} > ram_external_app_tetris
|
||||
|
||||
}
|
||||
|
||||
+60
-15
@@ -13,8 +13,8 @@ typedef uint8_t byte;
|
||||
typedef uint32_t word;
|
||||
|
||||
byte SPEED = 2;
|
||||
byte MAXLIFES = 5;
|
||||
byte LIFES = START_LIFES;
|
||||
byte MAXLIFES = 20;
|
||||
size_t LIFES = START_LIFES;
|
||||
byte GAMEWIN = 0;
|
||||
byte GAMEOVER = 0;
|
||||
byte DEMO = 1;
|
||||
@@ -25,6 +25,9 @@ byte GAMEPAUSED = 0;
|
||||
|
||||
byte PACMANFALLBACK = 0;
|
||||
|
||||
bool cheat_level = false;
|
||||
bool cheat_lifes = false;
|
||||
|
||||
#include "DrawIndexedMap.h"
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -901,14 +904,15 @@ class Playfield {
|
||||
|
||||
void PackmanDied() { // Noooo... PACMAN DIED :(
|
||||
|
||||
if (LIFES <= 0) {
|
||||
if (LIFES <= 0 && !cheat_lifes) {
|
||||
GAMEOVER = 1;
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
DEMO = 1;
|
||||
Init();
|
||||
} else {
|
||||
LIFES--;
|
||||
if (!cheat_lifes)
|
||||
LIFES--;
|
||||
|
||||
_inited = true;
|
||||
_state = ReadyState;
|
||||
@@ -934,8 +938,14 @@ class Playfield {
|
||||
_icons[13 - i] = BONUSICON + i;
|
||||
}
|
||||
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (!cheat_lifes) {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < 14; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1177,8 +1187,13 @@ class Playfield {
|
||||
if (GAMEWIN == 1) {
|
||||
GAMEWIN = 0;
|
||||
} else {
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
if (!cheat_level) {
|
||||
LEVEL = START_LEVEL;
|
||||
}
|
||||
if (!cheat_lifes) {
|
||||
LIFES = START_LIFES;
|
||||
}
|
||||
|
||||
ACTUALBONUS = 0; // actual bonus icon
|
||||
ACTIVEBONUS = 0; // status of bonus
|
||||
|
||||
@@ -1215,8 +1230,14 @@ class Playfield {
|
||||
}
|
||||
|
||||
// SET Lifes icons
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (cheat_lifes) {
|
||||
for (byte i = 0; i < 14; i++) { // cuz 14 lives full fills PP's screen
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1243,21 +1264,45 @@ class Playfield {
|
||||
}
|
||||
|
||||
void Step() {
|
||||
int16_t keys = 0;
|
||||
|
||||
if (GAMEWIN == 1) {
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
LEVEL++;
|
||||
Init();
|
||||
}
|
||||
|
||||
// Start GAME
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) {
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) { // start
|
||||
but_A = false;
|
||||
DEMO = 0;
|
||||
Init();
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // Or PAUSE GAME
|
||||
DEMO = 0;
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // pause
|
||||
but_A = false;
|
||||
GAMEPAUSED = 1;
|
||||
} else if (but_LEFT && DEMO == 1 && GAMEPAUSED == 0) { // -level
|
||||
cheat_level = true;
|
||||
but_LEFT = false;
|
||||
if (LEVEL > 1) {
|
||||
LEVEL--;
|
||||
}
|
||||
Init();
|
||||
} else if (but_RIGHT && DEMO == 1 && GAMEPAUSED == 0) { // +level
|
||||
cheat_level = true;
|
||||
but_RIGHT = false;
|
||||
if (LEVEL < 255) {
|
||||
LEVEL++;
|
||||
}
|
||||
Init();
|
||||
} else if (but_UP && DEMO == 1 && GAMEPAUSED == 0) { // full of lifes
|
||||
cheat_lifes = true;
|
||||
but_UP = false;
|
||||
Init();
|
||||
} else if (but_DOWN && DEMO == 1 && GAMEPAUSED == 0) { // reset
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
but_DOWN = false;
|
||||
LIFES = START_LIFES;
|
||||
Init();
|
||||
}
|
||||
|
||||
if (GAMEPAUSED && but_A && DEMO == 0) {
|
||||
|
||||
+22
-7
@@ -13,8 +13,6 @@ namespace ui::external_app::pacman {
|
||||
#include "playfield.hpp"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
Playfield _game;
|
||||
|
||||
PacmanView::PacmanView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&dummy});
|
||||
@@ -26,19 +24,36 @@ void PacmanView::focus() {
|
||||
|
||||
void PacmanView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
static Playfield _game;
|
||||
static bool wait_for_button_release{false};
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
_game.Init();
|
||||
}
|
||||
|
||||
auto switches_raw = swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu));
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
|
||||
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;
|
||||
// For the Select (Start/Pause) button, wait for release to avoid repeat
|
||||
uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel);
|
||||
if (wait_for_button_release) {
|
||||
if ((switches_debounced & buttons_to_wait_for) == 0)
|
||||
wait_for_button_release = false;
|
||||
switches_debounced &= ~buttons_to_wait_for;
|
||||
} else {
|
||||
if (switches_debounced & buttons_to_wait_for)
|
||||
wait_for_button_release = true;
|
||||
}
|
||||
|
||||
// For the directional buttons, use the raw inputs for fastest response time
|
||||
but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0;
|
||||
but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0;
|
||||
but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0;
|
||||
but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0;
|
||||
|
||||
// For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen
|
||||
but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0;
|
||||
|
||||
_game.Step();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// dummy include file to avoid changing original source
|
||||
|
||||
#ifndef __UI_Arial12x12_H__
|
||||
#define __UI_Arial12x12_H__
|
||||
|
||||
#define Arial12x12 (0)
|
||||
|
||||
#endif /*__UI_Arial12x12_H__*/
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// "HAL" display layer for Tetris code to run on PortaPack without its original ILI9341 functions
|
||||
|
||||
#ifndef __UI_SPI_TFT_ILI9341_H__
|
||||
#define __UI_SPI_TFT_ILI9341_H__
|
||||
|
||||
ui::Painter painter;
|
||||
|
||||
static int x_pos{0};
|
||||
static int y_pos{0};
|
||||
static int fg_color;
|
||||
static int bg_color;
|
||||
|
||||
enum {
|
||||
White,
|
||||
Blue,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Maroon,
|
||||
Orange,
|
||||
Black,
|
||||
};
|
||||
|
||||
// pp_colors must be in same order as enums above
|
||||
static const Color pp_colors[] = {
|
||||
Color::white(),
|
||||
Color::blue(),
|
||||
Color::yellow(),
|
||||
Color::purple(),
|
||||
Color::green(),
|
||||
Color::red(),
|
||||
Color::magenta(),
|
||||
Color::orange(),
|
||||
Color::black(),
|
||||
};
|
||||
|
||||
// NB: ELIMINATED SPI_TFT_ILI9341 DISPLAY CLASS DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS
|
||||
|
||||
static void claim(__FILE* x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void cls() {
|
||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
||||
};
|
||||
|
||||
static void background(int color) {
|
||||
bg_color = color;
|
||||
};
|
||||
|
||||
static void foreground(int color) {
|
||||
fg_color = color;
|
||||
};
|
||||
|
||||
static void locate(int x, int y) {
|
||||
x_pos = x;
|
||||
y_pos = y;
|
||||
};
|
||||
|
||||
static void set_orientation(int x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void set_font(unsigned char* x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void fillrect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
static void rect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
static void printf(std::string str) {
|
||||
auto style = (fg_color == White) ? ui::Styles::white : ui::Styles::bg_white;
|
||||
painter.draw_string({x_pos, y_pos - 1}, style, str);
|
||||
};
|
||||
|
||||
static void printf(std::string str, int v) {
|
||||
if (str.find_first_of("%") != std::string::npos) {
|
||||
str.resize(str.find_first_of("%")); // remove %d from end of string
|
||||
}
|
||||
printf(str + to_string_dec_uint(v));
|
||||
};
|
||||
|
||||
#endif /*__UI_SPI_TFT_ILI9341_H__*/
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* 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_tetris.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::tetris {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<TetrisView>();
|
||||
}
|
||||
} // namespace ui::external_app::tetris
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_tetris.application_information"), used)) application_information_t _application_information_tetris = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time
|
||||
/*.externalAppEntry = */ ui::external_app::tetris::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Tetris",
|
||||
/*.bitmap_data = */ {
|
||||
0xF8,
|
||||
0xFF,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0x88,
|
||||
0xF8,
|
||||
0xFF,
|
||||
0x80,
|
||||
0x08,
|
||||
0x80,
|
||||
0x08,
|
||||
0x9F,
|
||||
0x08,
|
||||
0x91,
|
||||
0x0F,
|
||||
0x11,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0xFF,
|
||||
0xF1,
|
||||
0x11,
|
||||
0x91,
|
||||
0x11,
|
||||
0x91,
|
||||
0x11,
|
||||
0x91,
|
||||
0xFF,
|
||||
0xF1,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().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
|
||||
};
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// "HAL" layer for Tetris code to run on PortaPack without its original mbed OS
|
||||
// (the dream here was to avoid modifying the original code)
|
||||
|
||||
#ifndef __UI_mbed_H__
|
||||
#define __UI_mbed_H__
|
||||
|
||||
using Callback = void (*)(void);
|
||||
|
||||
#define wait_us(x) (void)0
|
||||
#define wait(x) chThdSleepMilliseconds(x * 1000)
|
||||
#define PullUp 1
|
||||
|
||||
enum {
|
||||
dp0,
|
||||
dp1,
|
||||
dp2,
|
||||
dp3,
|
||||
dp4,
|
||||
dp5,
|
||||
dp6,
|
||||
dp7,
|
||||
dp8,
|
||||
dp9,
|
||||
dp10,
|
||||
dp11,
|
||||
dp12,
|
||||
dp13,
|
||||
dp14,
|
||||
dp15,
|
||||
dp16,
|
||||
dp17,
|
||||
dp18,
|
||||
dp19,
|
||||
dp20,
|
||||
dp21,
|
||||
dp22,
|
||||
dp23,
|
||||
dp24,
|
||||
dp25,
|
||||
};
|
||||
|
||||
static bool but_RIGHT;
|
||||
static bool but_LEFT;
|
||||
static bool but_UP;
|
||||
static bool but_DOWN;
|
||||
static bool but_SELECT;
|
||||
|
||||
//
|
||||
// AnalogIn Class -- DID NOT WORK DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS -- hacked original code module instead
|
||||
//
|
||||
// dp9 = joystick rotate button --> select button
|
||||
// dp10 = joystick y --> up & down buttons
|
||||
// dp11 = joystick x --> left & right buttons
|
||||
// dp13 = random number generator
|
||||
//
|
||||
//
|
||||
// class AnalogIn {
|
||||
// public:
|
||||
// AnalogIn(uint32_t analog_input) {
|
||||
// // FIXME - THIS CODE NEVER GETS EXECUTED!
|
||||
// analog_input_ = analog_input;
|
||||
// };
|
||||
//
|
||||
// // Tetris code only uses this function for dp13 - supposed to be a random number
|
||||
// uint16_t read_u16() {
|
||||
// return std::rand();
|
||||
// };
|
||||
//
|
||||
// // Tetris code uses read() function for direction buttons only
|
||||
// float read() {
|
||||
// float retval = 0.5;
|
||||
// switch (analog_input_) {
|
||||
// case dp11:
|
||||
// if (but_LEFT)
|
||||
// retval = 0.0;
|
||||
// else if (but_RIGHT)
|
||||
// retval = 1.0;
|
||||
// break;
|
||||
//
|
||||
// case dp10:
|
||||
// if (but_UP)
|
||||
// retval = 0.0;
|
||||
// else if (but_DOWN)
|
||||
// retval = 1.0;
|
||||
// break;
|
||||
// }
|
||||
// return retval;
|
||||
// };
|
||||
//
|
||||
// operator float() {
|
||||
// return read();
|
||||
// };
|
||||
//
|
||||
// private:
|
||||
// uint32_t analog_input_{INT_MAX};
|
||||
// };
|
||||
|
||||
//
|
||||
// Timer Class
|
||||
// (Timer object was used for unneeded button debouncing, so just returning 1000ms to indicate we've waited long enough)
|
||||
//
|
||||
class Timer {
|
||||
public:
|
||||
// NOTE: INITIALIZER CODE WON'T RUN
|
||||
Timer() { (void)0; };
|
||||
void reset() { (void)0; };
|
||||
void start() { (void)0; }
|
||||
uint32_t read_ms() { return 1000; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
//
|
||||
// Ticker Class
|
||||
// (Ticker is timed callback, used for checking "joystick" directional switches and when time to move piece down a row)
|
||||
//
|
||||
// NB: Only one callback is supported per Ticker class instantiation
|
||||
static Callback fall_timer_callback;
|
||||
static uint32_t fall_timer_timeout;
|
||||
static uint32_t fall_timer_counter;
|
||||
|
||||
static Callback dir_button_callback;
|
||||
|
||||
static void check_fall_timer() {
|
||||
if (fall_timer_callback) {
|
||||
if (++fall_timer_counter >= fall_timer_timeout) {
|
||||
fall_timer_counter = 0;
|
||||
fall_timer_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
// NOTE: INITIALIZER CODE WON'T RUN
|
||||
Ticker() { (void)0; };
|
||||
|
||||
void attach(Callback func, double delay_sec) {
|
||||
// 0.3 sec is requested only for button check -- kludge to use on_key callback for this one instead of timer
|
||||
if (delay_sec == 0.3) {
|
||||
dir_button_callback = func;
|
||||
} else {
|
||||
fall_timer_callback = func;
|
||||
fall_timer_timeout = delay_sec * 60; // timer interrupts at 60 Hz
|
||||
}
|
||||
}
|
||||
|
||||
void detach() {
|
||||
// shouldn't detach both, but don't know how to tell which object is which
|
||||
dir_button_callback = nullptr;
|
||||
fall_timer_callback = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
//
|
||||
// InterruptIn Class
|
||||
// (just used for the Select button)
|
||||
//
|
||||
static Callback sel_button_callback;
|
||||
|
||||
static bool check_encoder(const EncoderEvent delta) {
|
||||
(void)delta;
|
||||
// TODO: consider adding ability to rotate Tetronimo via encoder too
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_key(const KeyEvent key) {
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
but_RIGHT = (switches_debounced & 0x01) != 0;
|
||||
but_LEFT = (switches_debounced & 0x02) != 0;
|
||||
but_DOWN = (switches_debounced & 0x04) != 0;
|
||||
but_UP = (switches_debounced & 0x08) != 0;
|
||||
but_SELECT = (switches_debounced & 0x10) != 0;
|
||||
|
||||
if (key == KeyEvent::Select) {
|
||||
if (sel_button_callback)
|
||||
sel_button_callback();
|
||||
} else {
|
||||
if (dir_button_callback)
|
||||
dir_button_callback();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class InterruptIn {
|
||||
public:
|
||||
InterruptIn(int reg) {
|
||||
// NOTE: INITIALIZER CODE WON'T RUN
|
||||
(void)reg;
|
||||
};
|
||||
void fall(Callback func) { sel_button_callback = func; };
|
||||
void mode(int v) { (void)v; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif /*__UI_mbed_H__*/
|
||||
+548
@@ -0,0 +1,548 @@
|
||||
// Projekat Tetris
|
||||
// by Vrnjak Lamija & Selimović Denis
|
||||
// Elektrotehnički fakultet Sarajevo
|
||||
|
||||
// clang-format off
|
||||
|
||||
//////// HACKED FOR PORTAPACK -- CHANGES HIGHLIGHTED
|
||||
int main();
|
||||
void pause_game();
|
||||
void Initialize(unsigned char c);
|
||||
void DeleteFigure();
|
||||
void DrawFigure();
|
||||
bool InCollisionDown(char delta);
|
||||
bool InCollisionLeft();
|
||||
bool InCollisionRight();
|
||||
//////// PORTAPACK
|
||||
|
||||
#include "mbed.h"
|
||||
#include "SPI_TFT_ILI9341.h"
|
||||
#include "Arial12x12.h"
|
||||
|
||||
#define dp23 P0_0
|
||||
|
||||
//////// PORTAPACK - DISABLED MOST CLASSES DUE TO GLOBAL OBJECT INITIALIZER ISSUE WITH EXTERNAL APPS:
|
||||
//deklaracija display-a
|
||||
//SPI_TFT_ILI9341 display(dp2, dp1, dp6, dp24, dp23, dp25, "TFT");
|
||||
//
|
||||
//analogni ulazi za joystick
|
||||
// AnalogIn VRx(dp11);
|
||||
// AnalogIn VRy(dp10);
|
||||
//
|
||||
// AnalogIn random(dp13); //analogni ulaz za generisanje random vrijednosti
|
||||
//////// PORTAPACK
|
||||
|
||||
//taster na joysticku za rotaciju
|
||||
InterruptIn taster(dp9);
|
||||
|
||||
//ticker za spustanje figure
|
||||
//timer za debouncing tastera na joysticku
|
||||
Ticker game, joystick;
|
||||
Timer debounceTaster;
|
||||
|
||||
unsigned char level = 0; //mora biti tipa usigned char jer inače se može desiti da level bude manji od 0, a i da ne trošimo memoriju
|
||||
const float delays[4] = {1.2, 0.7, 0.4, 0.25}; //svakih koliko se spusti jedan red, ovo provjeriti da li je presporo ili prebrzo, ovisi o levelu
|
||||
|
||||
//////// PORTAPACK - DELETED UNNEEDED JOYSTICK HYSTERESIS VARIABLES
|
||||
//char leftBoundary = 1, rightBoundary = 5, downBoundary = 1, upBoundary = 5;// sada je ovo tipa char
|
||||
//////// PORTAPACK
|
||||
|
||||
unsigned int score = 0; //stavio sam ovo unsigned int za veći opseg, mada je jako teško da se i int premaši, ali nmvz
|
||||
bool firstTime = true; //ako je prvi put, figura se crta u Tickeru
|
||||
bool gameStarted = false;
|
||||
unsigned char nextFigure = 1; //ovo je sad globalna varijabla, da bi mogli unaprijed generisati sljedeću figuru radi prikaza
|
||||
|
||||
//white - no figure
|
||||
//I - BLUE
|
||||
//O - YELLOW
|
||||
//T - PURPLE
|
||||
//S - GREEN
|
||||
//Z - RED
|
||||
//J - MAROON
|
||||
//L - ORANGE
|
||||
const int colors[8] = {White, Blue, Yellow, Purple, Green, Red, Maroon, Orange};
|
||||
const short DIMENSION = 16;
|
||||
const short DIMENSION_NEXT = 12;
|
||||
short board[20][10] = {0}; //matrica boja, 0 - 7 indeksi boja
|
||||
|
||||
short figuresX[7][4] = {{0,0,0,0}, {0,0,1,1}, {0,1,1,1}, {1,1,0,0}, {0,1,0,1}, {1,1,1,0}, {1,1,1,0}};
|
||||
short figuresY[7][4] = {{0,1,2,3}, {1,0,0,1}, {1,1,2,0}, {0,1,1,2}, {0,1,1,2}, {2,1,0,0}, {0,1,2,2}};
|
||||
|
||||
unsigned int GenerateRandomSeed() {
|
||||
//////// PORTAPACK - USE RTC FOR SEED
|
||||
return LPC_RTC->CTIME0;
|
||||
//////// PORTAPACK
|
||||
}
|
||||
|
||||
void Init() {
|
||||
//ovo su zajedničke osobine za sve prikaze na display-u
|
||||
//nikad se ne mijenjaju i pozvat ćemo je jednom prije petlje
|
||||
claim(stdout);
|
||||
set_orientation(2); // 2 ili 0, zavisi kako okrenemo display, provjerit ćemo na labu kako nam je najlakše povezat
|
||||
set_font((unsigned char*) Arial12x12);
|
||||
}
|
||||
|
||||
|
||||
void ShowScore() {
|
||||
//pomocna funkcija za prikazivanje score-a
|
||||
fillrect(165, 20, 235, 50, White); //popunimo pravugaonik da obrišemo stari score
|
||||
locate(200, 35); //valjda je na sredini pravougaonika
|
||||
printf("%d", score);
|
||||
}
|
||||
|
||||
void ShowNextFigure() {
|
||||
//prikaz sljedeće figure koristeći pomoćnu varijablu nextFigure
|
||||
fillrect(165, 70, 235, 120, White);
|
||||
int upperLeftX = 176, upperLeftY = 83;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
int x = upperLeftX + DIMENSION_NEXT * figuresY[nextFigure - 1][i], y = upperLeftY + DIMENSION_NEXT * figuresX[nextFigure - 1][i];
|
||||
fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]);
|
||||
rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
//funkcija za crtanje cursora za odabir levela
|
||||
void DrawCursor(int color, unsigned char lev) {
|
||||
fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color);
|
||||
}
|
||||
|
||||
// PORTAPACK - ADDED EXTRA LEVEL:
|
||||
void ShowLevelMenu() {
|
||||
//ovdje inicijalizujemo display
|
||||
cls(); // brišemo prethodno
|
||||
background(Black);
|
||||
foreground(White);
|
||||
locate(80, 50);
|
||||
printf("LEVEL 1");
|
||||
locate(80, 120);
|
||||
printf("LEVEL 2");
|
||||
locate(80, 190);
|
||||
printf("LEVEL 3");
|
||||
locate(80, 260);
|
||||
printf("LEVEL 4");
|
||||
DrawCursor(White, level);
|
||||
}
|
||||
//////// PORTAPACK
|
||||
|
||||
//////// PORTAPACK - USE BUTTONS VS JOYSTICK:
|
||||
void ReadJoystickForLevel(){
|
||||
unsigned char old = level;
|
||||
if(but_UP){
|
||||
(level == 0) ? level = 3 : level--;
|
||||
}
|
||||
else if(but_DOWN){
|
||||
//ne radi ona prethodna varijanta jer % vraća i negastivni rezultat
|
||||
//to što ne koristimo unsigned tip ne pomaže jer će doći do overflow-a
|
||||
level = (level + 1) % 4;
|
||||
}
|
||||
DrawCursor(Black, old); //na prethodni level popunimo bojom pozadine
|
||||
DrawCursor(White, level); //na novi level popunimo bijelom bojom - pozadina je crna
|
||||
//koristio sam fillrect, jer njega svakako moramo koristiti, jer možda budemo morali da brišemo fillcircle iz biblioteke
|
||||
}
|
||||
//////// PORTAPACK
|
||||
|
||||
void EndPlay() {
|
||||
joystick.detach();
|
||||
score = 0;
|
||||
firstTime = true;
|
||||
gameStarted = false;
|
||||
for(int i = 0; i < 20; i++) {
|
||||
for(int j = 0; j < 10; j++) {
|
||||
board[i][j] = 0;
|
||||
}
|
||||
}
|
||||
//////// PORTAPACK - FIX TO REINITIALIZE SCREEN
|
||||
// ShowLevelMenu();
|
||||
// joystick.attach(&ReadJoystickForLevel, 0.3);
|
||||
main();
|
||||
//////// PORTAPACK
|
||||
}
|
||||
|
||||
void StartGame()
|
||||
{
|
||||
cls(); // brišemo ShowLevelMenu
|
||||
background(White);
|
||||
foreground(Black);
|
||||
fillrect(0, 0, 160, 320, White);
|
||||
fillrect(160, 0, 240, 320, Black); //dio za prikazivanje rezultata će biti crni pravougaonik, a tabla je bijeli
|
||||
ShowScore();
|
||||
}
|
||||
|
||||
void copyCoordinates(short X[], short Y[], unsigned char index)
|
||||
{
|
||||
//umjesto one prošle metode za kopiranje, ova prima index, čisto da nam olakša život
|
||||
for(int i = 0; i < 4; i++) {
|
||||
X[i] = figuresX[index][i];
|
||||
Y[i] = figuresY[index][i];
|
||||
}
|
||||
}
|
||||
|
||||
bool BottomEdge(int x){
|
||||
return x > 19;
|
||||
}
|
||||
|
||||
bool LeftEdge(int y){
|
||||
return y < 0;
|
||||
}
|
||||
|
||||
bool RightEdge(int y){
|
||||
return y > 9;
|
||||
}
|
||||
|
||||
bool OutOfBounds(int y, int x){
|
||||
return y < 0 || y > 9 || x > 19;
|
||||
}
|
||||
|
||||
void PutBorders(short x, short y) {
|
||||
for(int i = x - 1; i <= x + 1; i++) {
|
||||
for(int j = y - 1; j <= y + 1; j++) {
|
||||
if(i < 0 || i > 9 || j < 0 || j > 19 || board[j][i] == 0) continue;
|
||||
rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////// PORTAPACK - ELIMINATED CLASSES DUE TO GLOBAL OBJECT INITIALIZATION ISSUE WITH EXTERNAL APPS:
|
||||
//class Tetromino{
|
||||
//private:
|
||||
short X[4];
|
||||
short Y[4];
|
||||
short boardX, boardY;
|
||||
unsigned char colorIndex;//dodao sam colorIndex zasad, jer nema drugog načina da popunimo matricu sa indeksima boja
|
||||
//ovo je najbezbolnija varijanta što se memorije tiče
|
||||
//public:
|
||||
// Tetromino(){
|
||||
// unsigned char r = rand() % 7 + 1;
|
||||
// Initialize(r);
|
||||
// }
|
||||
|
||||
void Tetromino(unsigned char c) {
|
||||
Initialize(c);
|
||||
}
|
||||
|
||||
void Initialize(unsigned char c) {
|
||||
colorIndex = c;
|
||||
boardX = 0;
|
||||
boardY = 4; //3,4 ili 5 najbolje da vidimo kad imamo display
|
||||
copyCoordinates(X, Y, c - 1);
|
||||
}
|
||||
|
||||
void Rotate(){
|
||||
short pivotX = X[1], pivotY = Y[1];
|
||||
//prvi elemnti u matrici su pivoti oko koje rotiramo
|
||||
|
||||
short newX[4]; //pozicije blokova nakon rotiranja ako sve bude ok
|
||||
short newY[4];
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
short tmp = X[i], tmp2 = Y[i];
|
||||
newX[i] = pivotX + pivotY - tmp2;
|
||||
newY[i] = tmp + pivotX - pivotY;
|
||||
|
||||
if(OutOfBounds(boardY + newY[i], boardX + newX[i]) || board[boardX + newX[i]][boardY + newY[i]] != 0) return;
|
||||
}
|
||||
DeleteFigure();
|
||||
for(int i = 0; i < 4; i++){
|
||||
X[i] = newX[i];
|
||||
Y[i] = newY[i];
|
||||
}
|
||||
DrawFigure();
|
||||
}
|
||||
|
||||
void DrawFigure() {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
//display je deklarisani display logy iz biblioteke one
|
||||
//računamo gornji lijevi pixel po x i y
|
||||
//donji desni dobijemo kad dodamo DIMENZIJU
|
||||
//stavio sam 16 za početak, možemo se opet skontati na labu
|
||||
//ovo pretpostavlja da nema margina, mogu se lagano dodati uz neku konstantu kao offset
|
||||
int upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
|
||||
fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]);
|
||||
//ovo boji granice blokova u crno, možemo skloniti ako ti se ne sviđa
|
||||
rect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteFigure() {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
//ista logika kao u DrawFigure, samo popunjavamo sve blokove sa bijelim pravougaonicima
|
||||
short upperLeftX = (boardX + X[i]) * DIMENSION, upperLeftY = (boardY + Y[i]) * DIMENSION;
|
||||
fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, White);
|
||||
PutBorders(upperLeftY, upperLeftX);
|
||||
}
|
||||
}
|
||||
|
||||
void OnAttached() {
|
||||
//metoda se poziva kad figura prestanje da se krece
|
||||
//popunimo matricu indeksom boje
|
||||
for(int i = 0; i < 4; i++) {
|
||||
board[boardX + X[i]][boardY + Y[i]] = colorIndex;
|
||||
//btw board bi mogao biti niz tipa unsigned char, ali to ćemo vidjet kasnije
|
||||
}
|
||||
}
|
||||
|
||||
bool MoveDown(char delta = 1){
|
||||
if(!InCollisionDown(delta)){
|
||||
DeleteFigure();
|
||||
boardX+=delta;
|
||||
DrawFigure();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MoveLeft(){
|
||||
if(!InCollisionLeft()){
|
||||
DeleteFigure();
|
||||
boardY--;
|
||||
DrawFigure();
|
||||
}
|
||||
}
|
||||
|
||||
void MoveRight(){
|
||||
if(!InCollisionRight()){
|
||||
DeleteFigure();
|
||||
boardY++;
|
||||
DrawFigure();
|
||||
}
|
||||
}
|
||||
|
||||
void SoftDrop() {
|
||||
//ovo je funkcija za soft drop
|
||||
//obrisemo figuru, postavimo novu poziciju dva reda ispod, nacrtamo figuru
|
||||
DeleteFigure();
|
||||
MoveDown(2);
|
||||
DrawFigure();
|
||||
//treba još vidjeti koje izmjene u tickeru trebaju
|
||||
score += 2 * (level +1); //prema igrici koju smo igrali, dobije se 14 poena kad se uradi hard drop
|
||||
ShowScore();
|
||||
}
|
||||
|
||||
|
||||
bool InCollisionDown(char delta = 1){
|
||||
int newX, newY; //da bi bilo citljivije
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
newX = boardX + X[i] + delta;
|
||||
newY = boardY + Y[i];
|
||||
|
||||
if(BottomEdge(newX) || board[newX][newY] != 0){
|
||||
return true;
|
||||
}
|
||||
//jedna figura je u koliziji ako
|
||||
//pozicija na koju zelimo da pomjerimo bilo koji blok dotakne dno ili lijevu ili desnu ivicu ekrana
|
||||
//ili ako je pozicija na koju zelimo da pomjerimo bilo koji od blokova vec zauzeta a da nije dio figure prije pomijeranja
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InCollisionLeft(){
|
||||
int newX, newY;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
newX = boardX + X[i];
|
||||
newY = boardY + Y[i] - 1;
|
||||
|
||||
if(LeftEdge(newY) || board[newX][newY] != 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool InCollisionRight(){
|
||||
int newX, newY;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
newX = boardX + X[i];
|
||||
newY = boardY + Y[i] + 1;
|
||||
|
||||
if(RightEdge(newY) || board[newX][newY] != 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// };
|
||||
|
||||
// Tetromino currentTetromino;
|
||||
|
||||
//////// PORTAPACK - USE BUTTONS VS JOYSTICK, AND ADDED PAUSE FEATURE:
|
||||
void ReadJoystickForFigure() {
|
||||
if(but_LEFT) {
|
||||
MoveLeft();
|
||||
}
|
||||
else if(but_RIGHT) {
|
||||
MoveRight();
|
||||
}
|
||||
else if(but_UP) {
|
||||
pause_game();
|
||||
}
|
||||
else if(but_DOWN) {
|
||||
SoftDrop();
|
||||
}
|
||||
}
|
||||
//////// PORTAPACK
|
||||
|
||||
void CheckLines(short &firstLine, short &numberOfLines)
|
||||
{
|
||||
//vraća preko reference prvu liniju koja je popunjena do kraja, kao i takvih linija
|
||||
firstLine = -1; //postavljen na -1 jer ako nema linija koje treba brisati ispod u UpdateBoard neće se ući u petlju
|
||||
numberOfLines = 0;
|
||||
for(int i = 19; i >= 0; i--) {
|
||||
short temp = 0;
|
||||
for(int j = 0; j < 10; j++) {
|
||||
if(board[i][j] == 0) {
|
||||
if(numberOfLines > 0) return;
|
||||
break;
|
||||
}//ako je makar jedna bijela, prekida se brojanje
|
||||
temp++;
|
||||
}
|
||||
if(temp == 10) { //ako je temo došao do 10, niti jedna bijela - puna linija
|
||||
numberOfLines++;
|
||||
if(firstLine == -1) firstLine = i; //ovo mijenjamo samo prvi put
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int UpdateScore (short numOfLines){
|
||||
unsigned int newIncrement = 0;
|
||||
switch(numOfLines) {
|
||||
case 1 : newIncrement = 40; break;
|
||||
case 2 : newIncrement = 100; break;
|
||||
case 3 : newIncrement = 300; break;
|
||||
case 4 : newIncrement = 1200; break;
|
||||
default : newIncrement = 0; break; //update funkcije za score, još sam vratio ovo na 0
|
||||
}
|
||||
return newIncrement * (level + 1);
|
||||
}
|
||||
|
||||
void UpdateBoard()
|
||||
{
|
||||
short firstLine, numberOfLines;
|
||||
//pozivamo funkciju
|
||||
do {
|
||||
CheckLines(firstLine, numberOfLines);
|
||||
for(int i = firstLine; i >= numberOfLines; i--) {
|
||||
for(int j = 0; j < 10; j++) {
|
||||
board[i][j] = board[i - numberOfLines][j];
|
||||
board[i - numberOfLines][j] = 0;
|
||||
short tmp = i - numberOfLines;
|
||||
fillrect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , colors[board[i][j]]); // bojimo novi blok
|
||||
fillrect( j * DIMENSION, tmp * DIMENSION,(j + 1) * DIMENSION, (tmp + 1) * DIMENSION , White);
|
||||
if(board[i][j] != 0)
|
||||
rect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , Black);
|
||||
}
|
||||
}
|
||||
score += UpdateScore(numberOfLines);
|
||||
}
|
||||
while(numberOfLines != 0); //ovdje se mijenja globalna varijabla score
|
||||
}
|
||||
|
||||
bool IsOver() {
|
||||
for(int i = 0; i < 10; i++) {
|
||||
if(board[0][i] != 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShowGameOverScreen() {
|
||||
//////// PORTAPACK - SKIP CLS
|
||||
// cls();
|
||||
// background(Black);
|
||||
// foreground(White);
|
||||
background(White);
|
||||
foreground(Black);
|
||||
//////// PORTAPACK
|
||||
locate(60, 120);
|
||||
printf("GAME OVER");
|
||||
locate(40, 150);
|
||||
printf("YOUR SCORE IS %d", score);
|
||||
wait(5); //ovaj prikaz traje 3s (možemo mijenjati) a nakon toga se ponovo prikazuje meni sa levelima
|
||||
}
|
||||
|
||||
void InitGame() {
|
||||
if(firstTime) {
|
||||
Tetromino(rand() % 7 + 1);
|
||||
DrawFigure();
|
||||
nextFigure = rand() % 7 + 1;
|
||||
ShowNextFigure();
|
||||
firstTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayGame(){
|
||||
InitGame();
|
||||
if(!MoveDown()){
|
||||
OnAttached();
|
||||
UpdateBoard();
|
||||
ShowScore();
|
||||
|
||||
Tetromino(nextFigure);
|
||||
DrawFigure();
|
||||
nextFigure = rand() % 7 + 1;
|
||||
ShowNextFigure();
|
||||
if(IsOver()) {
|
||||
//ako je igra završena brišemo sve sa displey-a, prikazujemo poruku i score
|
||||
//takođe moramo dettach-at ticker
|
||||
game.detach();
|
||||
ShowGameOverScreen(); //prikaz da je kraj igre, ima wait od 3s
|
||||
EndPlay();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void OnTasterPressed(){
|
||||
if(debounceTaster.read_ms() > 200) {
|
||||
if(gameStarted){
|
||||
Rotate();
|
||||
}
|
||||
else{
|
||||
joystick.detach();
|
||||
gameStarted = true;
|
||||
StartGame(); //pocinje igra, prikazuje se tabla
|
||||
joystick.attach(&ReadJoystickForFigure, 0.3);
|
||||
game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
|
||||
}
|
||||
debounceTaster.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SetTaster() {
|
||||
taster.mode(PullUp); //mora se aktivirati pull up otpornik na tasteru joystick-a
|
||||
taster.fall(&OnTasterPressed);
|
||||
debounceTaster.reset();
|
||||
debounceTaster.start();
|
||||
}
|
||||
|
||||
int main() {
|
||||
srand(GenerateRandomSeed());
|
||||
Init();
|
||||
ShowLevelMenu();
|
||||
joystick.attach(&ReadJoystickForLevel, 0.3);
|
||||
SetTaster();
|
||||
|
||||
//////// PORTAPACK: CAN'T HANG HERE IN MAYHEM OR WE WON'T GET ANY CALLBACKS
|
||||
// while(1);
|
||||
return 0;
|
||||
//////// PORTAPACK
|
||||
}
|
||||
|
||||
//////// PORTAPACK - ADDED PAUSE FEATURE:
|
||||
void pause_game() {
|
||||
game.detach();
|
||||
joystick.detach();
|
||||
locate(180, 200);
|
||||
printf("PAUSED");
|
||||
while ((get_switches_state().to_ulong() & 0x10) == 0); // wait for SELECT button to resume
|
||||
printf(" ");
|
||||
joystick.attach(&ReadJoystickForFigure, 0.3);
|
||||
game.attach(&PlayGame, delays[level]); //svakih nekoliko spusta figuru jedan red nize
|
||||
};
|
||||
//////// PORTAPACK
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* 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_tetris.hpp"
|
||||
|
||||
namespace ui::external_app::tetris {
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
// external code, so ignore warnings
|
||||
#pragma GCC diagnostic ignored "-Weffc++"
|
||||
#include "tetris.cpp"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
TetrisView::TetrisView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&dummy});
|
||||
}
|
||||
|
||||
void TetrisView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
main();
|
||||
}
|
||||
}
|
||||
|
||||
void TetrisView::frame_sync() {
|
||||
check_fall_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool TetrisView::on_encoder(const EncoderEvent delta) {
|
||||
return check_encoder(delta);
|
||||
}
|
||||
|
||||
bool TetrisView::on_key(const KeyEvent key) {
|
||||
return check_key(key);
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::tetris
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* 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_TETRIS_H__
|
||||
#define __UI_TETRIS_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "limits.h"
|
||||
|
||||
namespace ui::external_app::tetris {
|
||||
|
||||
class TetrisView : public View {
|
||||
public:
|
||||
TetrisView(NavigationView& nav);
|
||||
|
||||
std::string title() const override { return "Tetris"; };
|
||||
|
||||
void focus() override { dummy.focus(); };
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::tetris
|
||||
|
||||
#endif /*__UI_TETRIS_H__*/
|
||||
@@ -24,8 +24,11 @@
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
uint8_t Debounce::state() {
|
||||
bool v = state_to_report_;
|
||||
uint8_t v = state_to_report_;
|
||||
simulated_pulse_ = false;
|
||||
return v;
|
||||
}
|
||||
@@ -149,3 +152,40 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t EncoderDebounce::state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
uint8_t EncoderDebounce::rotation_rate() {
|
||||
return last_rotation_rate_;
|
||||
}
|
||||
|
||||
// Returns TRUE if encoder position phase bits changed (after debouncing)
|
||||
bool EncoderDebounce::feed(const uint8_t phase_bits) {
|
||||
history_ = (history_ << 2) | phase_bits;
|
||||
|
||||
// If both inputs have been stable for the last 4 ticks, history_ should equal 0x00, 0x55, 0xAA, or 0xFF.
|
||||
uint8_t expected_stable_history = phase_bits * 0b01010101;
|
||||
|
||||
// But, checking for equal seems to cause issues with at least 1 user's encoder, so we're treating the input
|
||||
// as "stable" if at least ONE input bit is consistent for 4 ticks...
|
||||
uint8_t diff = (history_ ^ expected_stable_history);
|
||||
if (((diff & 0b01010101) == 0) || ((diff & 0b10101010) == 0)) {
|
||||
// Has the debounced input value changed?
|
||||
if (state_ != phase_bits) {
|
||||
state_ = phase_bits;
|
||||
|
||||
// Rate multiplier is for larger delta increments when dial is rotated rapidly.
|
||||
last_rotation_rate_ = rotation_rate_downcounter_;
|
||||
rotation_rate_downcounter_ = portapack::persistent_memory::encoder_rate_multiplier();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unstable input, or no change.
|
||||
// Decrement rotation rate detector once per timer tick.
|
||||
if (rotation_rate_downcounter_ > 1)
|
||||
rotation_rate_downcounter_--;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -64,4 +65,21 @@ class Debounce {
|
||||
bool long_press_occurred_{false}; // TRUE when button is being held down and LONG_PRESS_DELAY has been reached (only when long_press_enabled)
|
||||
};
|
||||
|
||||
class EncoderDebounce {
|
||||
public:
|
||||
bool feed(const uint8_t phase_bits); // returns TRUE if state changed after debouncing
|
||||
|
||||
uint8_t state(); // returns debounced phase bits from encoder
|
||||
|
||||
uint8_t rotation_rate(); // returns last rotation rate
|
||||
|
||||
private:
|
||||
uint8_t history_{0}; // shift register of previous reads from encoder
|
||||
|
||||
uint8_t state_{0}; // actual encoder output state (after debounce logic)
|
||||
|
||||
uint8_t last_rotation_rate_{1};
|
||||
uint8_t rotation_rate_downcounter_{1}; // down-counter to estimate rotation speed
|
||||
};
|
||||
|
||||
#endif /*__DEBOUNCE_H__*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -26,82 +27,51 @@
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
// Now supporting multiple levels of rotary encoder dial sensitivity
|
||||
//
|
||||
// Portapack H2 normally has a 30-step encoder, meaning one step (pulse) every
|
||||
// 12 degrees of rotation.
|
||||
//
|
||||
// For each encoder "pulse" there are 4 state transitions, and we can choose
|
||||
// between looking at all of them (high sensitivity), half of them (medium/default),
|
||||
// or one quarter of them (low sensitivity).
|
||||
static const int8_t transition_map[][16] = {
|
||||
// Normal (Medium) Sensitivity -- default
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// Low Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
0, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
0, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// High Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// Transition map for rotary encoder phase bits
|
||||
// 00 -> 01 -> 11 -> 10 cw
|
||||
// 00 -> 10 -> 11 -> 01 ccw
|
||||
// NB: Bits are swapped versus older FW versions
|
||||
static const int8_t transition_map[16] = {
|
||||
0, // 00->00: noop
|
||||
1, // 00->01: cw start
|
||||
-1, // 00->10: ccw start
|
||||
0, // 00->11: rate
|
||||
-1, // 01->00: ccw end
|
||||
0, // 01->01: noop
|
||||
0, // 01->10: rate
|
||||
1, // 01->11: cw end
|
||||
1, // 10->00: cw end
|
||||
0, // 10->01: rate
|
||||
0, // 10->10: noop
|
||||
-1, // 10->11: ccw end
|
||||
0, // 11->00: rate
|
||||
-1, // 11->01: ccw start
|
||||
1, // 11->10: cw start
|
||||
0, // 11->11: noop
|
||||
};
|
||||
|
||||
int_fast8_t Encoder::update(
|
||||
const uint_fast8_t phase_0,
|
||||
const uint_fast8_t phase_1) {
|
||||
state <<= 1;
|
||||
state |= phase_0;
|
||||
state <<= 1;
|
||||
state |= phase_1;
|
||||
// Rotary encoder dial sensitivity (transition ignored if bit is 0)
|
||||
static const uint16_t sensitivity_map[] = {
|
||||
0x0990, // DIAL_SENSITIVITY_NORMAL
|
||||
0x0110, // DIAL_SENSITIVITY_LOW
|
||||
0x6996, // DIAL_SENSITIVITY_HIGH
|
||||
};
|
||||
|
||||
// dial sensitivity setting is stored in pmem
|
||||
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
|
||||
int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
|
||||
state = ((state << 2) | phase_bits) & 0x0F;
|
||||
|
||||
int_fast8_t direction = transition_map[state];
|
||||
|
||||
// Require 2 state changes in same direction to register movement -- for additional level of contact switch debouncing
|
||||
if (direction == prev_direction) {
|
||||
if ((sensitivity_map[portapack::persistent_memory::encoder_dial_sensitivity()] & (1 << state)) == 0)
|
||||
return 0;
|
||||
return direction;
|
||||
}
|
||||
|
||||
// It's normal for transition map to return 0 between every +1/-1, so discarding those
|
||||
if (direction != 0)
|
||||
prev_direction = direction;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -26,12 +27,11 @@
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
int_fast8_t update(
|
||||
const uint_fast8_t phase_0,
|
||||
const uint_fast8_t phase_1);
|
||||
int_fast8_t update(const uint_fast8_t phase_bits);
|
||||
|
||||
private:
|
||||
uint_fast8_t state{0};
|
||||
int_fast8_t prev_direction{0};
|
||||
};
|
||||
|
||||
#endif /*__ENCODER_H__*/
|
||||
|
||||
@@ -149,7 +149,33 @@ void MAX2837::init() {
|
||||
set_mode(Mode::Standby);
|
||||
}
|
||||
|
||||
enum class Mask {
|
||||
void MAX2837::set_tx_LO_iq_phase_calibration(const size_t v) {
|
||||
/* IQ phase deg CAL adj (+4 ...-4) in 32 steps (5 bits), 00000 = +4deg (Q lags I by 94degs, default), 01111 = +0deg, 11111 = -4deg (Q lags I by 86degs) */
|
||||
|
||||
// TX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,0,1 (5dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
|
||||
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
|
||||
|
||||
gpio_max283x_enable.output();
|
||||
gpio_max2837_rxenable.output();
|
||||
gpio_max2837_txenable.output();
|
||||
|
||||
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
_map.r.tx_lo_iq.TXLO_IQ_SPI_EN = 1; // reg 30 D5, TX LO I/Q Phase SPI Adjust. Active when Address 30 D5 (TXLO_IQ_SPI_EN) = 1.
|
||||
_map.r.tx_lo_iq.TXLO_IQ_SPI = v; // reg 30 D4:D0, TX LO I/Q Phase SPI Adjust.
|
||||
flush_one(Register::TX_LO_IQ);
|
||||
|
||||
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
|
||||
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (0 = Normal operation (default)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (1 = Chip select enable )
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
set_mode(Mode::Standby); // Back 3 logic pins CALIBRATION mode -> Standby.
|
||||
}
|
||||
|
||||
enum class Mask { // There are class Mask ,and class mode with same names, but they are not same.
|
||||
Enable = 0b001,
|
||||
RxEnable = 0b010,
|
||||
TxEnable = 0b100,
|
||||
@@ -157,9 +183,11 @@ enum class Mask {
|
||||
Standby = Enable,
|
||||
Receive = Enable | RxEnable,
|
||||
Transmit = Enable | TxEnable,
|
||||
Rx_calibration = Enable | RxEnable, // sets the same 3 x logic pins to the Receive operating mode.
|
||||
Tx_calibration = Enable | TxEnable, // sets the same 3 x logic pins to the Transmit operating mode.
|
||||
};
|
||||
|
||||
Mask mode_mask(const Mode mode) {
|
||||
Mask mode_mask(const Mode mode) { // based on enum Mode cases, we set up the correct 3 logic PINS .
|
||||
switch (mode) {
|
||||
case Mode::Standby:
|
||||
return Mask::Standby;
|
||||
@@ -167,12 +195,16 @@ Mask mode_mask(const Mode mode) {
|
||||
return Mask::Receive;
|
||||
case Mode::Transmit:
|
||||
return Mask::Transmit;
|
||||
case Mode::Rx_Calibration: // Let's add those two CAL logic pin settings- Rx and Tx calibration modes.
|
||||
return Mask::Rx_calibration; // same logic pins as Receive mode = Enable | RxEnable, (the difference is in Reg add 16 D1:DO)
|
||||
case Mode::Tx_Calibration: // Let's add this CAL Tx calibration mode = Transmit.
|
||||
return Mask::Tx_calibration; // same logic pins as Transmit = Enable | TxEnable,(the difference is in Reg add 16 D1:DO)
|
||||
default:
|
||||
return Mask::Shutdown;
|
||||
}
|
||||
}
|
||||
|
||||
void MAX2837::set_mode(const Mode mode) {
|
||||
void MAX2837::set_mode(const Mode mode) { // We set up the 3 Logic Pins ENABLE, RXENABLE, TXENABLE accordingly to the max2837 mode case, that we want to set up .
|
||||
Mask mask = mode_mask(mode);
|
||||
gpio_max283x_enable.write(toUType(mask) & toUType(Mask::Enable));
|
||||
gpio_max2837_rxenable.write(toUType(mask) & toUType(Mask::RxEnable));
|
||||
|
||||
@@ -829,6 +829,7 @@ class MAX2837 : public MAX283x {
|
||||
bool set_frequency(const rf::Frequency lo_frequency) override;
|
||||
|
||||
void set_rx_lo_iq_calibration(const size_t v) override;
|
||||
void set_tx_LO_iq_phase_calibration(const size_t v) override;
|
||||
void set_rx_bias_trim(const size_t v);
|
||||
void set_vco_bias(const size_t v);
|
||||
void set_rx_buff_vcm(const size_t v) override;
|
||||
|
||||
@@ -142,6 +142,31 @@ void MAX2839::init() {
|
||||
set_mode(Mode::Standby);
|
||||
}
|
||||
|
||||
void MAX2839::set_tx_LO_iq_phase_calibration(const size_t v) {
|
||||
/* IQ phase deg CAL adj (+4 ...-4) This IC in 64 steps (6 bits), 000000 = +4deg (Q lags I by 94degs, default), 011111 = +0deg, 111111 = -4deg (Q lags I by 86degs) */
|
||||
|
||||
// TX calibration , 2 x Logic pins , ENABLE, RXENABLE = 1,0, (2dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
|
||||
set_mode(Mode::Tx_Calibration); // write to ram 3 LOGIC Pins .
|
||||
|
||||
gpio_max283x_enable.output(); // max2839 has only 2 x pins + regs to decide mode.
|
||||
gpio_max2839_rxtx.output(); // Here is combined rx & tx pin in one port.
|
||||
|
||||
_map.r.spi_en.CAL_SPI = 1; // Register Settings reg address 16, D1 (CAL mode 1)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (CHIP ENABLE 1)
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
_map.r.pa_drv.TXLO_IQ_SPI_EN = 1; // reg 27 D6, TX LO I/Q Phase SPI Adjust. Active when Address 27 D6 (TXLO_IQ_SPI_EN) = 1.
|
||||
_map.r.pa_drv.TXLO_IQ_SPI = v; // reg 27 D5:D0 6 bits, TX LO I/Q Phase SPI Adjust.
|
||||
flush_one(Register::PA_DRV);
|
||||
|
||||
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
|
||||
_map.r.spi_en.CAL_SPI = 0; // Register Settings reg address 16, D1 (0 = Normal operation (default)
|
||||
_map.r.spi_en.EN_SPI = 1; // Register Settings reg address 16, DO (1 = Chip select enable )
|
||||
flush_one(Register::SPI_EN);
|
||||
|
||||
set_mode(Mode::Standby); // Back 3 logic pins CALIBRATION mode -> Standby.
|
||||
}
|
||||
|
||||
enum class Mask {
|
||||
Enable = 0b01,
|
||||
RxTx = 0b10,
|
||||
@@ -149,6 +174,8 @@ enum class Mask {
|
||||
Standby = RxTx,
|
||||
Receive = Enable | RxTx,
|
||||
Transmit = Enable,
|
||||
Rx_calibration = Enable | RxTx, // sets the same 2 x logic pins to the Receive operating mode.
|
||||
Tx_calibration = Enable, // sets the same 2 x logic pins to the Transmit operating mode.
|
||||
};
|
||||
|
||||
Mask mode_mask(const Mode mode) {
|
||||
@@ -159,6 +186,10 @@ Mask mode_mask(const Mode mode) {
|
||||
return Mask::Receive;
|
||||
case Mode::Transmit:
|
||||
return Mask::Transmit;
|
||||
case Mode::Rx_Calibration: // Let's add this not used previously Rx calibration mode.
|
||||
return Mask::Rx_calibration; // same logic pins as Receive mode = Enable | RxTx, ,(the difference is in Regs )
|
||||
case Mode::Tx_Calibration: // Let's add this not used previously Tx calibration mode.
|
||||
return Mask::Tx_calibration; // same logic pins as Transmit = Enable , (the difference is in Reg add 16 D1:DO)
|
||||
default:
|
||||
return Mask::Shutdown;
|
||||
}
|
||||
|
||||
@@ -690,6 +690,7 @@ class MAX2839 : public MAX283x {
|
||||
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_tx_LO_iq_phase_calibration(const size_t v) override;
|
||||
void set_rx_buff_vcm(const size_t v) override;
|
||||
|
||||
int8_t temp_sense() override;
|
||||
|
||||
@@ -33,10 +33,10 @@ namespace max283x {
|
||||
namespace lo {
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 4> band{{
|
||||
{2300000000, 2400000000},
|
||||
{2170000000, 2400000000},
|
||||
{2400000000, 2500000000},
|
||||
{2500000000, 2600000000},
|
||||
{2600000000, 2700000000},
|
||||
{2600000000, 2740000000},
|
||||
}};
|
||||
|
||||
} /* namespace lo */
|
||||
@@ -48,11 +48,6 @@ namespace lna {
|
||||
constexpr range_t<int8_t> gain_db_range{0, 40};
|
||||
constexpr int8_t gain_db_step = 8;
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 2> band{{
|
||||
{2300000000, 2500000000},
|
||||
{2500000000, 2700000000},
|
||||
}};
|
||||
|
||||
} /* namespace lna */
|
||||
|
||||
/*************************************************************************/
|
||||
@@ -103,11 +98,13 @@ constexpr auto bandwidth_maximum = bandwidths[bandwidths.size() - 1];
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
enum Mode {
|
||||
enum Mode { // MAX283x Operating modes.
|
||||
Shutdown,
|
||||
Standby,
|
||||
Receive,
|
||||
Transmit,
|
||||
Rx_Calibration, // just add the sequential enum of those two CAL operating modes .
|
||||
Tx_Calibration,
|
||||
};
|
||||
|
||||
using reg_t = uint16_t;
|
||||
@@ -129,6 +126,8 @@ class MAX283x {
|
||||
virtual bool set_frequency(const rf::Frequency lo_frequency);
|
||||
|
||||
virtual void set_rx_lo_iq_calibration(const size_t v);
|
||||
virtual void set_tx_LO_iq_phase_calibration(const size_t v);
|
||||
|
||||
virtual void set_rx_buff_vcm(const size_t v);
|
||||
|
||||
virtual int8_t temp_sense();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* copyleft (ɔ) 2023 zxkmm under GPL license
|
||||
* Copyright (C) 2023 u-foka
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* copyleft (ɔ) 2023 zxkmm under GPL license
|
||||
* Copyright (C) 2023 u-foka
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -27,8 +28,10 @@ bool WAVFileReader::open(const std::filesystem::path& path) {
|
||||
size_t i = 0;
|
||||
char ch;
|
||||
const uint8_t tag_INAM[4] = {'I', 'N', 'A', 'M'};
|
||||
char title_buffer[32];
|
||||
uint32_t riff_size, data_end, title_size;
|
||||
const uint8_t tag_data[4] = {'d', 'a', 't', 'a'};
|
||||
struct data_t data_header;
|
||||
char title_buffer[32]{0};
|
||||
uint32_t riff_size, inam_search_start, title_size;
|
||||
size_t search_limit = 0;
|
||||
|
||||
// Already open ?
|
||||
@@ -37,19 +40,42 @@ bool WAVFileReader::open(const std::filesystem::path& path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reinitialize to avoid old data when switching files
|
||||
title_string = "";
|
||||
sample_rate_ = 0;
|
||||
bytes_per_sample = 0;
|
||||
|
||||
auto error = file_.open(path);
|
||||
|
||||
if (!error.is_valid()) {
|
||||
file_.read((void*)&header, sizeof(header)); // Read header (RIFF and WAVE)
|
||||
if (!file_.read((void*)&header, sizeof(header)).is_ok()) // Read header (RIFF and WAVE)
|
||||
return false;
|
||||
|
||||
riff_size = header.cksize + 8;
|
||||
data_start = header.fmt.cksize + 28;
|
||||
data_size_ = header.data.cksize;
|
||||
data_end = data_start + data_size_ + 1;
|
||||
// Assuming here that RIFF & WAV & fmt chunk ID's are all correct...
|
||||
riff_size = 8 + header.cksize;
|
||||
|
||||
// check for the "data" chunk ID
|
||||
if (memcmp(header.data.ckID, tag_data, 4) == 0) {
|
||||
data_start = 20 + header.fmt.cksize + 8;
|
||||
data_size_ = header.data.cksize;
|
||||
inam_search_start = data_start + data_size_;
|
||||
} else {
|
||||
// data header wasn't where we guessed; skip over one unexpected chunk (perhaps LIST/INFO/INAM)
|
||||
inam_search_start = 20 + header.fmt.cksize;
|
||||
file_.seek(20 + header.fmt.cksize + 8 + header.data.cksize);
|
||||
if (!file_.read((void*)&data_header, sizeof(data_header)).is_ok())
|
||||
return false;
|
||||
|
||||
if (memcmp(data_header.ckID, tag_data, 4) == 0) {
|
||||
data_start = 20 + header.fmt.cksize + 8 + header.data.cksize + 8;
|
||||
data_size_ = data_header.cksize;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for INAM (title) tag
|
||||
if (data_end < riff_size) {
|
||||
file_.seek(data_end);
|
||||
if (inam_search_start < riff_size) {
|
||||
file_.seek(inam_search_start);
|
||||
while (file_.read((void*)&ch, 1).is_ok()) {
|
||||
if (ch == tag_INAM[i++]) {
|
||||
if (i == 4) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -43,7 +44,7 @@ static Thread* thread_controls_event = NULL;
|
||||
|
||||
// Index with the Switch enum.
|
||||
static std::array<Debounce, 6> switch_debounce;
|
||||
static std::array<Debounce, 2> encoder_debounce;
|
||||
static EncoderDebounce encoder_debounce;
|
||||
|
||||
static_assert(std::size(switch_debounce) == toUType(Switch::Dfu) + 1);
|
||||
|
||||
@@ -162,16 +163,11 @@ static bool switches_update(const uint8_t raw) {
|
||||
}
|
||||
|
||||
static bool encoder_update(const uint8_t raw) {
|
||||
bool encoder_changed = false;
|
||||
|
||||
encoder_changed |= encoder_debounce[0].feed((raw >> 6) & 0x01);
|
||||
encoder_changed |= encoder_debounce[1].feed((raw >> 7) & 0x01);
|
||||
|
||||
return encoder_changed;
|
||||
return encoder_debounce.feed(raw >> 6);
|
||||
}
|
||||
|
||||
static bool encoder_read() {
|
||||
auto delta = encoder.update(encoder_debounce[0].state(), encoder_debounce[1].state());
|
||||
auto delta = encoder.update(encoder_debounce.state()) * encoder_debounce.rotation_rate();
|
||||
|
||||
if (injected_encoder > 0) {
|
||||
if (injected_encoder == 1) delta = -1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -162,6 +162,8 @@ static void event_loop() {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
first_if.init(); /* To avoid initial short Ant_DC_Bias pulse ,we need quick set up GP01_RFF507X =1 */
|
||||
|
||||
if (config_mode_should_enter()) {
|
||||
config_mode_clear();
|
||||
config_mode_run();
|
||||
@@ -169,25 +171,30 @@ int main(void) {
|
||||
|
||||
config_mode_set();
|
||||
|
||||
first_if.init(); /* To avoid initial short Ant_DC_Bias pulse ,we need quick set up GP01_RFF507X =1 */
|
||||
if (portapack::init()) {
|
||||
portapack::display.init();
|
||||
config_mode_clear();
|
||||
switch (portapack::init()) {
|
||||
case portapack::init_status_t::INIT_HACKRF_CPLD_FAILED:
|
||||
portapack::init_error = "HACKRF CPLD FAILED";
|
||||
[[fallthrough]];
|
||||
|
||||
// sdcStart(&SDCD1, nullptr); // Commented out as now happens in portapack.cpp
|
||||
case portapack::init_status_t::INIT_SUCCESS:
|
||||
|
||||
// controls_init(); // Commented out as now happens in portapack.cpp
|
||||
lcd_frame_sync_configure();
|
||||
rtc_interrupt_enable();
|
||||
config_mode_clear();
|
||||
|
||||
event_loop();
|
||||
lcd_frame_sync_configure();
|
||||
rtc_interrupt_enable();
|
||||
|
||||
sdcDisconnect(&SDCD1);
|
||||
sdcStop(&SDCD1);
|
||||
event_loop();
|
||||
|
||||
portapack::shutdown();
|
||||
} else {
|
||||
config_mode_clear();
|
||||
sdcDisconnect(&SDCD1);
|
||||
sdcStop(&SDCD1);
|
||||
|
||||
portapack::shutdown();
|
||||
break;
|
||||
|
||||
case portapack::init_status_t::INIT_NO_PORTAPACK:
|
||||
case portapack::init_status_t::INIT_PORTAPACK_CPLD_FAILED:
|
||||
config_mode_clear();
|
||||
break;
|
||||
}
|
||||
|
||||
m4_init(portapack::spi_flash::image_tag_hackrf, portapack::memory::map::m4_code_hackrf, true);
|
||||
|
||||
+201
-131
@@ -28,6 +28,7 @@
|
||||
#include "hackrf_hal.hpp"
|
||||
#include "hackrf_gpio.hpp"
|
||||
using namespace hackrf::one;
|
||||
#include "jtag_target_gpio.hpp"
|
||||
|
||||
#include "clock_manager.hpp"
|
||||
#include "event_m0.hpp"
|
||||
@@ -50,9 +51,12 @@ using asahi_kasei::ak4951::AK4951;
|
||||
#include "file.hpp"
|
||||
#include "sd_card.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "bitmap.hpp"
|
||||
|
||||
namespace portapack {
|
||||
|
||||
const char* init_error = nullptr;
|
||||
|
||||
portapack::IO io{
|
||||
portapack::gpio_dir,
|
||||
portapack::gpio_lcd_rdx,
|
||||
@@ -172,50 +176,21 @@ constexpr I2CConfig i2c_config_fast_clock{
|
||||
enum class PortaPackModel {
|
||||
R1_20150901,
|
||||
R2_20170522,
|
||||
AGM,
|
||||
AUTODETECT,
|
||||
};
|
||||
|
||||
static bool save_config(int8_t value) {
|
||||
persistent_memory::set_config_cpld(value);
|
||||
if (sd_card::status() == sd_card::Status::Mounted) {
|
||||
ensure_directory("/hardware");
|
||||
File file;
|
||||
auto sucess = file.create("/hardware/settings.txt");
|
||||
if (!sucess.is_valid()) {
|
||||
file.write_line(to_string_dec_uint(value));
|
||||
}
|
||||
}
|
||||
portapack::persistent_memory::cache::persist();
|
||||
return true;
|
||||
}
|
||||
|
||||
int read_file(std::string name) {
|
||||
std::string return_string = "";
|
||||
File file;
|
||||
auto success = file.open(name);
|
||||
|
||||
if (!success.is_valid()) {
|
||||
char one_char[1];
|
||||
for (size_t pointer = 0; pointer < file.size(); pointer++) {
|
||||
file.seek(pointer);
|
||||
file.read(one_char, 1);
|
||||
return_string += one_char[0];
|
||||
}
|
||||
return std::stoi(return_string);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int load_config() {
|
||||
static Optional<int> config_value;
|
||||
if (!config_value.is_valid()) {
|
||||
int8_t value = portapack::persistent_memory::config_cpld();
|
||||
if ((value <= 0 || value >= 5) && sd_card::status() == sd_card::Status::Mounted) {
|
||||
int data = read_file("/hardware/settings.txt");
|
||||
if (data != -1) {
|
||||
config_value = data;
|
||||
}
|
||||
} else {
|
||||
config_value = value;
|
||||
}
|
||||
config_value = value;
|
||||
}
|
||||
return config_value.value();
|
||||
}
|
||||
@@ -224,39 +199,49 @@ static PortaPackModel portapack_model() {
|
||||
static Optional<PortaPackModel> model;
|
||||
|
||||
if (!model.is_valid()) {
|
||||
const auto switches_state = get_switches_state();
|
||||
// Only save config if no other multi key boot action is triggered (like pmem reset)
|
||||
if (switches_state.count() == 1) {
|
||||
if (switches_state[(size_t)ui::KeyEvent::Up]) {
|
||||
jtag::GPIOTarget target{
|
||||
portapack::gpio_cpld_tck,
|
||||
portapack::gpio_cpld_tms,
|
||||
portapack::gpio_cpld_tdi,
|
||||
portapack::gpio_cpld_tdo};
|
||||
jtag::JTAG jtag{target};
|
||||
portapack::cpld::CPLD cpld{jtag};
|
||||
|
||||
cpld.reset();
|
||||
cpld.run_test_idle();
|
||||
uint32_t idcode = cpld.get_idcode();
|
||||
if (idcode == 0x25610) {
|
||||
model = PortaPackModel::AGM;
|
||||
} else {
|
||||
const auto switches_state = swizzled_switches();
|
||||
// chDbgPanic(to_string_hex((uint32_t)switches_state.count(), 8).c_str());
|
||||
// Only save config if no other multi key boot action is triggered (like pmem reset)
|
||||
if (((switches_state >> (size_t)ui::KeyEvent::Up) & 1) == 1) {
|
||||
save_config(1);
|
||||
// model = PortaPackModel::R2_20170522; // Commented these out as they should be set down below anyway
|
||||
} else if (switches_state[(size_t)ui::KeyEvent::Down]) {
|
||||
} else if (((switches_state >> (size_t)ui::KeyEvent::Down) & 1) == 1) {
|
||||
save_config(2);
|
||||
// model = PortaPackModel::R1_20150901;
|
||||
} else if (switches_state[(size_t)ui::KeyEvent::Left]) {
|
||||
} else if (((switches_state >> (size_t)ui::KeyEvent::Left) & 1) == 1) {
|
||||
save_config(3);
|
||||
// model = PortaPackModel::R1_20150901;
|
||||
} else if (switches_state[(size_t)ui::KeyEvent::Right]) {
|
||||
} else if (((switches_state >> (size_t)ui::KeyEvent::Right) & 1) == 1) {
|
||||
save_config(4);
|
||||
// model = PortaPackModel::R2_20170522;
|
||||
} else if (switches_state[(size_t)ui::KeyEvent::Select]) {
|
||||
} else if (((switches_state >> (size_t)ui::KeyEvent::Select) & 1) == 1) {
|
||||
save_config(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (load_config() == 1) {
|
||||
model = PortaPackModel::R2_20170522;
|
||||
} else if (load_config() == 2) {
|
||||
model = PortaPackModel::R1_20150901;
|
||||
} else if (load_config() == 3) {
|
||||
model = PortaPackModel::R1_20150901;
|
||||
} else if (load_config() == 4) {
|
||||
model = PortaPackModel::R2_20170522;
|
||||
} else {
|
||||
if (audio_codec_wm8731.detected()) {
|
||||
model = PortaPackModel::R1_20150901; // H1R1
|
||||
if (load_config() == 1) {
|
||||
model = PortaPackModel::R2_20170522;
|
||||
} else if (load_config() == 2) {
|
||||
model = PortaPackModel::R1_20150901;
|
||||
} else if (load_config() == 3) {
|
||||
model = PortaPackModel::R1_20150901;
|
||||
} else if (load_config() == 4) {
|
||||
model = PortaPackModel::R2_20170522;
|
||||
} else {
|
||||
model = PortaPackModel::R2_20170522; // H1R2, H2, H2+
|
||||
model = PortaPackModel::AUTODETECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,6 +328,129 @@ static void shutdown_base() {
|
||||
clock_manager.shutdown();
|
||||
}
|
||||
|
||||
static void set_cpu_clock_speed() {
|
||||
/* Incantation from LPC43xx UM10503 section 12.2.1.1, to bring the M4
|
||||
* core clock speed to the 110 - 204MHz range.
|
||||
*/
|
||||
|
||||
/* Step into the 90-110MHz M4 clock range */
|
||||
/* OG:
|
||||
* Fclkin = 40M
|
||||
* /N=2 = 20M = PFDin
|
||||
* Fcco = PFDin * (M=10) = 200M
|
||||
* r9:
|
||||
* Fclkin = 10M
|
||||
* /N=1 = 10M = PFDin
|
||||
* Fcco = PFDin * (M=20) = 200M
|
||||
* Fclk = Fcco / (2*(P=1)) = 100M
|
||||
*/
|
||||
cgu::pll1::ctrl({
|
||||
.pd = 1,
|
||||
.bypass = 0,
|
||||
.fbsel = 0,
|
||||
.direct = 0,
|
||||
.psel = 0,
|
||||
.autoblock = 1,
|
||||
.nsel = hackrf_r9 ? 0UL : 1UL,
|
||||
.msel = hackrf_r9 ? 19UL : 9UL,
|
||||
.clk_sel = cgu::CLK_SEL::GP_CLKIN,
|
||||
});
|
||||
|
||||
cgu::pll1::enable();
|
||||
while (!cgu::pll1::is_locked())
|
||||
;
|
||||
|
||||
set_clock_config(clock_config_pll1_step);
|
||||
|
||||
/* Delay >50us at 90-110MHz clock speed */
|
||||
volatile uint32_t delay = 1400;
|
||||
while (delay--)
|
||||
;
|
||||
|
||||
set_clock_config(clock_config_pll1);
|
||||
|
||||
/* Remove /2P divider from PLL1 output to achieve full speed */
|
||||
cgu::pll1::direct();
|
||||
}
|
||||
|
||||
static void draw_splash_screen_icon(int16_t n, const ui::Bitmap& bitmap) {
|
||||
ui::Painter painter;
|
||||
|
||||
painter.draw_bitmap(
|
||||
{portapack::display.width() / 2 - 8 - 40 + (n * 20), portapack::display.height() / 2 - 8 + 40},
|
||||
bitmap,
|
||||
ui::Color::white(),
|
||||
ui::Color::black());
|
||||
}
|
||||
|
||||
static bool is_portapack_present() {
|
||||
systime_t timeout = 50;
|
||||
uint8_t wm8731_reset_command[] = {0x0f, 0x00};
|
||||
if (i2c0.transmit(0x1a /* wm8731 */, wm8731_reset_command, 2, timeout) == false) {
|
||||
audio_codec_ak4951.reset();
|
||||
uint8_t ak4951_init_command[] = {0x00, 0x00};
|
||||
i2c0.transmit(0x12 /* ak4951 */, ak4951_init_command, 2, timeout);
|
||||
chThdSleepMilliseconds(10);
|
||||
if (i2c0.transmit(0x12 /* ak4951 */, ak4951_init_command, 2, timeout) == false) {
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_portapack_cpld() {
|
||||
switch (portapack_model()) {
|
||||
case PortaPackModel::AUTODETECT: {
|
||||
portapack::cpld::CpldUpdateStatus result = portapack::cpld::update_autodetect(
|
||||
portapack::cpld::rev_20150901::config, portapack::cpld::rev_20170522::config);
|
||||
if (result != portapack::cpld::CpldUpdateStatus::Success) {
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
|
||||
case PortaPackModel::R1_20150901:
|
||||
case PortaPackModel::R2_20170522: {
|
||||
portapack::cpld::CpldUpdateStatus result = portapack::cpld::update_if_necessary(portapack_cpld_config());
|
||||
if (result == portapack::cpld::CpldUpdateStatus::Program_failed) {
|
||||
chThdSleepMilliseconds(10);
|
||||
// Mode left (R1) and right (R2,H2,H2+) bypass going into hackrf mode after failing CPLD update
|
||||
// Mode center (autodetect), up (R1) and down (R2,H2,H2+) will go into hackrf mode after failing CPLD update
|
||||
if (load_config() != 3 /* left */ && load_config() != 4 /* right */) {
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case PortaPackModel::AGM:
|
||||
// the AGM devices are always factory flashed. so do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void initialize_boot_splash_screen() {
|
||||
ui::Painter painter;
|
||||
portapack::display.init();
|
||||
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
|
||||
chThdSleepMilliseconds(17);
|
||||
portapack::backlight()->on();
|
||||
|
||||
painter.draw_bitmap(
|
||||
{portapack::display.width() / 2 - 40, portapack::display.height() / 2 - 8},
|
||||
ui::bitmap_titlebar_image,
|
||||
ui::Color::white(),
|
||||
ui::Color::black());
|
||||
}
|
||||
|
||||
/* Clock scheme after exiting bootloader in SPIFI mode:
|
||||
*
|
||||
* XTAL_OSC = powered down
|
||||
@@ -389,28 +497,29 @@ static void shutdown_base() {
|
||||
* everything else = IRC
|
||||
*/
|
||||
|
||||
bool init() {
|
||||
init_status_t init() {
|
||||
set_idivc_base_clocks(cgu::CLK_SEL::IDIVC);
|
||||
|
||||
i2c0.start(i2c_config_boot_clock);
|
||||
|
||||
// Keeping this here for now incase we need to revert
|
||||
// if( !portapack::cpld::update_if_necessary(portapack_cpld_config()) ) {
|
||||
// shutdown_base();
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if( !hackrf::cpld::load_sram() ) {
|
||||
// chSysHalt();
|
||||
// }
|
||||
chThdSleepMilliseconds(100);
|
||||
|
||||
configure_pins_portapack();
|
||||
|
||||
portapack::io.init();
|
||||
persistent_memory::cache::init();
|
||||
|
||||
const auto switches_state = swizzled_switches() & (~(0xC0 | 0x80));
|
||||
bool lcd_fast_setup = switches_state == 0 && portapack::display.read_display_status();
|
||||
|
||||
if (lcd_fast_setup) {
|
||||
initialize_boot_splash_screen();
|
||||
} else {
|
||||
if (check_portapack_cpld() == false)
|
||||
return init_status_t::INIT_PORTAPACK_CPLD_FAILED;
|
||||
}
|
||||
|
||||
/* Cache some configuration data from persistent memory. */
|
||||
persistent_memory::cache::init();
|
||||
rtc_time::dst_init();
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
@@ -423,48 +532,10 @@ bool init() {
|
||||
set_clock_config(clock_config_irc);
|
||||
cgu::pll1::disable();
|
||||
|
||||
/* Incantation from LPC43xx UM10503 section 12.2.1.1, to bring the M4
|
||||
* core clock speed to the 110 - 204MHz range.
|
||||
*/
|
||||
set_cpu_clock_speed();
|
||||
|
||||
/* Step into the 90-110MHz M4 clock range */
|
||||
/* OG:
|
||||
* Fclkin = 40M
|
||||
* /N=2 = 20M = PFDin
|
||||
* Fcco = PFDin * (M=10) = 200M
|
||||
* r9:
|
||||
* Fclkin = 10M
|
||||
* /N=1 = 10M = PFDin
|
||||
* Fcco = PFDin * (M=20) = 200M
|
||||
* Fclk = Fcco / (2*(P=1)) = 100M
|
||||
*/
|
||||
cgu::pll1::ctrl({
|
||||
.pd = 1,
|
||||
.bypass = 0,
|
||||
.fbsel = 0,
|
||||
.direct = 0,
|
||||
.psel = 0,
|
||||
.autoblock = 1,
|
||||
.nsel = hackrf_r9 ? 0UL : 1UL,
|
||||
.msel = hackrf_r9 ? 19UL : 9UL,
|
||||
.clk_sel = cgu::CLK_SEL::GP_CLKIN,
|
||||
});
|
||||
|
||||
cgu::pll1::enable();
|
||||
while (!cgu::pll1::is_locked())
|
||||
;
|
||||
|
||||
set_clock_config(clock_config_pll1_step);
|
||||
|
||||
/* Delay >50us at 90-110MHz clock speed */
|
||||
volatile uint32_t delay = 1400;
|
||||
while (delay--)
|
||||
;
|
||||
|
||||
set_clock_config(clock_config_pll1);
|
||||
|
||||
/* Remove /2P divider from PLL1 output to achieve full speed */
|
||||
cgu::pll1::direct();
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(0, ui::bitmap_icon_memory);
|
||||
|
||||
usb_serial.initialize();
|
||||
|
||||
@@ -472,18 +543,11 @@ bool init() {
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
/* Check if portapack is attached by checking if any of the two audio chips is present. */
|
||||
systime_t timeout = 50;
|
||||
uint8_t wm8731_reset_command[] = {0x0f, 0x00};
|
||||
if (i2c0.transmit(0x1a /* wm8731 */, wm8731_reset_command, 2, timeout) == false) {
|
||||
audio_codec_ak4951.reset();
|
||||
uint8_t ak4951_init_command[] = {0x00, 0x00};
|
||||
i2c0.transmit(0x12 /* ak4951 */, ak4951_init_command, 2, timeout);
|
||||
chThdSleepMilliseconds(10);
|
||||
if (i2c0.transmit(0x12 /* ak4951 */, ak4951_init_command, 2, timeout) == false) {
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (lcd_fast_setup == false && is_portapack_present() == false)
|
||||
return init_status_t::INIT_NO_PORTAPACK;
|
||||
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(1, ui::bitmap_icon_remote);
|
||||
|
||||
touch::adc::init();
|
||||
controls_init();
|
||||
@@ -499,20 +563,19 @@ bool init() {
|
||||
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
portapack::cpld::CpldUpdateStatus result = portapack::cpld::update_if_necessary(portapack_cpld_config());
|
||||
if (result == portapack::cpld::CpldUpdateStatus::Program_failed) {
|
||||
chThdSleepMilliseconds(10);
|
||||
// Mode left (R1) and right (R2,H2,H2+) bypass going into hackrf mode after failing CPLD update
|
||||
// Mode center (autodetect), up (R1) and down (R2,H2,H2+) will go into hackrf mode after failing CPLD update
|
||||
if (load_config() != 3 /* left */ && load_config() != 4 /* right */) {
|
||||
shutdown_base();
|
||||
return false;
|
||||
}
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(2, ui::bitmap_icon_sd);
|
||||
|
||||
init_status_t return_code = init_status_t::INIT_SUCCESS;
|
||||
if (!hackrf::cpld::load_sram()) {
|
||||
if (lcd_fast_setup)
|
||||
chDbgPanic("HACKRF CPLD FAILED");
|
||||
|
||||
return_code = init_status_t::INIT_HACKRF_CPLD_FAILED;
|
||||
}
|
||||
|
||||
if (!hackrf::cpld::load_sram()) {
|
||||
chSysHalt();
|
||||
}
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(3, ui::bitmap_icon_hackrf);
|
||||
|
||||
chThdSleepMilliseconds(10); // This delay seems to solve white noise audio issues
|
||||
|
||||
@@ -523,7 +586,14 @@ bool init() {
|
||||
|
||||
audio::init(portapack_audio_codec());
|
||||
|
||||
return true;
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(4, ui::bitmap_icon_speaker);
|
||||
else {
|
||||
portapack::display.init();
|
||||
portapack::backlight()->on();
|
||||
}
|
||||
|
||||
return return_code;
|
||||
}
|
||||
|
||||
void shutdown(const bool leave_screen_on) {
|
||||
|
||||
@@ -41,6 +41,15 @@
|
||||
* guardrails on setting properties. */
|
||||
namespace portapack {
|
||||
|
||||
enum class init_status_t {
|
||||
INIT_SUCCESS,
|
||||
INIT_NO_PORTAPACK,
|
||||
INIT_PORTAPACK_CPLD_FAILED,
|
||||
INIT_HACKRF_CPLD_FAILED,
|
||||
};
|
||||
|
||||
extern const char* init_error;
|
||||
|
||||
extern portapack::IO io;
|
||||
|
||||
extern lcd::ILI9341 display;
|
||||
@@ -65,7 +74,7 @@ extern TemperatureLogger temperature_logger;
|
||||
void set_antenna_bias(const bool v);
|
||||
bool get_antenna_bias();
|
||||
|
||||
bool init();
|
||||
init_status_t init();
|
||||
void shutdown(const bool leave_screen_on = false);
|
||||
|
||||
void setEventDispatcherToUSBSerial(EventDispatcher* evt);
|
||||
|
||||
@@ -116,8 +116,9 @@ void set_direction(const rf::Direction new_direction) {
|
||||
/* TODO: Refactor all the various "Direction" enumerations into one. */
|
||||
/* TODO: Only make changes if direction changes, but beware of clock enabling. */
|
||||
|
||||
// Prevents ghosting when switching back to RX from TX mode.
|
||||
hackrf::cpld::load_sram_no_verify();
|
||||
// That below code line , was used to prevent RX interf ghosting when switching back to RX from any TX mode, but in recent code. it seems not necessary.
|
||||
// Deleting that load_sram_no_verify() (or the original , load_sram() ), solves random TX swap I/Q problem in H1R1 , others OK- (and no side effects to all).
|
||||
// hackrf::cpld::load_sram_no_verify(); // After commit "removed the use of the hackrf cpld eeprom #1732", in a H1R1, Mic App wrong SSB TX with random USB/LSB change.
|
||||
|
||||
direction = new_direction;
|
||||
|
||||
@@ -184,11 +185,13 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
if (tuning_config.is_valid()) {
|
||||
first_if.disable();
|
||||
|
||||
// Program first local oscillator frequency (if there is one) into RFFC507x
|
||||
if (tuning_config.first_lo_frequency) {
|
||||
first_if.set_frequency(tuning_config.first_lo_frequency);
|
||||
first_if.enable();
|
||||
}
|
||||
|
||||
// Program second local oscillator frequency into MAX283x
|
||||
const auto result_second_if = second_if->set_frequency(tuning_config.second_lo_frequency);
|
||||
|
||||
rf_path.set_band(tuning_config.rf_path_band);
|
||||
@@ -246,6 +249,10 @@ void set_antenna_bias(const bool on) {
|
||||
}
|
||||
}
|
||||
|
||||
void set_tx_max283x_iq_phase_calibration(const size_t v) {
|
||||
second_if->set_tx_LO_iq_phase_calibration(v);
|
||||
}
|
||||
|
||||
/*void enable(Configuration configuration) {
|
||||
configure(configuration);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ 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);
|
||||
void set_tx_max283x_iq_phase_calibration(const size_t v);
|
||||
|
||||
/* Use ReceiverModel or TransmitterModel instead. */
|
||||
// void enable(Configuration configuration);
|
||||
|
||||
@@ -39,8 +39,8 @@ enum class Direction {
|
||||
|
||||
namespace path {
|
||||
|
||||
constexpr FrequencyRange band_low{0, 2170000000};
|
||||
constexpr FrequencyRange band_high{2740000000, 7250000000};
|
||||
constexpr FrequencyRange band_low{0, 2170'000'000};
|
||||
constexpr FrequencyRange band_high{2740'000'000, 7250'000'000};
|
||||
constexpr FrequencyRange band_mid{band_low.maximum, band_high.minimum};
|
||||
|
||||
enum class Band {
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
*/
|
||||
|
||||
#include "rtc_time.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace lpc43xx;
|
||||
using namespace portapack;
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace rtc_time {
|
||||
@@ -77,8 +78,10 @@ rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime) {
|
||||
if (doy != dst_current_doy) {
|
||||
if (datetime.year() != dst_current_year)
|
||||
dst_update_date_range(datetime.year(), doy);
|
||||
else
|
||||
dst_check_date_range(doy);
|
||||
else {
|
||||
dst_current_doy = doy;
|
||||
dst_in_range = dst_check_date_range(doy, dst_start_doy, dst_end_doy);
|
||||
}
|
||||
}
|
||||
|
||||
// Not in DST date range
|
||||
@@ -105,15 +108,13 @@ rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime) {
|
||||
}
|
||||
|
||||
// Check if current date is within the DST range (called when date changes)
|
||||
void dst_check_date_range(uint16_t doy) {
|
||||
dst_current_doy = doy;
|
||||
|
||||
bool dst_check_date_range(uint16_t doy, uint16_t start_doy, uint16_t end_doy) {
|
||||
// Check if date is within DST range
|
||||
// (note that dates are reversed in Southern hemisphere because Summer starts in December)
|
||||
if (dst_start_doy <= dst_end_doy)
|
||||
dst_in_range = ((doy >= dst_start_doy) && (doy < dst_end_doy));
|
||||
if (start_doy <= end_doy)
|
||||
return ((doy >= start_doy) && (doy < end_doy));
|
||||
else
|
||||
dst_in_range = ((doy >= dst_start_doy) || (doy < dst_end_doy));
|
||||
return ((doy >= start_doy) || (doy < end_doy));
|
||||
}
|
||||
|
||||
// Update DST parameters (called at power-up, when year changes, or DST settings are changed)
|
||||
@@ -125,12 +126,20 @@ void dst_update_date_range(uint16_t year, uint16_t doy) {
|
||||
dst_start_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.start_month, dst.b.start_which, dst.b.start_weekday);
|
||||
dst_end_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.end_month, dst.b.end_which, dst.b.end_weekday);
|
||||
|
||||
dst_check_date_range(doy);
|
||||
dst_current_doy = doy;
|
||||
dst_in_range = dst_check_date_range(doy, dst_start_doy, dst_end_doy);
|
||||
} else {
|
||||
dst_in_range = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Test date range for display purposes only when setting Time
|
||||
bool dst_test_date_range(uint16_t year, uint16_t doy, pmem::dst_config_t dst) {
|
||||
uint16_t start_doy = day_of_year_of_nth_weekday(year, dst.b.start_month, dst.b.start_which, dst.b.start_weekday);
|
||||
uint16_t end_doy = day_of_year_of_nth_weekday(year, dst.b.end_month, dst.b.end_which, dst.b.end_weekday);
|
||||
return dst_check_date_range(doy, start_doy, end_doy);
|
||||
}
|
||||
|
||||
// Set RTC clock.
|
||||
// If the user has enabled DST, the time entered and passed to this function is interpreted as having been adjusted for DST.
|
||||
// When DST functionality is enabled in pmem, the value stored in the RTC hardware is non-DST time, and we only fudge the time when read.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -25,7 +26,7 @@
|
||||
#include "signal.hpp"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
namespace rtc_time {
|
||||
|
||||
@@ -45,8 +46,9 @@ rtc::RTC now(rtc::RTC& out_datetime);
|
||||
/* Daylight Savings Time functions */
|
||||
void dst_init();
|
||||
rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime);
|
||||
void dst_check_date_range(uint16_t doy);
|
||||
bool dst_check_date_range(uint16_t doy, uint16_t start_doy, uint16_t end_doy);
|
||||
void dst_update_date_range(uint16_t year, uint16_t doy);
|
||||
bool dst_test_date_range(uint16_t year, uint16_t doy, portapack::persistent_memory::dst_config_t dst);
|
||||
uint8_t days_per_month(uint16_t year, uint8_t month);
|
||||
uint8_t current_day_of_week();
|
||||
uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day);
|
||||
|
||||
@@ -185,7 +185,7 @@ std::string to_string_freq(const uint64_t f) {
|
||||
|
||||
// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
|
||||
std::string to_string_short_freq(const uint64_t f) {
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
|
||||
auto final_str = to_string_dec_int((f + 50) / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
|
||||
return final_str;
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
|
||||
|
||||
uint32_t divisor = pow10[6 - precision];
|
||||
|
||||
final_str = to_string_dec_uint(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
final_str = to_string_dec_uint((f + (divisor / 2)) / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
}
|
||||
return final_str;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -85,7 +86,7 @@ const tone_key_t tone_keys = {
|
||||
{"Senn. 32.768k", F2Ix100(32768.0)}};
|
||||
|
||||
std::string fx100_string(uint32_t f) {
|
||||
return to_string_dec_uint(f / 100) + "." + to_string_dec_uint(((f + 5) / 10) % 10);
|
||||
return to_string_dec_uint((f + 5) / 100) + "." + to_string_dec_uint(((f + 5) / 10) % 10);
|
||||
}
|
||||
|
||||
float tone_key_frequency(tone_index index) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -26,44 +26,43 @@
|
||||
namespace tuning {
|
||||
namespace config {
|
||||
|
||||
namespace {
|
||||
|
||||
// Low band <2170 Mhz:
|
||||
constexpr rf::Frequency low_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
return 2650000000 - (target_frequency / 7);
|
||||
}
|
||||
|
||||
constexpr rf::Frequency high_band_second_lo_regions_2_and_3(const rf::Frequency target_frequency) {
|
||||
return (target_frequency < 5100000000)
|
||||
? (2350000000 + ((target_frequency - 3600000000) / 5))
|
||||
: (2500000000 + ((target_frequency - 5100000000) / 9));
|
||||
}
|
||||
|
||||
constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
return (target_frequency < 3600000000)
|
||||
? (2170000000 + (((target_frequency - 2740000000) * 57) / 86))
|
||||
: high_band_second_lo_regions_2_and_3(target_frequency);
|
||||
return 2650'000'000 - (target_frequency / 7);
|
||||
}
|
||||
|
||||
Config low_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency + low_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = first_lo_frequency - target_frequency;
|
||||
const rf::Frequency second_lo_frequency = low_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency first_lo_frequency = target_frequency + second_lo_frequency;
|
||||
const bool mixer_invert = true;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert};
|
||||
}
|
||||
|
||||
// Mid band 2170-2740 Mhz:
|
||||
Config mid_band(const rf::Frequency target_frequency) {
|
||||
return {0, target_frequency, rf::path::Band::Mid, false};
|
||||
const rf::Frequency second_lo_frequency = target_frequency;
|
||||
const rf::Frequency first_lo_frequency = 0;
|
||||
const bool mixer_invert = false;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Mid, mixer_invert};
|
||||
}
|
||||
|
||||
// High band >2740 Mhz:
|
||||
constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
if (target_frequency < 3600'000'000)
|
||||
return (2170'000'000 + (((target_frequency - 2740'000'000) * 57) / 86));
|
||||
else if (target_frequency < 5100'000'000)
|
||||
return (2350'000'000 + ((target_frequency - 3600'000'000) / 5));
|
||||
else
|
||||
return (2500'000'000 + ((target_frequency - 5100'000'000) / 9));
|
||||
}
|
||||
|
||||
Config high_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency - high_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = target_frequency - first_lo_frequency;
|
||||
const rf::Frequency second_lo_frequency = high_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency first_lo_frequency = target_frequency - second_lo_frequency;
|
||||
const bool mixer_invert = false;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, mixer_invert};
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
Config create(const rf::Frequency target_frequency) {
|
||||
/* TODO: This is some lame code. */
|
||||
if (rf::path::band_low.contains(target_frequency)) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2019 Elia Yehuda (z4ziggy)
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -126,6 +127,7 @@ void BtnGridView::add_item(GridItem new_item) {
|
||||
|
||||
void BtnGridView::update_items() {
|
||||
size_t i = 0;
|
||||
Color bg_color = portapack::persistent_memory::menu_color();
|
||||
|
||||
if ((menu_items.size()) > (displayed_max + offset)) {
|
||||
more = true;
|
||||
@@ -146,6 +148,7 @@ void BtnGridView::update_items() {
|
||||
item->set_text(menu_items[i + offset].text);
|
||||
item->set_bitmap(menu_items[i + offset].bitmap);
|
||||
item->set_color(menu_items[i + offset].color);
|
||||
item->set_bg_color(bg_color);
|
||||
item->on_select = menu_items[i + offset].on_select;
|
||||
item->set_dirty();
|
||||
}
|
||||
@@ -170,15 +173,19 @@ bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
if (((uint32_t)new_value > offset) && ((new_value - offset) >= displayed_max)) {
|
||||
// Shift BtnGridView up
|
||||
highlighted_item = new_value;
|
||||
// rounding up new offset to next multiple of rows
|
||||
offset = new_value - displayed_max + rows_;
|
||||
offset -= (offset % rows_);
|
||||
update_items();
|
||||
set_dirty();
|
||||
// refresh whole screen (display flickers) only if scrolling last row up and a blank button is needed at the bottom
|
||||
if ((new_value + rows_ >= item_count) && (item_count % rows_) != 0)
|
||||
set_dirty();
|
||||
} else if ((uint32_t)new_value < offset) {
|
||||
// Shift BtnGridView down
|
||||
highlighted_item = new_value;
|
||||
offset = (new_value / rows_) * rows_;
|
||||
update_items();
|
||||
set_dirty();
|
||||
// no need to set_dirty() here since all buttons have been repainted
|
||||
} else {
|
||||
// Just update highlight
|
||||
highlighted_item = new_value;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -498,12 +499,13 @@ void GeoMap::draw_scale(Painter& painter) {
|
||||
if (m < 1000) {
|
||||
km_string = to_string_dec_uint(m) + "m";
|
||||
} else {
|
||||
m += 50; // (add rounding factor for div by 100 below)
|
||||
uint32_t km = m / 1000;
|
||||
m -= km * 1000;
|
||||
if (m == 0) {
|
||||
km_string = to_string_dec_uint(km) + " km";
|
||||
} else {
|
||||
km_string = to_string_dec_uint(km) + "." + to_string_dec_uint((m + 50) / 100, 1) + "km";
|
||||
km_string = to_string_dec_uint(km) + "." + to_string_dec_uint(m / 100, 1) + "km";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
|
||||
@@ -86,6 +86,12 @@ const Style Styles::bg_blue{
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
const Style Styles::bg_dark_blue{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::dark_blue(),
|
||||
.foreground = Color::white(),
|
||||
};
|
||||
|
||||
const Style Styles::light_grey{
|
||||
.font = font::fixed_8x16,
|
||||
.background = Color::black(),
|
||||
|
||||
@@ -58,6 +58,9 @@ class Styles {
|
||||
/* Blue background. */
|
||||
static const Style bg_blue;
|
||||
|
||||
/* Dark blue background. */
|
||||
static const Style bg_dark_blue;
|
||||
|
||||
/* Light grey foreground. */
|
||||
static const Style light_grey;
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace ui {
|
||||
|
||||
/* static */ bool ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) {
|
||||
File app;
|
||||
uint32_t checksum{0};
|
||||
|
||||
auto openError = app.open(filePath);
|
||||
if (openError)
|
||||
@@ -115,7 +116,6 @@ namespace ui {
|
||||
application_information_t application_information = {};
|
||||
|
||||
auto readResult = app.read(&application_information, sizeof(application_information_t));
|
||||
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
@@ -135,6 +135,8 @@ namespace ui {
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value());
|
||||
|
||||
if (readResult.value() < std::filesystem::max_file_block_size)
|
||||
break;
|
||||
}
|
||||
@@ -156,6 +158,8 @@ namespace ui {
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
checksum += simple_checksum((uint32_t)target_memory, readResult.value());
|
||||
|
||||
if (readResult.value() != bytes_to_read)
|
||||
break;
|
||||
}
|
||||
@@ -168,11 +172,16 @@ namespace ui {
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value());
|
||||
|
||||
if (readResult.value() < std::filesystem::max_file_block_size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (checksum != EXT_APP_EXPECTED_CHECKSUM)
|
||||
return false;
|
||||
|
||||
application_information.externalAppEntry(nav);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
#define EXT_APP_EXPECTED_CHECKSUM 0x00000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <size_t Width, size_t Height>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user