Compare commits

...

14 Commits

Author SHA1 Message Date
Mark Thompson
ee2e57d702 2nd-Level Encoder Debouncing for scrappy encoder dials (#1838)
* Experimental encoder debouncing

* Experimental encoder debouncing

* Clang

* Comment changed
2024-02-01 14:13:28 -06:00
Mark Thompson
a2a5fb166e Improved Debounce for Encoders (#1837)
* Fix variable type declaration

* Fix typo

* Two-bit encoder debouncing

* Slight optimization

* Comment change
2024-01-31 14:13:21 -06:00
Mark Thompson
f59f5dfaa3 Fixed ms_duration function to show actual ms (#1836) 2024-01-31 21:08:17 +01:00
E.T
13f12227bd Remove ccache hack from dockerfiles (#1835)
Now we have a proper solution in cmake
2024-01-30 21:32:01 +01:00
Herman Semenov
c9db21063b Speedup compilation using ccache package (#1832) 2024-01-30 20:55:59 +01:00
Mark Thompson
125184f300 Add audio playback ability to WAV Viewer app (#1829) 2024-01-30 20:55:32 +01:00
ʎʞsʌoʞᴉɐɥɔꓕ ɹʇoʎꓒ
8068517808 add warn when enable stealth mode & add nav valid check (#1823)
* add warn when enable stealth

* change FLASH ERROR to FLASH ERR cuz previous one were overlapped with date

* check if nav is valid
2024-01-30 13:58:40 +08:00
Mark Thompson
a4f6bbda5e Fix compiler warning (#1827)
* Fix compiler warning

* Fix compiler warning

* Friggin C-lang
2024-01-29 18:07:32 -06:00
Totoo
8b2598fdac Call the flasher app when flashing from serial (#1825) 2024-01-29 20:52:35 +01:00
sommermoregentraum
c36fe78bd5 pacman: cheat lifes and cheat skip level (#1821)
cheat lives and cheat skip level and fixed review issue. Thanks @NotherNgineer
2024-01-29 00:54:13 +08:00
Mark Thompson
bd8385464e Tuner code readability (no functional change) (#1820) 2024-01-28 10:02:11 -06:00
Totoo
8c996b5bc6 Embedded friendly usb (#1817) 2024-01-27 19:33:08 +01:00
Mark Thompson
d1901939e1 Fix tuning of 2170-2300 and 2700-2740 MHz (#1819) 2024-01-27 13:30:32 +01:00
Mark Thompson
41ec11fb66 Check app checksums during untar (#1815) 2024-01-27 13:29:24 +01:00
33 changed files with 469 additions and 250 deletions

View File

@@ -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)

View File

@@ -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"]

View File

@@ -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

View File

@@ -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"]

View File

@@ -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

View File

@@ -116,15 +116,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
8, // 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);

View File

@@ -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() {

View File

@@ -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);
};

View File

@@ -105,6 +105,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),

View File

@@ -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) {
@@ -81,6 +82,8 @@ 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");
@@ -117,9 +120,90 @@ 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;
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();
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
16, // bits per sample
0, // tone key disabled
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
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,
@@ -128,21 +212,26 @@ ViewWavView::ViewWavView(
&text_title,
&text_duration,
&button_open,
&button_play,
&waveform,
&progressbar,
&field_pos_seconds,
&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)) {
@@ -155,6 +244,15 @@ ViewWavView::ViewWavView(
};
};
field_volume.set_value(field_volume.value());
button_play.on_select = [this, &nav](ImageButton&) {
if (this->is_active())
stop();
else
start_playback();
};
field_scale.on_change = [this](int32_t value) {
update_scale(value);
};
@@ -179,4 +277,9 @@ void ViewWavView::focus() {
button_open.focus();
}
ViewWavView::~ViewWavView() {
stop();
baseband::shutdown();
}
} /* namespace ui */

View File

@@ -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,6 +40,9 @@ 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;
@@ -46,6 +52,20 @@ class ViewWavView : public View {
void on_pos_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{};
@@ -60,10 +80,11 @@ class ViewWavView : public View {
{{0 * 8, 1 * 16}, "Samplerate:", 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}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 14 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 15 * 16}, "Delta:", Color::light_grey()},
{{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}};
Text text_filename{
{5 * 8, 0 * 16, 12 * 8, 16},
@@ -80,6 +101,13 @@ class ViewWavView : public View {
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,34 +117,29 @@ 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},
3,
{0, 999},
1,
' '};
NumberField field_pos_samples{
{14 * 8, 11 * 16},
{14 * 8, 12 * 16},
6,
{0, 999999},
1,
'0'};
NumberField field_scale{
{28 * 8, 11 * 16},
{28 * 8, 12 * 16},
2,
{1, 40},
1,
' '};
NumberField field_cursor_a{
{9 * 8, 12 * 16},
3,
{0, 239},
1,
' ',
true};
NumberField field_cursor_b{
{9 * 8, 13 * 16},
3,
{0, 239},
@@ -124,9 +147,40 @@ class ViewWavView : public View {
' ',
true};
NumberField field_cursor_b{
{9 * 8, 14 * 16},
3,
{0, 239},
1,
' ',
true};
Text text_delta{
{6 * 8, 14 * 16, 30 * 8, 16},
{7 * 8, 15 * 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 */

View File

@@ -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,

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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__*/

View File

@@ -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;
}

View File

@@ -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__*/

View File

@@ -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 */
/*************************************************************************/

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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)) {

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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

View File

@@ -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[]) {

View File

@@ -30,23 +30,31 @@
void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
if (!configured) return;
int32_t audio_sample_m;
// 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_m = sample * 256;
} else {
audio_sample_m = audio_sample;
}
// 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_m;
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
audio_output.write_unprocessed(audio_buffer);
}
@@ -69,7 +77,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 +91,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 +113,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);

View File

@@ -48,9 +48,11 @@ 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};
int8_t bytes_per_sample{1};
int16_t audio_sample_s16{};
int16_t audio_data[AUDIO_OUTPUT_BUFFER_SIZE];
buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
@@ -59,7 +61,7 @@ class AudioTXProcessor : public BasebandProcessor {
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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);