mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61d9c051de | |||
| 5ea1bff1e6 | |||
| bc035cff6a | |||
| f2c008602d | |||
| b8073bca0f | |||
| c30a61441b | |||
| f0f279eec5 | |||
| 464cc8449e | |||
| ee2e57d702 | |||
| a2a5fb166e | |||
| f59f5dfaa3 | |||
| 13f12227bd | |||
| c9db21063b | |||
| 125184f300 | |||
| 8068517808 | |||
| a4f6bbda5e | |||
| 8b2598fdac | |||
| c36fe78bd5 | |||
| bd8385464e | |||
| 8c996b5bc6 | |||
| d1901939e1 | |||
| 41ec11fb66 | |||
| df47490d85 | |||
| a2f6d14b15 |
@@ -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"]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -89,6 +89,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits_per_sample;
|
||||
|
||||
stop();
|
||||
|
||||
@@ -104,6 +105,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
// 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),
|
||||
@@ -116,15 +118,16 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
// TODO: Delete all this and use tx model.
|
||||
baseband::set_audiotx_config(
|
||||
TONES_SAMPLERATE / 20, // Update vu-meter at 20Hz
|
||||
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
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
bits_per_sample,
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
0, // AM
|
||||
0, // DSB
|
||||
0, // USB
|
||||
0 // LSB
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
|
||||
@@ -171,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) {
|
||||
|
||||
@@ -127,8 +127,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 +199,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 +237,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 +316,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");
|
||||
|
||||
@@ -280,6 +280,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},
|
||||
|
||||
@@ -552,6 +552,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 +562,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 +585,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 *******************************************************/
|
||||
|
||||
@@ -385,7 +385,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:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "ui_flash_utility.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -33,13 +34,13 @@ 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{1};
|
||||
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 = FLASH_STARTING_ADDRESS; i < FLASH_ROM_SIZE / sizeof(read_buffer); i++) {
|
||||
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
|
||||
@@ -109,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());
|
||||
@@ -122,25 +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();
|
||||
path = extract_tar(u'/' + path, painter).native();
|
||||
}
|
||||
|
||||
if (path.empty() || !valid_firmware_file(path.c_str()))
|
||||
return; // bad firmware image - just returning back to the file list
|
||||
|
||||
ui::Painter painter;
|
||||
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;
|
||||
}
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
@@ -153,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() {
|
||||
|
||||
@@ -47,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_;
|
||||
@@ -61,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);
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "tonesets.hpp"
|
||||
#include "ui_tone_key.hpp"
|
||||
#include "wm8731.hpp"
|
||||
#include "radio.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -105,6 +106,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 +337,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 +370,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();
|
||||
|
||||
@@ -113,6 +113,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 +133,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 +162,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 +341,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",
|
||||
|
||||
@@ -81,6 +81,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 +101,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 +154,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 +173,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 ******************************************/
|
||||
|
||||
@@ -126,6 +126,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 +174,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 {
|
||||
|
||||
@@ -22,11 +22,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 +38,17 @@ void ViewWavView::update_scale(int32_t new_scale) {
|
||||
}
|
||||
|
||||
void ViewWavView::refresh_waveform() {
|
||||
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 +81,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 +115,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;
|
||||
@@ -117,9 +153,93 @@ void ViewWavView::reset_controls() {
|
||||
field_cursor_b.set_value(0);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,42 +247,65 @@ 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);
|
||||
field_pos_seconds.focus();
|
||||
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, wav_reader->sample_count() / 240);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
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 +322,9 @@ void ViewWavView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
|
||||
ViewWavView::~ViewWavView() {
|
||||
stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -24,12 +24,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 +40,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 +75,45 @@ class ViewWavView : public View {
|
||||
int32_t scale{1};
|
||||
uint64_t ns_per_pixel{};
|
||||
uint64_t position{};
|
||||
bool updating_position{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 +123,40 @@ class ViewWavView : public View {
|
||||
false,
|
||||
Color::white()};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 11 * 16, 30 * 8, 8}};
|
||||
|
||||
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},
|
||||
{26 * 8, 12 * 16},
|
||||
4,
|
||||
{1, 9999},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
|
||||
NumberField field_cursor_a{
|
||||
{9 * 8, 12 * 16},
|
||||
{9 * 8, 14 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
@@ -117,7 +164,7 @@ class ViewWavView : public View {
|
||||
true};
|
||||
|
||||
NumberField field_cursor_b{
|
||||
{9 * 8, 13 * 16},
|
||||
{9 * 8, 15 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
@@ -125,8 +172,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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
+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
|
||||
)
|
||||
|
||||
+13
-6
@@ -31,6 +31,7 @@ MEMORY
|
||||
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
|
||||
ram_external_app_tetris(rwx) : org = 0xEEF70000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -59,7 +60,7 @@ 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,33 +78,33 @@ 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));
|
||||
@@ -123,4 +124,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) {
|
||||
|
||||
@@ -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,102 @@
|
||||
/*
|
||||
* 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(),
|
||||
};
|
||||
|
||||
class SPI_TFT_ILI9341 {
|
||||
public:
|
||||
SPI_TFT_ILI9341(int, int, int, int, int, int, std::string) { (void)0; };
|
||||
|
||||
void claim(__FILE* x) { (void)x; };
|
||||
|
||||
void cls() {
|
||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
||||
};
|
||||
|
||||
void background(int color) { bg_color = color; };
|
||||
void foreground(int color) { fg_color = color; };
|
||||
|
||||
void locate(int x, int y) {
|
||||
x_pos = x;
|
||||
y_pos = y;
|
||||
};
|
||||
void set_orientation(int x) { (void)x; };
|
||||
void set_font(unsigned char* x) { (void)x; };
|
||||
|
||||
void fillrect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
void rect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
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 BECAUSE INITIALIZER CODE WON'T EXECUTE -- 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__*/
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
// Projekat Tetris
|
||||
// by Vrnjak Lamija & Selimović Denis
|
||||
// Elektrotehnički fakultet Sarajevo
|
||||
|
||||
// clang-format off
|
||||
|
||||
//////// PORTAPACK CHANGES HIGHLIGHTED
|
||||
int main();
|
||||
void pause_game();
|
||||
//////// PORTAPACK
|
||||
|
||||
#include "mbed.h"
|
||||
#include "SPI_TFT_ILI9341.h"
|
||||
#include "Arial12x12.h"
|
||||
|
||||
#define dp23 P0_0
|
||||
|
||||
//deklaracija display-a
|
||||
SPI_TFT_ILI9341 display(dp2, dp1, dp6, dp24, dp23, dp25, "TFT");
|
||||
|
||||
//////// PORTAPACK - DISABLED ANALOGIN CLASS DUE TO OBJECT INITIALIZER CODE NOT RUNNING:
|
||||
//analogni ulazi za joystick
|
||||
// AnalogIn VRx(dp11);
|
||||
// AnalogIn VRy(dp10);
|
||||
//////// PORTAPACK
|
||||
|
||||
//taster na joysticku za rotaciju
|
||||
InterruptIn taster(dp9);
|
||||
|
||||
//////// PORTAPACK - DISABLED ANALOGIN CLASS DUE TO OBJECT INITIALIZER CODE NOT RUNNING:
|
||||
// AnalogIn random(dp13); //analogni ulaz za generisanje random vrijednosti
|
||||
//////// PORTAPACK
|
||||
|
||||
//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 - 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;
|
||||
// unsigned int randomNumber = 0;
|
||||
// for(int i = 0; i <= 32; i += 2) {
|
||||
// randomNumber += ((random.read_u16() % 3) << i);
|
||||
// wait_us(10);
|
||||
// }
|
||||
// return randomNumber;
|
||||
//////// 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
|
||||
display.claim(stdout);
|
||||
display.set_orientation(2); // 2 ili 0, zavisi kako okrenemo display, provjerit ćemo na labu kako nam je najlakše povezat
|
||||
display.set_font((unsigned char*) Arial12x12);
|
||||
}
|
||||
|
||||
|
||||
void ShowScore() {
|
||||
//pomocna funkcija za prikazivanje score-a
|
||||
display.fillrect(165, 20, 235, 50, White); //popunimo pravugaonik da obrišemo stari score
|
||||
display.locate(200, 35); //valjda je na sredini pravougaonika
|
||||
printf("%d", score);
|
||||
}
|
||||
|
||||
void ShowNextFigure() {
|
||||
//prikaz sljedeće figure koristeći pomoćnu varijablu nextFigure
|
||||
display.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];
|
||||
display.fillrect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, colors[nextFigure]);
|
||||
display.rect(x, y, x + DIMENSION_NEXT, y + DIMENSION_NEXT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
//funkcija za crtanje cursora za odabir levela
|
||||
void DrawCursor(int color, unsigned char lev) {
|
||||
display.fillrect(60, lev * 70 + 50, 72, lev * 70 + 50 + 12, color);
|
||||
}
|
||||
|
||||
// PORTAPACK - ADDED EXTRA LEVEL:
|
||||
void ShowLevelMenu() {
|
||||
//ovdje inicijalizujemo display
|
||||
display.cls(); // brišemo prethodno
|
||||
display.background(Black);
|
||||
display.foreground(White);
|
||||
display.locate(80, 50);
|
||||
printf("LEVEL 1");
|
||||
display.locate(80, 120);
|
||||
printf("LEVEL 2");
|
||||
display.locate(80, 190);
|
||||
printf("LEVEL 3");
|
||||
display.locate(80, 260);
|
||||
printf("LEVEL 4");
|
||||
DrawCursor(White, level);
|
||||
}
|
||||
|
||||
//////// PORTAPACK - KLUDGED FOR BUTTONS VS JOYSTICK:
|
||||
void ReadJoystickForLevel(){
|
||||
unsigned char old = level;
|
||||
if(but_UP){
|
||||
// upBoundary = 4;
|
||||
(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
|
||||
// downBoundary = 2;
|
||||
level = (level + 1) % 4;
|
||||
}
|
||||
else {
|
||||
// downBoundary = 1;
|
||||
// upBoundary = 5;
|
||||
}
|
||||
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()
|
||||
{
|
||||
display.cls(); // brišemo ShowLevelMenu
|
||||
display.background(White);
|
||||
display.foreground(Black);
|
||||
display.fillrect(0, 0, 160, 320, White);
|
||||
display.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;
|
||||
display.rect(i * DIMENSION, j * DIMENSION, (i + 1) * DIMENSION, (j + 1) * DIMENSION, Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(){
|
||||
//////// PORTAPACK - NOTE - DEFAULT INITIALIZER CODE DOESN'T GET EXECUTED FOR SOME REASON:
|
||||
unsigned char r = rand() % 7 + 1;
|
||||
Initialize(r);
|
||||
}
|
||||
|
||||
Tetromino(unsigned char colorIndex) {
|
||||
Initialize(colorIndex);
|
||||
}
|
||||
|
||||
void Initialize(unsigned char colorIndex) {
|
||||
Tetromino::colorIndex = colorIndex;
|
||||
boardX = 0;
|
||||
boardY = 4; //3,4 ili 5 najbolje da vidimo kad imamo display
|
||||
copyCoordinates(X, Y, colorIndex - 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;
|
||||
display.fillrect( upperLeftY, upperLeftX, upperLeftY + DIMENSION, upperLeftX + DIMENSION, colors[colorIndex]);
|
||||
//ovo boji granice blokova u crno, možemo skloniti ako ti se ne sviđa
|
||||
|
||||
//////// PORTAPACK - HIDE DEFAULT WHITE BLOCK (ALTERNATE KLUDGE FOR TETRONIMO INITIALIZATION CODE NOT RUNNING AT CONSTRUCTION)
|
||||
if (colorIndex != White)
|
||||
//////// PORTAPACK
|
||||
display.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;
|
||||
display.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){
|
||||
//////// PORTAPACK - MOVE DEFAULT WHITE BLOCK TO BOTTOM IMMEDIATELY (ALTERNATE KLUDGE FOR TETRONIMO INITIALIZATION CODE NOT RUNNING AT CONSTRUCTION)
|
||||
if (colorIndex == White) delta = 19;
|
||||
//////// PORTAPACK
|
||||
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 - KLUDGED FOR BUTTONS VS JOYSTICK:
|
||||
void ReadJoystickForFigure() {
|
||||
if(but_LEFT) {
|
||||
// leftBoundary = 2;
|
||||
currentTetromino.MoveLeft();
|
||||
}
|
||||
else if(but_RIGHT) {
|
||||
// rightBoundary = 4;
|
||||
currentTetromino.MoveRight();
|
||||
}
|
||||
else if(but_UP) {
|
||||
// downBoundary = 2;
|
||||
pause_game();
|
||||
}
|
||||
else if(but_DOWN) {
|
||||
// upBoundary = 4;
|
||||
currentTetromino.SoftDrop();
|
||||
}
|
||||
else {
|
||||
// leftBoundary = 1;
|
||||
// rightBoundary = 5;
|
||||
// downBoundary = 1;
|
||||
// upBoundary = 5;
|
||||
}
|
||||
}
|
||||
//////// 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;
|
||||
display.fillrect( j * DIMENSION,i * DIMENSION, (j + 1) * DIMENSION , (i + 1) * DIMENSION , colors[board[i][j]]); // bojimo novi blok
|
||||
display.fillrect( j * DIMENSION, tmp * DIMENSION,(j + 1) * DIMENSION, (tmp + 1) * DIMENSION , White);
|
||||
if(board[i][j] != 0)
|
||||
display.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
|
||||
// display.cls();
|
||||
// display.background(Black);
|
||||
// display.foreground(White);
|
||||
display.background(White);
|
||||
display.foreground(Black);
|
||||
//////// PORTAPACK
|
||||
display.locate(60, 120);
|
||||
printf("GAME OVER");
|
||||
display.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) {
|
||||
//////// PORTAPACK - NOTE - ATTEMPTED WORKAROUND FOR SKIPPED INITIALIZER CODE - BUT ANY OF THESE CRASHES FIRMWARE AT POWER-UP EVEN IF THERE'S NO TETRIS APP INSTALLED:
|
||||
// currentTetromino = Tetromino(rand() % 7 + 1); // TEST #1
|
||||
// currentTetromino.Initialize(rand() % 7 + 1); // TEST #2
|
||||
//////// PORTAPACK
|
||||
currentTetromino.DrawFigure();
|
||||
nextFigure = rand() % 7 + 1;
|
||||
ShowNextFigure();
|
||||
firstTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayGame(){
|
||||
InitGame();
|
||||
if(!currentTetromino.MoveDown()){
|
||||
currentTetromino.OnAttached();
|
||||
UpdateBoard();
|
||||
ShowScore();
|
||||
|
||||
currentTetromino = Tetromino(nextFigure);
|
||||
currentTetromino.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){
|
||||
currentTetromino.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();
|
||||
display.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,39 @@
|
||||
#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__*/
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "utility.hpp"
|
||||
|
||||
uint8_t Debounce::state() {
|
||||
bool v = state_to_report_;
|
||||
uint8_t v = state_to_report_;
|
||||
simulated_pulse_ = false;
|
||||
return v;
|
||||
}
|
||||
@@ -149,3 +149,24 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t EncoderDebounce::state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
// Returns TRUE if encoder position phase bits changed (after debouncing)
|
||||
bool EncoderDebounce::feed(const uint8_t phase_bits) {
|
||||
history_ = (history_ << 2) | phase_bits;
|
||||
|
||||
// Has input been constant for 4 ticks? (phase_bits * 01010101b should be 0x00, 0x55, 0xAA, or 0xFF)
|
||||
if (history_ == (phase_bits * 0x55)) {
|
||||
// Has the debounced input value changed?
|
||||
if (state_ != phase_bits) {
|
||||
state_ = phase_bits;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unstable input, or no change
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -64,4 +64,16 @@ 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
|
||||
|
||||
private:
|
||||
uint8_t history_{0}; // shift register of previous reads from encoder
|
||||
|
||||
uint8_t state_{0}; // actual encoder output state (after debounce logic)
|
||||
};
|
||||
|
||||
#endif /*__DEBOUNCE_H__*/
|
||||
|
||||
@@ -26,82 +26,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::config_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;
|
||||
}
|
||||
|
||||
@@ -26,12 +26,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();
|
||||
|
||||
@@ -27,7 +27,7 @@ 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];
|
||||
char title_buffer[32]{0};
|
||||
uint32_t riff_size, data_end, title_size;
|
||||
size_t search_limit = 0;
|
||||
|
||||
@@ -37,11 +37,17 @@ 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)
|
||||
|
||||
// TODO: Work needed here to process RIFF file format correctly, i.e. properly skip over LIST & INFO chunks
|
||||
riff_size = header.cksize + 8;
|
||||
data_start = header.fmt.cksize + 28;
|
||||
data_size_ = header.data.cksize;
|
||||
|
||||
@@ -43,7 +43,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 +162,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());
|
||||
|
||||
if (injected_encoder > 0) {
|
||||
if (injected_encoder == 1) delta = -1;
|
||||
|
||||
@@ -184,11 +184,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 +248,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.
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "signal.hpp"
|
||||
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
namespace rtc_time {
|
||||
|
||||
@@ -45,8 +45,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);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -315,8 +315,15 @@ SystemStatusView::SystemStatusView(
|
||||
refresh();
|
||||
};
|
||||
|
||||
toggle_stealth.on_change = [this](bool v) {
|
||||
toggle_stealth.on_change = [this, &nav](bool v) {
|
||||
pmem::set_stealth_mode(v);
|
||||
if (nav.is_valid() && v) {
|
||||
nav.display_modal(
|
||||
"Stealth",
|
||||
"You just enabled stealth mode.\n"
|
||||
"When you transmit,\n"
|
||||
"screen will turn off;\n");
|
||||
}
|
||||
refresh();
|
||||
};
|
||||
|
||||
@@ -553,7 +560,7 @@ InformationView::InformationView(
|
||||
#endif
|
||||
|
||||
if (firmware_checksum_error()) {
|
||||
version.set("FLASH ERROR");
|
||||
version.set("FLASH ERR");
|
||||
version.set_style(&Styles::red);
|
||||
}
|
||||
|
||||
@@ -585,6 +592,10 @@ bool NavigationView::is_top() const {
|
||||
return view_stack.size() == 1;
|
||||
}
|
||||
|
||||
bool NavigationView::is_valid() const {
|
||||
return view_stack.size() != 0; // work around to check if nav is valid, not elegant i know. so TODO
|
||||
}
|
||||
|
||||
View* NavigationView::push_view(std::unique_ptr<View> new_view) {
|
||||
free_view();
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ class NavigationView : public View {
|
||||
NavigationView& operator=(NavigationView&&) = delete;
|
||||
|
||||
bool is_top() const;
|
||||
bool is_valid() const;
|
||||
|
||||
template <class T, class... Args>
|
||||
T* push(Args&&... args) {
|
||||
|
||||
@@ -118,7 +118,7 @@ uint8_t usb_descriptor_configuration_full_speed[] = {
|
||||
USB_INT_IN_EP_ADDR, // bEndpointAddress
|
||||
0x03, // bmAttributes: BULK
|
||||
USB_WORD(16), // wMaxPacketSize
|
||||
0xFF, // bInterval: no NAK
|
||||
0x20, // bInterval: no NAK
|
||||
|
||||
9, // bLength
|
||||
USB_DESCRIPTOR_TYPE_INTERFACE, // bDescriptorType
|
||||
@@ -203,7 +203,7 @@ uint8_t usb_descriptor_configuration_high_speed[] = {
|
||||
USB_INT_IN_EP_ADDR, // bEndpointAddress
|
||||
0x03, // bmAttributes: BULK
|
||||
USB_WORD(16), // wMaxPacketSize
|
||||
0xFF, // bInterval: no NAK
|
||||
0x20, // bInterval: no NAK
|
||||
|
||||
9, // bLength
|
||||
USB_DESCRIPTOR_TYPE_INTERFACE, // bDescriptorType
|
||||
|
||||
@@ -152,45 +152,15 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->display_modal("Flashing", "Flashing from serial.\r\nPlease wait!\r\nDevice will restart.");
|
||||
// check file extensions
|
||||
if (strEndsWith(path.native(), u".ppfw.tar")) {
|
||||
// extract tar
|
||||
chprintf(chp, "Extracting TAR file.\r\n");
|
||||
auto res = UnTar::untar(
|
||||
path.native(), [chp](const std::string fileName) {
|
||||
chprintf(chp, fileName.c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
});
|
||||
if (res.empty()) {
|
||||
chprintf(chp, "error bad TAR file.\r\n");
|
||||
nav->pop();
|
||||
return;
|
||||
}
|
||||
path = res; // it will contain the last bin file in tar
|
||||
} else if (strEndsWith(path.native(), u".bin")) {
|
||||
// nothing to do for this case yet.
|
||||
} else {
|
||||
chprintf(chp, "error only .bin or .ppfw.tar files can be flashed.\r\n");
|
||||
nav->pop();
|
||||
return;
|
||||
}
|
||||
nav->home(false);
|
||||
|
||||
if (!ui::valid_firmware_file(path.native().c_str())) {
|
||||
chprintf(chp, "error corrupt firmware file.\r\n");
|
||||
nav->pop();
|
||||
return;
|
||||
// call nav with flash
|
||||
auto open_view = nav->push<ui::FlashUtilityView>();
|
||||
chprintf(chp, "Flashing started\r\n");
|
||||
chThdSleepMilliseconds(150); // to give display some time to paint the screen
|
||||
if (!open_view->flash_firmware(path.native())) {
|
||||
chprintf(chp, "error\r\n");
|
||||
}
|
||||
|
||||
chprintf(chp, "Flashing: ");
|
||||
chprintf(chp, path.string().c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
chThdSleepMilliseconds(50);
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.native().c_str(), (path.native().length() + 1) * 2);
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(50);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
m0_halt();
|
||||
}
|
||||
|
||||
static void cmd_screenshot(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
|
||||
@@ -30,23 +30,33 @@
|
||||
void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) return;
|
||||
|
||||
buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, sampling_rate};
|
||||
int16_t audio_sample_s16;
|
||||
|
||||
// Zero-order hold (poop)
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
resample_acc += resample_inc;
|
||||
if (resample_acc >= 0x10000) {
|
||||
resample_acc -= 0x10000;
|
||||
if (stream) {
|
||||
stream->read(&audio_sample, 1);
|
||||
bytes_read++;
|
||||
audio_sample = 0;
|
||||
stream->read(&audio_sample, bytes_per_sample); // assumes little endian when reading 1 byte
|
||||
samples_read++;
|
||||
}
|
||||
}
|
||||
|
||||
sample = audio_sample - 0x80;
|
||||
if (bytes_per_sample == 1) {
|
||||
sample = audio_sample - 0x80;
|
||||
audio_sample_s16 = sample * 256;
|
||||
} else {
|
||||
audio_sample_s16 = (int16_t)audio_sample;
|
||||
sample = audio_sample_s16 / 256;
|
||||
}
|
||||
|
||||
// Output to speaker too
|
||||
if (!tone_key_enabled) {
|
||||
uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1);
|
||||
audio_data[imod32] = sample * 256;
|
||||
audio_data[imod32] = audio_sample_s16;
|
||||
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
|
||||
audio_output.write_unprocessed(audio_buffer);
|
||||
}
|
||||
@@ -69,7 +79,7 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (progress_samples >= progress_interval_samples) {
|
||||
progress_samples -= progress_interval_samples;
|
||||
|
||||
txprogress_message.progress = bytes_read; // Inform UI about progress
|
||||
txprogress_message.progress = samples_read; // Inform UI about progress
|
||||
txprogress_message.done = false;
|
||||
shared_memory.application_queue.push(txprogress_message);
|
||||
}
|
||||
@@ -83,7 +93,7 @@ void AudioTXProcessor::on_message(const Message* const message) {
|
||||
|
||||
case Message::ID::ReplayConfig:
|
||||
configured = false;
|
||||
bytes_read = 0;
|
||||
samples_read = 0;
|
||||
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
|
||||
break;
|
||||
|
||||
@@ -105,6 +115,7 @@ void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) {
|
||||
tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight);
|
||||
progress_interval_samples = message.divider;
|
||||
resample_acc = 0;
|
||||
bytes_per_sample = message.bits_per_sample / 8;
|
||||
audio_output.configure(false);
|
||||
|
||||
tone_key_enabled = (message.tone_key_delta != 0);
|
||||
@@ -124,6 +135,7 @@ void AudioTXProcessor::replay_config(const ReplayConfigMessage& message) {
|
||||
|
||||
void AudioTXProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
|
||||
resample_inc = (((uint64_t)message.sample_rate) << 16) / baseband_fs; // 16.16 fixed point message.sample_rate
|
||||
sampling_rate = message.sample_rate;
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -48,18 +48,19 @@ class AudioTXProcessor : public BasebandProcessor {
|
||||
uint32_t resample_inc{}, resample_acc{};
|
||||
uint32_t fm_delta{0};
|
||||
uint32_t phase{0}, sphase{0};
|
||||
uint8_t audio_sample{};
|
||||
uint32_t audio_sample{};
|
||||
int32_t sample{0}, delta{};
|
||||
int8_t re{0}, im{0};
|
||||
uint8_t bytes_per_sample{1};
|
||||
uint32_t sampling_rate{48000};
|
||||
|
||||
int16_t audio_data[AUDIO_OUTPUT_BUFFER_SIZE];
|
||||
buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
|
||||
AudioOutput audio_output{};
|
||||
|
||||
size_t progress_interval_samples = 0, progress_samples = 0;
|
||||
|
||||
bool configured{false};
|
||||
uint32_t bytes_read{0};
|
||||
uint32_t samples_read{0};
|
||||
bool tone_key_enabled{false};
|
||||
|
||||
void sample_rate_config(const SampleRateConfigMessage& message);
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "spi_image.hpp"
|
||||
|
||||
#define CURRENT_HEADER_VERSION 0x00000001
|
||||
#define CURRENT_HEADER_VERSION 0x00000002
|
||||
#define MIN_HEADER_VERSION_FOR_CHECKSUM 0x00000002
|
||||
|
||||
typedef void (*externalAppEntry_t)(ui::NavigationView& nav);
|
||||
|
||||
|
||||
@@ -931,6 +931,7 @@ class AudioTXConfigMessage : public Message {
|
||||
const float deviation_hz,
|
||||
const float audio_gain,
|
||||
const uint8_t audio_shift_bits_s16,
|
||||
const uint8_t bits_per_sample,
|
||||
const uint32_t tone_key_delta,
|
||||
const float tone_key_mix_weight,
|
||||
const bool am_enabled,
|
||||
@@ -942,6 +943,7 @@ class AudioTXConfigMessage : public Message {
|
||||
deviation_hz(deviation_hz),
|
||||
audio_gain(audio_gain),
|
||||
audio_shift_bits_s16(audio_shift_bits_s16),
|
||||
bits_per_sample(bits_per_sample),
|
||||
tone_key_delta(tone_key_delta),
|
||||
tone_key_mix_weight(tone_key_mix_weight),
|
||||
am_enabled(am_enabled),
|
||||
@@ -954,6 +956,7 @@ class AudioTXConfigMessage : public Message {
|
||||
const float deviation_hz;
|
||||
const float audio_gain;
|
||||
const uint8_t audio_shift_bits_s16;
|
||||
const uint8_t bits_per_sample;
|
||||
const uint32_t tone_key_delta;
|
||||
const float tone_key_mix_weight;
|
||||
const bool am_enabled;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "modems.hpp"
|
||||
#include "serializer.hpp"
|
||||
#include "volume.hpp"
|
||||
#include "config_mode.hpp"
|
||||
|
||||
// persistent memory from/to sdcard flag file
|
||||
#define PMEM_FILEFLAG u"/SETTINGS/PMEM_FILEFLAG"
|
||||
@@ -242,8 +243,6 @@ void set_disable_touchscreen(bool v);
|
||||
uint8_t config_encoder_dial_sensitivity();
|
||||
void set_encoder_dial_sensitivity(uint8_t v);
|
||||
|
||||
#define CONFIG_MODE_GUARD_VALUE 0x000007d1
|
||||
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
|
||||
uint32_t config_mode_storage_direct();
|
||||
void set_config_mode_storage_direct(uint32_t v);
|
||||
bool config_disable_config_mode_direct();
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <string.h>
|
||||
#include "string_format.hpp"
|
||||
#include "file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
class UnTar {
|
||||
public:
|
||||
@@ -86,6 +88,10 @@ class UnTar {
|
||||
std::string binfile = "";
|
||||
std::string fn = "";
|
||||
int filesize;
|
||||
uint32_t app_checksum;
|
||||
bool app_file;
|
||||
bool first_read;
|
||||
bool corrupt_file{false};
|
||||
for (;;) {
|
||||
auto readres = a->read(buff, 512);
|
||||
if (!readres.is_ok()) return "";
|
||||
@@ -132,20 +138,49 @@ class UnTar {
|
||||
delete_file(fn);
|
||||
auto fres = f.open(fn, false, true);
|
||||
if (!fres.value().ok()) return "";
|
||||
app_file = (fn.substr(fn.size() - 5) == ".ppma");
|
||||
first_read = true;
|
||||
app_checksum = 0;
|
||||
corrupt_file = false;
|
||||
}
|
||||
while (filesize > 0) {
|
||||
readres = a->read(buff, 512);
|
||||
if (!readres.is_ok()) return "";
|
||||
if (!readres.is_ok()) {
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
bytes_read = readres.value();
|
||||
if (bytes_read < 512) {
|
||||
return "";
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
if (filesize < 512)
|
||||
bytes_read = filesize;
|
||||
if (app_file && first_read) {
|
||||
// Check app file header for min version for checksum support
|
||||
struct application_information_t* app_info = (struct application_information_t*)buff;
|
||||
if (app_info->header_version < MIN_HEADER_VERSION_FOR_CHECKSUM)
|
||||
app_file = false;
|
||||
first_read = false;
|
||||
}
|
||||
if (app_file) {
|
||||
app_checksum += simple_checksum((uint32_t)buff, bytes_read);
|
||||
}
|
||||
auto fwres = f.write(buff, bytes_read);
|
||||
if (!fwres.is_ok()) return "";
|
||||
if (!fwres.is_ok()) {
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
filesize -= bytes_read;
|
||||
f.sync();
|
||||
if ((filesize == 0) && app_file && (app_checksum != EXT_APP_EXPECTED_CHECKSUM)) {
|
||||
corrupt_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (corrupt_file) {
|
||||
delete_file(fn);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return binfile;
|
||||
|
||||
@@ -111,7 +111,7 @@ inline uint32_t ms_duration(
|
||||
if (sample_rate == 0 || bytes_per_sample == 0)
|
||||
return 0;
|
||||
|
||||
return buffer_size / bytes_per_sample / sample_rate * 1000;
|
||||
return buffer_size * 1000 / (bytes_per_sample * sample_rate);
|
||||
}
|
||||
|
||||
int fast_int_magnitude(int y, int x);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 179 B |
@@ -2,31 +2,20 @@
|
||||
# (Freq range in MHz, min separation 3MHz)
|
||||
# (Description up to 20 char)
|
||||
# (fields comma delimiter)
|
||||
# PAGERS https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
|
||||
34,36,USA PAGERS 35
|
||||
43,45,USA PAGERS 44
|
||||
151,153,USA PAGERS 152
|
||||
453,454,USA PAGERS 454
|
||||
929,931,USA PAGERS 930
|
||||
# KEYFOBS
|
||||
260,500,315/433 MHz KEYFOBS
|
||||
# MICS, RADIOSONDES, LEYFOB
|
||||
140,380,VHF MICS AND MARINE
|
||||
390,420,RADIOSONDES
|
||||
420,660,UHF MICS
|
||||
# LoRa
|
||||
433,435,LoRa 443EU
|
||||
863,870,LoRa 868EU
|
||||
902,928,LoRa 915US
|
||||
779,787,LoRa 780CN
|
||||
# BROADCAST RADIO
|
||||
65,74,FM BROADCAST RUSSIA
|
||||
76,95,FM BROADCAST JAPAN
|
||||
76,108,FM BROADCAST BRAZIL
|
||||
87,108,FM BROADCAST
|
||||
# 433 MHz BAND
|
||||
423,443,433 BAND
|
||||
# AVIATION
|
||||
108,137,AVIATION 108
|
||||
960,1215,AVIATION 960
|
||||
# BROADCAST RADIO
|
||||
87,108,FM BROADCAST
|
||||
76,108,FM BROADCAST BRAZIL
|
||||
76,95,FM BROADCAST JAPAN
|
||||
65,74,FM BROADCAST RUSSIA
|
||||
# DECT
|
||||
1879,1931,DECT
|
||||
# FRS/GMRS
|
||||
462,468,FRS/GMRS
|
||||
# HAM RADIO
|
||||
28,30,HAM 10-METER
|
||||
50,54,HAM 6-METER
|
||||
@@ -35,6 +24,26 @@
|
||||
420,450,HAM 70-CM
|
||||
902,928,HAM 33-CM
|
||||
1240,1300,HAM 23-CM
|
||||
# KEYFOBS
|
||||
260,500,315/433 MHz KEYFOBS
|
||||
# LoRa
|
||||
433,435,LoRa 443EU
|
||||
863,870,LoRa 868EU
|
||||
902,928,LoRa 915US
|
||||
779,787,LoRa 780CN
|
||||
# MARINE BAND
|
||||
155,163,MARINE VHF
|
||||
# MICS
|
||||
140,380,MICS (VHF) AND MARINE
|
||||
420,660,MICS (UHF)
|
||||
# PAGERS https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
|
||||
34,36,USA PAGERS 35
|
||||
43,45,USA PAGERS 44
|
||||
151,153,USA PAGERS 152
|
||||
453,454,USA PAGERS 454
|
||||
929,931,USA PAGERS 930
|
||||
# RADIOSONDES
|
||||
390,420,RADIOSONDES
|
||||
# WATER METERS
|
||||
850,900,Water meters
|
||||
902,928,ISM 900MHz
|
||||
|
||||
Reference in New Issue
Block a user